Commit 8ec4771b authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Support the detection of primary topic

parent 83d2596a244b
......@@ -190,22 +190,33 @@ function setHeader(
}
/**
* Detects linked data in HTTP headers
* @param headers he headers
* Gets all the links in the headers
* @param headers The headers
*/
function detectDataOnLinks(
headers: chrome.webRequest.HttpHeader[]
): DataSourceLinked[] {
function getAllLinks(headers: chrome.webRequest.HttpHeader[]): Link[] {
let linksContent = getHeader(headers, "Link");
if (linksContent == null) return [];
let links = parseLinks(linksContent);
return parseLinks(linksContent);
}
/**
* Detects the primary topic in the links
* @param links The links in the header
*/
function detectTopicOnlinks(links: Link[]): string {
return links
.filter((link: Link) => {
return link.refersToData();
})
.map((link: Link) => {
return new DataSourceLinked(link);
});
.filter((link: Link) => link.refersToPrimaryTopic())
.reduce((acc: string, link: Link) => (acc != null ? acc : link.url), null);
}
/**
* Detects linked data in HTTP headers
* @param links The links in the header
*/
function detectDataOnLinks(links: Link[]): DataSourceLinked[] {
return links
.filter((link: Link) => link.refersToData())
.map((link: Link) => new DataSourceLinked(link));
}
/**
......@@ -284,18 +295,21 @@ function onHeadersReceived(
// already detected something
return {};
// try to detect the primary topic in HTTP headers' links
let links = getAllLinks(details.responseHeaders);
data.primaryTopic = detectTopicOnlinks(links);
// determine if we shall preempt the content due to its type (raw RDF data)
let mime = shouldPreempt(details);
if (mime != null) {
// preempts this request
return doPreempt(details, mime);
}
// try to detect alternate sources of content
data.sources.push(detectDataOnContent(details, details.responseHeaders));
data.sources = data.sources.concat(
detectDataOnLinks(details.responseHeaders)
);
data.sources = data.sources.concat(detectDataOnLinks(links));
data.selectMain();
// if there are still nothing, try to probe with HTTP content negotiation
if (data.mainSource == NO_DATA) {
// try to probe with HTTP content negotiation
tryNegotiateData(details.url)
.then((source: DataSourceLinked) => {
data.sources.push(source);
......@@ -345,12 +359,11 @@ F.registerNavigations(onBeforeNavigate, onCompleted);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
let data = getTabData(sender.tab.id);
if (request.reqType == "links") {
data.sources = data.sources.concat(
request.links
.filter((link: Link) => link.refersToData())
.map((link: Link) => new DataSourceLinked(link))
);
if (data.primaryTopic == null) {
data.primaryTopic = detectTopicOnlinks(request.links);
}
data.sources = data.sources.concat(detectDataOnLinks(request.links));
data.selectMain();
updateIcon(sender.tab.id);
updateTab(sender.tab.id);
}
});
......@@ -92,6 +92,16 @@ export class Link {
MIME.hasOwnProperty(this.tags.type)
);
}
/**
* Determines whether this link refers to the primary topic on a page
*/
public refersToPrimaryTopic(): boolean {
return (
this.tags.hasOwnProperty("rev") &&
"describedBy".toLocaleLowerCase() == this.tags.rev.toLocaleLowerCase()
);
}
}
/**
......@@ -301,6 +311,7 @@ export class TabData {
this.mainSource = NO_DATA;
this.sources = [];
this.clear = this.clear.bind(this);
this.primaryTopic = null;
this.selectMain = this.selectMain.bind(this);
}
......@@ -316,6 +327,10 @@ export class TabData {
* All the alternative data sources
*/
sources: DataSource[];
/**
* The primary topic on this tab
*/
primaryTopic: string;
/**
* Clears this data
......@@ -323,6 +338,7 @@ export class TabData {
clear(): void {
this.mainSource = NO_DATA;
this.sources = [];
this.primaryTopic = null;
}
/**
......
......@@ -21,7 +21,7 @@
import { RawContent, TabData, fetchSource } from "./api";
/// <reference path="./rdflib-interface.d.ts"/>
import { RDF } from "rdflib";
import { RDF, RdfStore } from "rdflib";
let _rdf = require("rdflib");
export const $rdf: RDF = _rdf;
......@@ -52,3 +52,36 @@ export function loadRdfStore(tabData: TabData): Promise<any> {
});
});
}
/**
* Determines whether the RDF store with the specified primary topic matches a trait:
* Has a primary topic
* @param store The RDF store
* @param primaryTopic The primary topic
*/
export function hasTraitHasPrimary(
store: RdfStore,
primaryTopic: string
): boolean {
return primaryTopic != null;
}
/**
* Determines whether the RDF store with the specified primary topic matches a trait:
* The primary topic is a person in DBPedia
* @param store The RDF store
* @param primaryTopic The primary topic
*/
export function hasTraitDbPediaPerson(
store: RdfStore,
primaryTopic: string
): boolean {
if (!hasTraitHasPrimary(store, primaryTopic)) return false;
let a = store.each(
$rdf.sym(primaryTopic),
$rdf.sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
$rdf.sym("http://dbpedia.org/ontology/Person"),
undefined
);
return a != null && a != undefined && a.length > 0;
}
......@@ -73,7 +73,7 @@ declare module "rdflib" {
predicate: NamedNode,
object: Term,
why: NamedNode
): Triple;
): Term;
each(
subject: SubjectNode,
......
......@@ -27,12 +27,13 @@ import {
/// <reference path="../../common/rdflib-interface.d.ts"/>
import { RdfStore, Triple, Term, NamedNode, BlankNode, Literal } from "rdflib";
import * as React from "react";
import { hasTraitDbPediaPerson } from "../../common/rdf";
export class NTripleRendering implements ViewImplementation {
constructor() {
this.descriptor = {
identifier: "::Defaults::NTripleRendering",
name: "N-Triples default rendering",
name: "RDF Triples",
description: "Renders a complete RDF dataset using the N-Triples syntax",
origin: null
};
......@@ -43,7 +44,7 @@ export class NTripleRendering implements ViewImplementation {
descriptor: ViewDescriptor;
priorityFor(store: RdfStore, target: string): number {
return Number.MIN_SAFE_INTEGER;
return Number.MAX_SAFE_INTEGER;
}
render(
......@@ -118,6 +119,71 @@ function renderRdfNode(node: Term): React.ReactNode {
return null;
}
export class DBPediaPersonRendering implements ViewImplementation {
constructor() {
this.descriptor = {
identifier: "::Defaults::DBPediaPerson",
name: "DBPedia Person view",
description: "Renders a DBPedia person",
origin: null
};
this.priorityFor = this.priorityFor.bind(this);
this.render = this.render.bind(this);
}
descriptor: ViewDescriptor;
priorityFor(store: RdfStore, target: string): number {
return hasTraitDbPediaPerson(store, target) ? 10 : -1;
}
render(
renderer: ViewRenderer,
store: RdfStore,
root: string,
target: string
): React.ReactNode {
return (
<div
style={{
width: "90%",
marginLeft: "5%",
marginTop: "5vh",
marginBottom: "5vh"
}}
>
<div
style={{
width: "75%",
marginLeft: "12.5%",
marginTop: "2vh",
marginBottom: "2vh"
}}
>
<div className="alert alert-info" role="alert">
{this.descriptor.name}
</div>
</div>
<table className="table-striped table-condensed">
<thead>
<tr>
<td>Subject</td>
<td>Property</td>
<td>Object</td>
</tr>
</thead>
<tbody>
{store.statements.map((triple: Triple, index: number) => {
return renderRdfTriple(triple, index);
})}
</tbody>
</table>
</div>
);
}
}
export function register(): void {
REGISTRY.register(new NTripleRendering());
REGISTRY.register(new DBPediaPersonRendering());
}
......@@ -36,13 +36,15 @@ class State {
stateType: StateType;
message: string;
content: RdfStore;
root: string;
}
export class Viewer extends React.Component<any, State> implements Application {
state: State = {
stateType: StateType.Loading,
message: "Loading",
content: null
content: null,
root: null
};
constructor(props: any) {
......@@ -54,12 +56,14 @@ export class Viewer extends React.Component<any, State> implements Application {
/**
* Sets the content for the application
* @param content The content
* @param root The URI of the root resource
*/
public onContent(content: RdfStore): void {
public onContent(content: RdfStore, root: string): void {
let newState: State = {
stateType: StateType.Displaying,
message: "",
content: content
content: content,
root: root
};
this.setState(newState);
}
......@@ -72,7 +76,8 @@ export class Viewer extends React.Component<any, State> implements Application {
let newState: State = {
stateType: StateType.Error,
message: description,
content: null
content: null,
root: null
};
this.setState(newState);
}
......@@ -110,7 +115,7 @@ export class Viewer extends React.Component<any, State> implements Application {
);
} else if (this.state.stateType == StateType.Displaying) {
let renderer = new ViewRenderer(this, REGISTRY);
let result = renderer.render(this.state.content, null); // TODO: pass root here
let result = renderer.render(this.state.content, this.state.root);
if (result != null) return result;
return (
<div
......
......@@ -28,8 +28,9 @@ export interface Application {
/**
* Sets the content for the application
* @param content The content
* @param root The URI of the root resource
*/
onContent(content: RdfStore): void;
onContent(content: RdfStore, root: string): void;
/**
* Sets the application on error
......
......@@ -123,20 +123,21 @@ export class ViewRegistry {
let implementations = this.implementations;
let sorted: PrioritizedViewImplementation[] = Object.keys(
this.implementations
).map((id: string) => {
let impl = implementations[id];
return {
implementation: impl,
priority: impl.priorityFor(store, target)
};
});
sorted = sorted.sort(
(x: PrioritizedViewImplementation, y: PrioritizedViewImplementation) => {
return x.priority - y.priority;
}
);
)
.map((id: string) => {
let impl = implementations[id];
return {
implementation: implementations[id],
priority: impl.priorityFor(store, target)
};
})
.filter((a: PrioritizedViewImplementation) => a.priority >= 0)
.sort(
(x: PrioritizedViewImplementation, y: PrioritizedViewImplementation) =>
x.priority - y.priority
);
if (sorted.length == 0) return null;
return sorted[sorted.length - 1].implementation;
return sorted[0].implementation;
}
}
......
......@@ -38,7 +38,7 @@ function onUpdateActivate(tabData: TabData): void {
let app = App.injectApplication();
let p = loadRdfStore(tabData);
p.then((store: any) => {
app.onContent(store);
app.onContent(store, tabData.primaryTopic);
}).catch(reason => {
app.onError(reason.toString());
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment