UNPKG

lincd

Version:

LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)

289 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PropertyShape = exports.NodeShape = exports.SHACL_Shape = void 0; /* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ const models_1 = require("../models"); const Shape_1 = require("./Shape"); const shacl_1 = require("../ontologies/shacl"); const List_1 = require("./List"); const xsd_1 = require("../ontologies/xsd"); const NodeSet_1 = require("../collections/NodeSet"); const rdf_1 = require("../ontologies/rdf"); const CoreMap_1 = require("../collections/CoreMap"); const ForwardReasoning_1 = require("../utils/ForwardReasoning"); const ShapeClass_1 = require("../utils/ShapeClass"); class SHACL_Shape extends Shape_1.Shape { get type() { return this.getOne(rdf_1.rdf.type); } set type(val) { this.overwrite(rdf_1.rdf.type, val); } _validateNode(node, validated = new CoreMap_1.CoreMap()) { return false; } } exports.SHACL_Shape = SHACL_Shape; SHACL_Shape.targetClass = shacl_1.shacl.Shape; //Note: this shape is linked in Module.ts to avoid cyclical dependencies class NodeShape extends SHACL_Shape { get targetNode() { return this.getOne(shacl_1.shacl.targetNode); } set targetNode(value) { this.overwrite(shacl_1.shacl.targetNode, value); } get targetClass() { return this.getOne(shacl_1.shacl.targetClass); } set targetClass(value) { this.overwrite(shacl_1.shacl.targetClass, value); } addPropertyShape(property) { this.set(shacl_1.shacl.property, property.namedNode); } getPropertyShapes() { return PropertyShape.getSetOf(this.getAll(shacl_1.shacl.property)); } getPropertyShape(label, checkSubShapes = true) { //look at this nodeShape, but also the nodeshapes of the parent classes of the class that created this nodeshape let shapeClass = (0, ShapeClass_1.getShapeClass)(this.namedNode).prototype; let res; while (!res && shapeClass) { res = shapeClass.nodeShape.getPropertyShapes().find((shape) => shape.label === label); shapeClass = checkSubShapes ? Object.getPrototypeOf(shapeClass) : null; } return res; } /** * Returns all the classes and properties that are references by this shape */ getOntologyEntities() { let entities = new NodeSet_1.NodeSet(); if (this.targetClass) { entities.add(this.targetClass); } //add ontology entities of all property shapes this.getPropertyShapes().forEach((propertyShape) => { entities = entities.concat(propertyShape.getOntologyEntities()); }); return entities; } validateNode(node) { return this._validateNode(node); } validateNodeByType(node) { return node.has(rdf_1.rdf.type, this.targetClass); } _validateNode(node, validated = new CoreMap_1.CoreMap()) { if (validated.has(node)) { return validated.get(node); } //whilst validating, if a connected node wants to validate THIS node, we consider this node to be valid until proven otherwise below validated.set(node, true); if (this.targetClass) { //NOTE, we're using Reasoning to check types, so that if this node has a type which is a subClassOf the targetClass, it still matches. //this would not be needed if a Forwards reasoning engine was in place if (!(node instanceof models_1.NamedNode && ForwardReasoning_1.ForwardReasoning.hasType(node, this.targetClass))) { validated.set(node, false); return false; } } const propertyShapes = this.getPropertyShapes(); if (propertyShapes.size > 0) { if (node instanceof models_1.Literal) { validated.set(node, false); return false; } else if (node instanceof models_1.NamedNode) { if (!this.getPropertyShapes().every((propertyShape) => { return propertyShape._validateNode(node, validated); })) { validated.set(node, false); return false; } } } // validated.set(node,true); return true; } static get instances() { if (!this._instances) { this._instances = this.getLocalInstancesByType(); } return this._instances; } static getShapesOf(node, onlyByTargetType = false) { if (onlyByTargetType) { return this.instances.filter((shape) => { return ForwardReasoning_1.ForwardReasoning.hasType(node, shape.targetClass); }); } return this.getLocalInstances().filter((shape) => { return shape.validateNode(node); }); } } exports.NodeShape = NodeShape; NodeShape.targetClass = shacl_1.shacl.NodeShape; //Note: this shape is linked in Module.ts to avoid cyclical dependencies class PropertyShape extends SHACL_Shape { get class() { return this.getOne(shacl_1.shacl.class); } set class(value) { this.overwrite(shacl_1.shacl.class, value); } /** * Returns the NodeShape that all value nodes need to conform to * On a graph level this accessor returns the value of shacl:node for this PropertyShape (if any) * Note: it's named valueShape because node & nodeShape are already used internally in LINCD * @see https://www.w3.org/TR/shacl/#NodeConstraintComponent * */ //@NOTE: If the name valueShape is an issue we could always rename `get nodeShape` to `get shaclShape` in Shape.ts get valueShape() { return this.hasProperty(shacl_1.shacl.node) ? new NodeShape(this.getOne(shacl_1.shacl.node)) : null; } set valueShape(value) { this.overwrite(shacl_1.shacl.node, value.node); } get nodeKind() { return this.getOne(shacl_1.shacl.nodeKind); } set nodeKind(value) { this.overwrite(shacl_1.shacl.nodeKind, value); } get datatype() { return this.getOne(shacl_1.shacl.datatype); } set datatype(value) { this.overwrite(shacl_1.shacl.datatype, value); } get maxCount() { return parseInt(this.getValue(shacl_1.shacl.maxCount)); } set maxCount(value) { this.overwrite(shacl_1.shacl.maxCount, new models_1.Literal(value.toString(), xsd_1.xsd.integer)); } get minCount() { return parseInt(this.getValue(shacl_1.shacl.minCount)); } set minCount(value) { this.overwrite(shacl_1.shacl.minCount, new models_1.Literal(value.toString(), xsd_1.xsd.integer)); } get name() { return this.getValue(shacl_1.shacl.name); } // Setter overloading - would be nice to have one for String and another for Literal: // https://github.com/microsoft/TypeScript/issues/2521 set name(value) { this.overwrite(shacl_1.shacl.name, new models_1.Literal(value)); } get optional() { return this.getValue(shacl_1.shacl.optional); } set optional(value) { this.overwrite(shacl_1.shacl.optional, new models_1.Literal(value, xsd_1.xsd.boolean)); } get path() { return this.getOne(shacl_1.shacl.path); } set path(value) { this.overwrite(shacl_1.shacl.path, value); } get in() { return this.getOne(shacl_1.shacl.in); } set in(value) { this.overwrite(shacl_1.shacl.in, value); } get inList() { return this.hasProperty(shacl_1.shacl.in) ? List_1.List.getOf(this.getOne(shacl_1.shacl.in)) : null; } set inList(value) { this.overwrite(shacl_1.shacl.in, value.node); } get editInline() { return this.getValue(shacl_1.shacl.editInline) === 'true'; } set editInline(val) { this.overwrite(shacl_1.shacl.editInline, new models_1.Literal(val ? 'true' : "false", xsd_1.xsd.boolean)); } get parentNodeShape() { return this.hasInverseProperty(shacl_1.shacl.property) ? new NodeShape(this.getOneInverse(shacl_1.shacl.property)) : null; } /** * Returns all the classes and properties that are references by this shape */ getOntologyEntities() { //start with values of those properties that have a NamedNode as value const entities = new NodeSet_1.NodeSet([this.class, this.path, this.datatype].filter((value) => value && true)); //this caused loops! // if (this.nodeShape) { //if a node shape is defined, also add all the entities of that node shape // entities = entities.concat(this.nodeShape.getOntologyEntities()); // } return entities; } validateNode(node) { return this._validateNode(node); } resolveFor(node) { //TODO: support more complex property paths return node.getAll(this.path); } _validateNode(node, validated = new CoreMap_1.CoreMap()) { //TODO: make property nodes support property paths beyond a single property const property = this.path; const values = node instanceof models_1.NamedNode ? node.getAll(property) : null; if (this.class) { if (!values.every((value) => value instanceof models_1.NamedNode && value.has(rdf_1.rdf.type, this.class))) { return false; } } if (this.datatype) { if (!values.every((value) => value instanceof models_1.Literal && value.datatype === this.datatype)) { return false; } } if (this.valueShape) { //every value should be a valid instance of this nodeShape const nodeShape = this.valueShape; if (!values.every((value) => { //nodes referring to each other or to themselves may cause loops here //this is currently avoided by keeping track of which nodes have already been validated, during the validation of the root most node //TODO: perhaps at some point we may want to store validation results in the shape or even the node, and invalidate whenever the node changes any of its properties. (though for complex property paths that would mean more complex invalidation as well. i.e. back tracing property shapes on a change in node 1 to invalidate a distant node 2) if (validated.has(value)) { return validated.get(value); } return (value === node && this.parentNodeShape.equals(nodeShape)) || nodeShape._validateNode(value, validated); })) { return false; } } if (this.minCount) { if (values.size < this.minCount) { return false; } } if (this.maxCount) { if (values.size > this.maxCount) { return false; } } return true; } } exports.PropertyShape = PropertyShape; PropertyShape.targetClass = shacl_1.shacl.PropertyShape; //# sourceMappingURL=SHACL.js.map