UNPKG

sparnatural

Version:

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

298 lines 15.9 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _SHACLSpecificationProvider_instances, _SHACLSpecificationProvider_parser, _SHACLSpecificationProvider_generator, _SHACLSpecificationProvider_skolemizeAnonymousPropertyShapes; import { DataFactory } from 'rdf-data-factory'; import { Parser, Generator } from "sparqljs"; import { BaseRDFReader, RDF } from "../BaseRDFReader"; import { SHACLSpecificationEntry } from "./SHACLSpecificationEntry"; import { SHACLSpecificationEntity, SpecialSHACLSpecificationEntityRegistry } from "./SHACLSpecificationEntity"; import { SHACLSpecificationProperty } from "./SHACLSpecificationProperty"; import { StoreModel } from '../../../rdf/StoreModel'; import { Dag } from '../../dag/Dag'; import { StatisticsReader } from '../StatisticsReader'; import { SH } from '../../../rdf/vocabularies/SH'; import { XSD } from '../../../rdf/vocabularies/XSD'; const factory = new DataFactory(); export class SHACLSpecificationProvider extends BaseRDFReader { constructor(n3store, lang) { super(n3store, lang); _SHACLSpecificationProvider_instances.add(this); _SHACLSpecificationProvider_parser.set(this, void 0); _SHACLSpecificationProvider_generator.set(this, void 0); // init SPARQL parser and generator once __classPrivateFieldSet(this, _SHACLSpecificationProvider_parser, new Parser(), "f"); __classPrivateFieldSet(this, _SHACLSpecificationProvider_generator, new Generator(), "f"); __classPrivateFieldGet(this, _SHACLSpecificationProvider_instances, "m", _SHACLSpecificationProvider_skolemizeAnonymousPropertyShapes).call(this); } getAllSparnaturalEntities() { const quadsArray = this.store.getQuads(null, RDF.TYPE, SH.NODE_SHAPE, null); const itemsSet = new Set(); for (const quad of quadsArray) { var nodeShapeId = quad.subject.value; itemsSet.add(this.getEntity(nodeShapeId)); } let items = SHACLSpecificationEntry.sort(Array.from(itemsSet)); return items.map(e => e.getId()); } getEntitiesInDomainOfAnyProperty() { // map to extract just the uri return this.getInitialEntityList().map(e => e.getId()); } getEntitiesTreeInDomainOfAnyProperty() { // 1. get the entities that are in a domain of a property let entities = this.getInitialEntityList(); // 2. add the children of these entities - recursively // while the children of every entity is not found in our flat list of entity, continue adding children while (!entities.every(entity => { return entity.getChildren().every(child => { // avoid testing deactivated shapes return (this.isDeactivated(factory.namedNode(child))) || (entities.find(anotherEntity => anotherEntity.getId() === child) != undefined); }); })) { let childrenToAdd = []; // for each entity in the initial list... entities.forEach(entity => { // foreach child of this entity... entity.getChildren().forEach(child => { // if this child is not in the list, add it if ( // don't put deactivated shapes in the list !this.isDeactivated(factory.namedNode(child)) && !entities.find(anotherEntity => anotherEntity.getId() === child)) { childrenToAdd.push(this.getEntity(child)); } }); }); childrenToAdd.forEach(p => entities.push(p)); } // 3. complement the initial list with their parents let disabledList = new Array(); while (!entities.every(entity => { return entity.getParents().every(parent => { return (entities.find(anotherEntity => anotherEntity.getId() === parent) != undefined); }); })) { let parentsToAdd = []; entities.forEach(entity => { entity.getParents().forEach(parent => { if (!entities.find(anotherEntity => anotherEntity.getId() === parent)) { parentsToAdd.push(this.getEntity(parent)); } }); }); parentsToAdd.forEach(p => entities.push(p)); // also keep that as a disabled node parentsToAdd.forEach(p => disabledList.push(p.getId())); } let dag = new Dag(); dag.initFromParentableAndIdAbleEntity(entities, disabledList); let statisticsReader = new StatisticsReader(new StoreModel(this.store)); // add count dag.traverseBreadthFirst((node) => { if (node.parents.length == 0) { // if this is a root // add a count to it node.count = statisticsReader.getEntitiesCountForShape(factory.namedNode(node.payload.getId())); } else { // otherwise make absolutely sure the count is undefined node.count = undefined; } }); // sort tree dag.sort(SHACLSpecificationEntity.compare); return dag; } expandSparql(sparql, prefixes) { // for each sh:targetClass this.store .getQuads(null, SH.TARGET_CLASS, null, null) .forEach((quad) => { // find it with the full URI var re = new RegExp("<" + quad.subject.value + ">", "g"); sparql = sparql.replace(re, "<" + quad.object.value + ">"); }); // for each NodeShape that is itself a rdfs:Class ... // do nothing :-) their URI is already correct // for each sh:path this.store .getQuads(null, SH.PATH, null, null) .forEach((quad) => { try { // find it with the full URI var re = new RegExp("<" + quad.subject.value + ">", "g"); let sparqlReplacementString = new StoreModel(this.store).pathToSparql(quad.object); sparql = sparql.replace(re, sparqlReplacementString); } catch (error) { console.error("Unsupported sh:path for " + quad.subject.value + " - review your configuration"); } }); // for each sh:target/sh:select ... this.store .getQuads(null, SH.TARGET, null, null) .forEach((q1) => { // get the subject URI that will be replaced let nodeShapeUri = q1.subject.value; this.store .getQuads(q1.object, SH.SELECT, null, null) .forEach((quad) => { let sparqlTarget = quad.object.value; // extract everything between '{' and '}' let beginWhere = sparqlTarget.substring(sparqlTarget.indexOf('{') + 1); let whereClause = beginWhere.substring(0, beginWhere.lastIndexOf('}')).trim(); // replace the $this with the name of the original variable in the query // \S matches any non-whitespace charracter // note how we match optionnally the final dot of the triple, and an optional space after the type (not always here) // flag "g" is for global search var re = new RegExp("(\\S*) (rdf:type|a|<http://www\\.w3\\.org/1999/02/22-rdf-syntax-ns#type>) <" + nodeShapeUri + ">( ?\\.)?", "g"); let replacer = function (match, // name of the variable being matched, e.g. ?Person_1 p1, offset, fullString) { // first substitutes any other variable name with a prefix // so that we garantee unicity across the complete query var reVariables = new RegExp("\\?(\\S*)", "g"); let whereClauseReplacedVariables = whereClause.replace(reVariables, "?$1_" + p1.substring(1)); // then, replace the match on the original URI with the whereClause of the target // replacing "$this" with the original variable name var reThis = new RegExp("\\$this", "g"); let whereClauseReplacedThis = whereClauseReplacedVariables.replace(reThis, p1); // then we make sure the where clause properly ends with a dot if (!whereClauseReplacedThis.trim().endsWith(".")) { whereClauseReplacedThis += "."; } return whereClauseReplacedThis; }; sparql = sparql.replace(re, replacer); // add the prefixes from the target query to our query var parsedTargetQuery = __classPrivateFieldGet(this, _SHACLSpecificationProvider_parser, "f").parse(sparqlTarget); for (var key in parsedTargetQuery.prefixes) { let prefixUri = parsedTargetQuery.prefixes[key]; // prepend the SPARQL prefix declaration to our SPARQL query if it does not already contain it if (!sparql.includes("PREFIX " + key + ": <" + prefixUri + ">")) { sparql = "PREFIX " + key + ": <" + prefixUri + ">\n" + sparql; } } }); }); // reparse the query, apply prefixes, and reserialize the query // console.log(sparql) // console.log(this.#parser.parse("SELECT ?this WHERE { ?this a <http://www.sparna.fr/foo> . FILTER(?this = \"foo\" || ?this = \"bar\" || ?this = \"baz\") }")); var query = __classPrivateFieldGet(this, _SHACLSpecificationProvider_parser, "f").parse(sparql); for (var key in prefixes) { query.prefixes[key] = prefixes[key]; } return __classPrivateFieldGet(this, _SHACLSpecificationProvider_generator, "f").stringify(query); } getLanguages() { let languages = this.store .getQuads(null, SH.NAME, null, null) .map((quad) => quad.object.language); // deduplicate the list of languages return [...new Set(languages)]; } getEntity(uri) { if (SpecialSHACLSpecificationEntityRegistry.getInstance().getRegistry().has(uri)) { return SpecialSHACLSpecificationEntityRegistry.getInstance().getRegistry().get(uri); } return new SHACLSpecificationEntity(uri, this, this.store, this.lang); } getProperty(uri) { if (!this.graph.hasTriple(factory.namedNode(uri), null, null)) { console.warn("The requested property " + uri + " is unknown in the configuration. We cannot find any triple with this URI as subject."); return undefined; } return new SHACLSpecificationProperty(uri, this, this.store, this.lang); } getInitialEntityList() { const duplicatedNodeShapes = this.store.getQuads(null, SH.PROPERTY, null, null).map(triple => triple.subject); let dedupNodeShapes = [...new Set(duplicatedNodeShapes)]; // remove from the initial list the NodeShapes that are marked with sh:deactivated let that = this; dedupNodeShapes = dedupNodeShapes.filter(node => { return !that.isDeactivated(node); }); // remove from the initial list the NodeShapes that are connected to only properties that Sparnatural will not use // by checking the lenght of the list of properties we can make sure of that dedupNodeShapes = dedupNodeShapes.filter(node => { return this.getEntity(node.value).getProperties().length > 0; }); var items = []; for (const aNode of dedupNodeShapes) { items.push(this.getEntity(aNode.value)); } items = SHACLSpecificationEntry.sort(items); return items; } /** * @param node a NodeShape * @returns true if the NodeShape is the subject of sh:deactivated true */ isDeactivated(node) { return this.graph.hasTriple(node, SH.DEACTIVATED, factory.literal("true", XSD.BOOLEAN)); } getNodeShapesLocallyReferencedWithShNode() { const duplicatedNodeShapes = this.store.getQuads(null, SH.NODE, null, null).map(triple => triple.object.value); let dedupNodeShapes = [...new Set(duplicatedNodeShapes)]; return dedupNodeShapes; } /** * @returns the NodeShape targeting the provided class, either implicitly or explicitly through sh:targetClass * @param c */ getNodeShapeTargetingClass(c) { if (this.graph.hasTriple(c, RDF.TYPE, SH.NODE_SHAPE)) { // class if also a NodeShape, return it directly return c; } else { let shapes = this.graph.findSubjectsWithPredicate(SH.TARGET_CLASS, c); if (shapes.length > 0) { if (shapes.length > 1) { console.warn("Warning, found more than one NodeShape targeting class " + c.value); } return shapes[0]; } return null; } } } _SHACLSpecificationProvider_parser = new WeakMap(), _SHACLSpecificationProvider_generator = new WeakMap(), _SHACLSpecificationProvider_instances = new WeakSet(), _SHACLSpecificationProvider_skolemizeAnonymousPropertyShapes = function _SHACLSpecificationProvider_skolemizeAnonymousPropertyShapes() { // any subject of an sh:path... const quadsArray = this.store.getQuads(null, SH.PROPERTY, null, null); let i = 0; for (const quad of quadsArray) { var propertyShapeNode = quad.object; if (propertyShapeNode.termType == "BlankNode") { // 1. get the base URI pointing to this property shape var nodeShape = quad.subject; if (nodeShape.termType == "NamedNode") { let uri = quad.subject.value; // 2. build a property shape URI from it let propertyShapeUri = uri + "_" + i; // 3. replace all triples where the blank node is subject this.store.getQuads(propertyShapeNode, null, null, null).forEach(q => { this.store.removeQuad(q); this.store.addQuad(factory.quad(factory.namedNode(propertyShapeUri), q.predicate, q.object, q.graph)); }); // 4. replace all triples where the blank node is object this.store.getQuads(null, null, propertyShapeNode, null).forEach(q => { this.store.removeQuad(q); this.store.addQuad(factory.quad(q.subject, q.predicate, factory.namedNode(propertyShapeUri), q.graph)); }); } } i++; } }; //# sourceMappingURL=SHACLSpecificationProvider.js.map