UNPKG

lincd

Version:

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

251 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addNodeShapeToShapeClass = addNodeShapeToShapeClass; exports.getShapeClass = getShapeClass; exports.getSubShapesClasses = getSubShapesClasses; exports.getSuperShapesClasses = getSuperShapesClasses; exports.hasSuperClass = hasSuperClass; exports.hasSubClass = hasSubClass; exports.getMostSpecificSubShapes = getMostSpecificSubShapes; exports.getShapeOrSubShape = getShapeOrSubShape; exports.getMostSpecificShapes = getMostSpecificShapes; exports.getMostSpecificShapesByType = getMostSpecificShapesByType; const Shape_1 = require("../shapes/Shape"); const rdf_1 = require("../ontologies/rdf"); let subShapesSpecificityCache = new Map(); let subShapesCache = new Map(); let mostSpecificSubShapesCache = new Map(); let nodeShapeToShapeClass = new Map(); let shouldResetCache = false; function addNodeShapeToShapeClass(nodeShape, shapeClass) { nodeShapeToShapeClass.set(nodeShape.namedNode, shapeClass); //make sure that the cache is reset after the next event loop if (!shouldResetCache) { shouldResetCache = true; setTimeout(() => { subShapesSpecificityCache.clear(); subShapesCache.clear(); mostSpecificSubShapesCache.clear(); shouldResetCache = false; }, 0); } } function getShapeClass(nodeShape) { return nodeShapeToShapeClass.get(nodeShape); } function getSubShapesClasses(shape, _internalKey) { let key = _internalKey || getKey(shape); if (!subShapesCache.has(key)) { //make sure we have a real class shape = ensureShapeConstructor(shape); //apply the hasSuperclass function to the shape let filterFunction = applyFnToShapeOrArray(shape, hasSubClass); //filter and then sort the results based on their inheritance (most specific classes first, so we use hasSuperClass for the sorting) subShapesCache.set(key, filterShapeClasses(filterFunction).sort((a, b) => { return hasSubClass(a, b) ? 1 : -1; })); } //return a copy of the array to prevent it from being modified return [...subShapesCache.get(key)]; // let extendsGivenShapeClass = Array.isArray(shape) ? (shapeClass) => { // return shape.some(s => shapeClass.constructor.prototype instanceof s); // } : (shapeClass) => { // return shapeClass.constructor.prototype instanceof shape; // } // // let result = []; // nodeShapeToShapeClass.forEach((shapeClass) => { // if(extendsGivenShapeClass(shapeClass)) { // result.push(shapeClass); // } // }); // return result; } function getSuperShapesClasses(shape) { //make sure we have a real class shape = ensureShapeConstructor(shape); //apply the hasSuperclass function to the shape let filterFunction = applyFnToShapeOrArray(shape, hasSuperClass); //filter and then sort the results based on their inheritance return filterShapeClasses(filterFunction).sort((a, b) => { return hasSubClass(a, b) ? 1 : -1; }); } //https://stackoverflow.com/a/30760236 function isClass(v) { return typeof v === 'function' && /^\s*class\s+/.test(v.toString()); } function ensureShapeConstructor(shape) { //TODO: figure out why sometimes we need shape.prototype, sometimes we need shape.constructor.prototype // in other words, why we sometimes get a ES6 Class and sometimes its constructor? //make sure we have a real class //NOTE: update, this started breaking for when classes are functions. the constructor is native Function //had to turn it off for now, waiting for issues to come back up to understand what needs to happen return shape; // if(Array.isArray(shape)) // { // return shape.map(s => { // if (!isClass(s)) // { // return s.constructor as any; // } // return s; // }) as any[]; // } else { // if (!isClass(shape)) // { // return shape.constructor as any; // } // return shape; // } } function hasSuperClass(a, b) { return a.prototype instanceof b; } function hasSubClass(a, b) { return b.prototype instanceof a; } function applyFnToShapeOrArray(shape, filterFn) { if (Array.isArray(shape)) { return (shapeClass) => { //returns true if one of the given shapes extends the shapeClass passed as argument return shape.some((s) => filterFn(s, shapeClass)); }; } else { //first argument will be the given shape class, second argument will be each stored shape class in the map //will filter down where the given shape extends the stored shape return filterFn.bind(null, shape); } } function filterShapeClasses(filterFn) { let result = []; nodeShapeToShapeClass.forEach((shapeClass) => { if (filterFn(shapeClass)) { result.push(shapeClass); } }); return result; } function getMostSpecificSubShapes(shape) { if (!Array.isArray(shape)) { shape = [shape]; } let key = shape.map((s) => s.name).join(','); if (!mostSpecificSubShapesCache.has(key)) { //get the subshapes of the given shapes let subShapes = getSubShapesClasses(shape, key); //filter them down to the most specific ones (that are not extended by any other shape) mostSpecificSubShapesCache.set(key, filterShapesToMostSpecific(subShapes)); } return mostSpecificSubShapesCache.get(key); } function filterShapesToMostSpecific(subShapes) { return subShapes.filter((subShape) => { return !subShapes.some((otherSubShape) => { return otherSubShape.prototype instanceof subShape; }); }); } /** * Finds the most specific shape class (which extends other shape classes) * of all shape classes that this node matches with (that is the node is a valid instance of the shape) * And returns an instance of that shape * @param property * @param shape */ function getShapeOrSubShape(node, shape) { if (!node) return null; //new: //find all shapes that extend the given shape(s) let mostSpecificShapes = getMostSpecificShapes(node, shape); //take the first one and return a new instance of that shape if (mostSpecificShapes.length > 0) { return new mostSpecificShapes[0](node); } //by default, if no more specific shapes were found, just create an instance of the (first) given shape if (Array.isArray(shape)) { return new shape[0](node); } return new shape(node); // //start with the shape itself, but add any extending shapes // let extendingShapes:typeof Shape[] = []; // // //if shape is an array, we check if the node is an instance of any of the shapes in the array // //NOTE: I'm not exactly sure why we have to add .constructor, but the shapeClasses coming in are // //apparently not of the same kind (class) as the shapeClasses in the nodeShapeToShapeClass map // //so, we have to compare by its constructors prototype, that seems to work // let classExtendsGivenShapeClass = Array.isArray(shape) ? (shapeClass) => { // return shape.some(s => shapeClass.constructor.prototype instanceof s); // } : (shapeClass) => { // return shapeClass.constructor.prototype instanceof shape; // } // // let shapesOfNode = NodeShape.getShapesOf(node); // shapesOfNode.forEach(nodeShape => { // let shapeClass = getShapeClass(nodeShape.namedNode); // if(classExtendsGivenShapeClass(shapeClass.prototype)) { // extendingShapes.push(shapeClass); // } // }); // // extendingShapes.sort((s1,s2) => { // return s1.prototype instanceof s2 ? -1 : 1; // }); // if(extendingShapes.length > 0) { // return new (extendingShapes[0] as any)(node) as S; // } // // return new (shape as any)(node) as S; } function getMostSpecificShapes(node, baseShape = Shape_1.Shape) { return _getMostSpecificShapes(baseShape, (subShape) => subShape.shape.validateNode(node)); } function getMostSpecificShapesByType(node, baseShape = Shape_1.Shape) { return _getMostSpecificShapes(baseShape, (subShape) => node.has(rdf_1.rdf.type, subShape.targetClass)); } function getKey(shape) { return Array.isArray(shape) ? shape.map((s) => getShapeKey(s)).join(',') : getShapeKey(shape); } function getShapeKey(shape) { var _a; //return a unique string for each shape return ((_a = shape.targetClass) === null || _a === void 0 ? void 0 : _a.uri) || shape.name + shape.prototype.constructor.toString().substring(0, 80); } function getSubShapesClassesSortedBySpecificity(baseShape = Shape_1.Shape) { let key = getKey(baseShape); if (!subShapesSpecificityCache.has(key)) { let subShapes = getSubShapesClasses(baseShape, key); let specificityGroups = []; while (subShapes.length > 0) { let mostSpecificSubShapes = filterShapesToMostSpecific(subShapes); specificityGroups.push(mostSpecificSubShapes); mostSpecificSubShapes.forEach((mostSpecificSubShape) => { subShapes.splice(subShapes.indexOf(mostSpecificSubShape), 1); }); } subShapesSpecificityCache.set(key, specificityGroups); } return subShapesSpecificityCache.get(key); } function _getMostSpecificShapes(baseShape = Shape_1.Shape, shapeValidationFn) { //get the subshapes of the given base shape(s) let subShapes = getSubShapesClassesSortedBySpecificity(baseShape); let res; //for each group of most specific subshapes (before going to the next group of less specific subshapes) for (let subShapeGroup of subShapes) { //filter them down to the ones that this node is a valid instance of let shapesThatMatchNode = subShapeGroup.filter(shapeValidationFn); //if any of them can create a valid instance for this node, then return that if (shapesThatMatchNode.length > 0) { res = shapesThatMatchNode; break; } } if (!res) { res = []; } return res; } //# sourceMappingURL=ShapeClass.js.map