sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
301 lines (260 loc) • 9.77 kB
text/typescript
import { DataFactory } from 'rdf-data-factory';
import rdfParser from "rdf-parse";
var Readable = require('stream').Readable
import Datasources from "../ontologies/SparnaturalConfigDatasources";
import { RdfStore } from 'rdf-stores';
import { NamedNode, Quad, Stream, Term } from "@rdfjs/types";
import { StoreModel } from './StoreModel';
const factory = new DataFactory();
const RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
export const RDF = {
LANG_STRING: factory.namedNode(RDF_NAMESPACE + "langString") as NamedNode,
TYPE: factory.namedNode(RDF_NAMESPACE + "type") as NamedNode,
FIRST: factory.namedNode(RDF_NAMESPACE + "first") as NamedNode,
REST: factory.namedNode(RDF_NAMESPACE + "rest") as NamedNode,
NIL: factory.namedNode(RDF_NAMESPACE + "nil") as NamedNode,
};
const RDFS_NAMESPACE = "http://www.w3.org/2000/01/rdf-schema#";
export const RDFS = {
CLASS: factory.namedNode(RDFS_NAMESPACE + "Class") as NamedNode,
LABEL: factory.namedNode(RDFS_NAMESPACE + "label") as NamedNode,
COMMENT: factory.namedNode(RDFS_NAMESPACE + "comment") as NamedNode,
DOMAIN: factory.namedNode(RDFS_NAMESPACE + "domain") as NamedNode,
RANGE: factory.namedNode(RDFS_NAMESPACE + "range") as NamedNode,
RESOURCE: factory.namedNode(RDFS_NAMESPACE + "Resource") as NamedNode,
SUBPROPERTY_OF: factory.namedNode(RDFS_NAMESPACE + "subPropertyOf") as NamedNode,
SUBCLASS_OF: factory.namedNode(RDFS_NAMESPACE + "subClassOf") as NamedNode,
};
export class BaseRDFReader {
protected lang: string;
protected store: RdfStore;
protected graph: StoreModel;
constructor(n3store: RdfStore, lang: string) {
this.store = n3store;
this.lang = lang;
this.graph = new StoreModel(n3store);
}
static buildStoreFromString(configData:string, filePath:string, callback: any) {
const store:RdfStore = RdfStore.createDefault();
console.log("Building store from string...");
let quadStream: Stream<Quad> = BaseRDFReader.#toQuadStream(configData,filePath);
store.import(quadStream)
.on('error', () => console.log("Problem parsing inline config"))
.once('end', () => callback(store));
}
static buildStore(files: Array<string>, callback: any) {
// Create a new store with default settings
// see https://www.npmjs.com/package/rdf-stores
const store:RdfStore = RdfStore.createDefault();
let promises = new Array<Promise<RdfStore>>();
for(let config of files) {
console.log("Importing in store '" + config + "'");
let p:Promise<RdfStore> = new Promise((resolve, reject) =>
$.ajax({
method: "GET",
url: config,
dataType: "text",
}).done(function (configData) {
let quadStream: Stream<Quad> = BaseRDFReader.#toQuadStream(configData,config);
store.import(quadStream)
.on('error', reject)
.once('end', () => resolve(store));
}).fail(function (response) {
console.error(
"Sparnatural - unable to load RDF config file : " + config
);
console.log(response);
reject();
})
); // end Promise
promises.push(p);
}
// when all done, call callback
Promise.all(promises).then((values) => {
console.log(
"Specification store populated with " +
store.countQuads(
null,
null,
null,
null
) +
" triples."
);
callback(store);
})
}
static #toQuadStream(string:any, filePath:any) {
// turn input string into a stream
var textStream = new Readable();
textStream.push(string) // the string you want
textStream.push(null) // indicates end-of-file basically - the end of the stream
var quadStream;
try {
// attempt to parse based on path
console.log("Attempt to parse determining format from path " + filePath+"...");
quadStream = rdfParser.parse(textStream, { path: filePath });
} catch (exception) {
try {
console.log("Attempt to parse in Turtle...");
// attempt to parse in turtle
quadStream = rdfParser.parse(textStream, {
contentType: "text/turtle",
});
} catch (exception) {
console.log("Attempt to parse in RDF/XML...");
// attempt to parse in RDF/XML
quadStream = rdfParser.parse(textStream, {
contentType: "application/rdf+xml",
});
}
}
return quadStream;
}
_readDatasourceAnnotationProperty(
propertyOrClassId: any,
datasourceAnnotationProperty: any
) {
// read predicate datasource
const datasourceQuads = this.store.getQuads(
factory.namedNode(propertyOrClassId),
factory.namedNode(datasourceAnnotationProperty),
null,
null
);
if (datasourceQuads.length == 0) {
return null;
}
for (const datasourceQuad of datasourceQuads) {
const datasourceUri = datasourceQuad.object.value;
var knownDatasource = Datasources.DATASOURCES_CONFIG.get(datasourceUri);
if (knownDatasource != null) {
return knownDatasource;
} else {
return this.#_buildDatasource(datasourceUri);
}
}
// IMPORTANT should here be propper error handling?
return {};
}
/**
* {
* queryString: "...",
* queryTemplate: "...",
* labelPath: "...",
* labelProperty: "...",
* childrenPath: "...",
* childrenProperty: "...",
* noSort: true
* }
**/
#_buildDatasource(datasourceUri: string) {
var datasource: {
queryString?: string;
queryTemplate?: any;
labelPath?: any;
labelProperty?: any;
childrenPath?: any;
childrenProperty?: any;
sparqlEndpointUrl?: any;
noSort?: any;
} = {};
// read datasource characteristics
// Alternative 1 : read optional queryString
var queryStrings = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.QUERY_STRING)
).map(n=>n.value);
if (queryStrings.length > 0) {
datasource.queryString = queryStrings[0];
}
// Alternative 2 : query template + label path
var queryTemplates = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.QUERY_TEMPLATE)
).map(n=>n.value);
if (queryTemplates.length > 0) {
var theQueryTemplate = queryTemplates[0];
var knownQueryTemplate =
Datasources.QUERY_STRINGS_BY_QUERY_TEMPLATE.get(theQueryTemplate);
if (knownQueryTemplate != null) {
// 2.1 It is known in default Sparnatural ontology
datasource.queryTemplate = knownQueryTemplate;
} else {
// 2.2 Unknown, read the query string on the query template
var queryStrings = this.graph.readProperty(
factory.namedNode(theQueryTemplate),
factory.namedNode(Datasources.QUERY_STRING)
).map(n=>n.value);
if (queryStrings.length > 0) {
var queryString = queryStrings[0];
datasource.queryTemplate =
queryString.startsWith('"') && queryString.endsWith('"')
? queryString.substring(1, queryString.length - 1)
: queryString;
}
}
// labelPath
var labelPaths = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.LABEL_PATH)
).map(n=>n.value);
if (labelPaths.length > 0) {
datasource.labelPath = labelPaths[0];
}
// labelProperty
var labelProperties = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.LABEL_PROPERTY)
).map(n=>n.value);
if (labelProperties.length > 0) {
datasource.labelProperty = labelProperties[0];
}
// childrenPath
var childrenPaths = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.CHILDREN_PATH)
).map(n=>n.value);
if (childrenPaths.length > 0) {
datasource.childrenPath = childrenPaths[0];
}
// childrenProperty
var childrenProperties = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.CHILDREN_PROPERTY)
).map(n=>n.value);
if (childrenProperties.length > 0) {
datasource.childrenProperty = childrenProperties[0];
}
}
// read optional sparqlEndpointUrl
var sparqlEndpointUrls = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.SPARQL_ENDPOINT_URL)
).map(n=>n.value);
if (sparqlEndpointUrls.length > 0) {
datasource.sparqlEndpointUrl = sparqlEndpointUrls[0];
}
// read optional noSort
var noSorts = this.graph.readProperty(
factory.namedNode(datasourceUri),
factory.namedNode(Datasources.NO_SORT)
).map(n=>n.value);
if (noSorts.length > 0) {
datasource.noSort = noSorts[0] === "true";
}
return datasource;
}
/**
* Reads rdf:type(s) of an entity, and return them as an array
**/
_readRdfTypes(uri: Term):Term[] {
return this.graph.readProperty(uri, RDF.TYPE);
}
_pushIfNotExist(item: any, items: any[]) {
if (items.indexOf(item) < 0) {
items.push(item);
}
return items;
}
}