UNPKG

sparnatural

Version:

Visual client-side SPARQL query builder and knowledge graph exploration tool

313 lines 12.1 kB
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