sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
313 lines • 12.1 kB
JavaScript
import { RDF } from "../sparnatural/spec-providers/BaseRDFReader";
import { DataFactory } from "rdf-data-factory";
import { SH } from "./vocabularies/SH";
import { XSD } from "./vocabularies/XSD";
let DF = new DataFactory();
export class StoreModel {
constructor(n3store) {
this.store = n3store;
}
static getLocalName(uri) {
if (uri.includes('#'))
return uri.split('#').pop();
return uri.split('/').pop();
}
/**
* Reads the given property on an entity, and return values as an array
**/
readProperty(subject, property) {
return this.store
.getQuads(subject, property, null, null)
.map(quad => quad.object);
}
/**
* Reads the given property on an entity recursively, and return values as an array
**/
readPropertyRec(subject, property) {
let values = this.store
.getQuads(subject, property, null, null)
.map(quad => quad.object);
let recValues = [];
values.forEach(v => {
recValues.push(...this.readPropertyRec(v, property));
});
values.push(...recValues);
return values;
}
/**
* Reads the given property on an entity, and returns the first value found, or undefined if not found
**/
readSingleProperty(subject, property) {
var values = this.readProperty(subject, property);
if (values.length > 0) {
return values[0];
}
}
/**
* Reads the given property on an entity, and returns the first value found cast to Number
**/
readSinglePropertyAsNumber(subject, property) {
var term = this.readSingleProperty(subject, property);
if (term) {
return Number.parseInt(term.value);
}
}
/**
* Finds all subjects having the given property with the given object, or undefined if not found
**/
findSubjectsOf(property, object) {
return this.store
.getQuads(null, property, object, null)
.map(quad => quad.subject);
}
/**
* Finds the subjects having the given property with the given object, and returns the first value found, or undefined if not found
*/
findSingleSubjectOf(property, object) {
var values = this.findSubjectsOf(property, object);
if (values.length > 0) {
return values[0];
}
}
readPropertyInLang(subject, property, lang, defaultToNoLang = true) {
var values = this.store
.getQuads(subject, property, null, null)
.filter((quad) => quad.object.language == lang)
.map((quad) => quad.object);
if (values.length == 0 && defaultToNoLang) {
values = this.store
.getQuads(subject, property, null, null)
.filter((quad) => quad.object.language == "")
.map((quad) => quad.object);
}
return values;
}
/**
* Reads the given property on an entity, and returns the first value found, or undefined if not found
**/
readSinglePropertyInLang(subject, property, lang, defaultToNoLang = true) {
var values = this.readPropertyInLang(subject, property, lang, defaultToNoLang);
if (values.length > 0) {
return values[0];
}
}
size() {
return this.store.size;
}
hasProperty(subject, property) {
return this.hasTriple(subject, property, null);
}
hasTriple(rdfNode, property, value) {
return (this.store.getQuads(rdfNode, property, value, null).length > 0);
}
findSubjectsWithPredicate(property, rdfNode) {
return this.store
.getQuads(null, property, rdfNode, null)
.map(quad => quad.subject);
}
/****** LIST HANDLING ********/
listContains(rdfNode, propertyList, property, value) {
let found = false;
let listContent = this.readAsList(rdfNode, propertyList);
listContent.forEach(node => {
if (this.hasTriple(node, property, value))
found = true;
});
return found;
}
/**
* returns RDFTerms
*/
readAsList(rdfNode, property) {
let result = new Array();
this.store
.getQuads(rdfNode, property, null, null)
.map((quad) => {
result.push(...this.readListContent(quad.object));
});
return result;
}
/**
* returns RDFTerms
*/
readListContent(list) {
var result = this.store
.getQuads(list, RDF.FIRST, null, null)
.map((quad) => quad.object);
var subLists = this.readProperty(list, RDF.REST);
if (subLists.length > 0) {
result = result.concat(this.readListContent(subLists[0]));
}
return result;
}
readRootList(listId) {
var root = this.readSuperList(listId);
if (root == null) {
return listId;
}
else {
return this.readRootList(root);
}
}
readSuperList(listId) {
const propertyQuads = this.store.getQuads(null, RDF.REST, listId, null);
if (propertyQuads.length > 0) {
return propertyQuads[0].subject;
}
else {
return null;
}
}
/******* END LIST HANDLING *********/
/**
* Finds the most suitable value of a given property for a list of languages.
* Falls back to literals with no language if no better literal is found.
* @param subject The subject resource.
* @param langs The allowed languages.
* @param properties The properties to check.
* @return The best suitable value or null.
*/
getBestStringLiteral(subject, langs, properties) {
let bestLiteral = null;
let bestLangIndex = -1;
for (const property of properties) {
const quads = this.store.getQuads(subject, property, null, null);
for (const quad of quads) {
const object = quad.object;
if (object.termType === "Literal") {
const lang = object.language || "";
if (lang === "" && bestLiteral === null) {
bestLiteral = object;
}
else {
let startLangIndex = bestLangIndex < 0 ? langs.length - 1 : bestLangIndex;
for (let i = startLangIndex; i >= 0; i--) {
const langPreference = langs[i];
if (langPreference.toLowerCase() === lang.toLowerCase()) {
bestLiteral = object;
bestLangIndex = i;
}
else if (lang.includes("-") && lang.startsWith(langPreference) && bestLiteral === null) {
bestLiteral = object;
}
}
}
}
}
}
return bestLiteral;
}
/**
* Render a list of RDFNode as a string, in plain text.
* @param list The list of RDFNode to be displayed.
* @return A comma-separated string.
*/
static render(list) {
return this.renderWithPlainString(list, true);
}
/**
* Render an RDFNode as a string, in plain text.
* @param node The node to be rendered.
* @return The rendered string.
*/
static renderNode(node) {
return this.renderWithPlainString([node], true);
}
/**
* Render a list of RDFNode as a comma-separated string. If plainString is true, make it a plain string,
* otherwise uses HTML markup to display e.g. datatypes and languages in "sup" tags.
* @param list The list of RDFNode to be displayed.
* @param plainString True to retrieve a plain string, false to retrieve a piece of HTML.
* @return The rendered string.
*/
static renderWithPlainString(list, plainString) {
if (!list || list.length === 0) {
return "";
}
return list.map(item => this.renderSingleNode(item, plainString)).join(", ");
}
/**
* Render an RDFNode as a string. If plainString is true, make it a plain string,
* otherwise uses HTML markup to display e.g. datatypes and languages in "sup" tags.
* @param node The node to be rendered.
* @param plainString True to retrieve a plain string, false to retrieve a piece of HTML.
* @return The rendered string.
*/
static renderSingleNode(node, plainString) {
if (!node) {
return "";
}
if (node.termType === "NamedNode") {
return node.value; // Assuming short form is handled elsewhere.
}
else if (node.termType === "BlankNode") {
return node.value;
}
else if (node.termType === "Literal") {
if (plainString) {
return node.value;
}
if (node.datatype && node.datatype.value !== RDF.LANG_STRING.value) {
if (node.datatype.value !== XSD.STRING.value) {
return `"${node.value}"<sup>^^${node.datatype.value}</sup>`;
}
else {
return `"${node.value}"`;
}
}
else if (node.language) {
return `"${node.value}"<sup>@${node.language}</sup>`;
}
else {
return node.value;
}
}
else {
return node.value;
}
}
/**
* Renders the provided SHACL property path as a SPARQL property path syntax, using prefixed URIs.
* @param path The SHACL property path to render in SPARQL.
* @param usePrefixes True to use prefixes, false to use full URIs.
* @return The rendered SPARQL property path.
*/
pathToSparql(path, usePrefixes = false) {
if (path.termType === "NamedNode") {
if (usePrefixes) {
return StoreModel.getLocalName(path.value);
}
else {
return `<${path.value}>`;
}
}
else if (path.termType === "BlankNode") {
if (this.store.getQuads(path, RDF.FIRST, null, null).length > 0) {
// This is an RDF list, indicating a sequence path
const sequence = this.readListContent(path);
return sequence.map(t => this.pathToSparql(t, usePrefixes)).join("/");
}
else {
if (this.store.getQuads(path, SH.ONE_OR_MORE_PATH, null, null).length > 0) {
return this.pathToSparql(this.store.getQuads(path, SH.ONE_OR_MORE_PATH, null, null)[0].object, usePrefixes) + "+";
}
if (this.store.getQuads(path, SH.INVERSE_PATH, null, null).length > 0) {
return "^" + this.pathToSparql(this.store.getQuads(path, SH.INVERSE_PATH, null, null)[0].object, usePrefixes);
}
if (this.store.getQuads(path, SH.ALTERNATIVE_PATH, null, null).length > 0) {
const list = this.store.getQuads(path, SH.ALTERNATIVE_PATH, null, null)[0].object;
const sequence = this.readListContent(list);
return `(${sequence.map(t => this.pathToSparql(t, usePrefixes)).join("|")})`;
}
if (this.store.getQuads(path, SH.ZERO_OR_MORE_PATH, null, null).length > 0) {
return this.pathToSparql(this.store.getQuads(path, SH.ZERO_OR_MORE_PATH, null, null)[0].object, usePrefixes) + "*";
}
if (this.store.getQuads(path, SH.ZERO_OR_ONE_PATH, null, null).length > 0) {
return this.pathToSparql(this.store.getQuads(path, SH.ZERO_OR_ONE_PATH, null, null)[0].object, usePrefixes) + "?";
}
}
}
throw new Error("Unsupported SHACL property path");
}
}
//# sourceMappingURL=StoreModel.js.map