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
JavaScript
;
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