UNPKG

rdf-validate-shacl

Version:
134 lines (133 loc) 5.22 kB
import NodeSet from './node-set.js'; /** * Extracts all the nodes of a property path from a graph and returns a * property path object. * * @param pathNode - Pointer to the start node of the path * @param ns - Namespaces * @param allowNamedNodeInList - Allow named node in lists. By default, only blank nodes are allowed * @return Property path object */ export function extractPropertyPath(pathNode, ns, allowNamedNodeInList) { if (pathNode.term.termType === 'NamedNode' && !allowNamedNodeInList) { return pathNode.term; } if (pathNode.term.termType === 'BlankNode' || pathNode.term.termType === 'NamedNode') { const first = pathNode.out(ns.rdf.first).term; if (first) { const paths = [...pathNode.list()]; return paths.map(path => extractPropertyPath(path, ns, allowNamedNodeInList)); } const alternativePath = pathNode.out(ns.sh.alternativePath); if (alternativePath.term) { const paths = [...alternativePath.list()]; return { or: paths.map(path => extractPropertyPath(path, ns, allowNamedNodeInList)) }; } const zeroOrMorePath = pathNode.out(ns.sh.zeroOrMorePath); if (zeroOrMorePath.term) { return { zeroOrMore: extractPropertyPath(zeroOrMorePath, ns, allowNamedNodeInList) }; } const oneOrMorePath = pathNode.out(ns.sh.oneOrMorePath); if (oneOrMorePath.term) { return { oneOrMore: extractPropertyPath(oneOrMorePath, ns, allowNamedNodeInList) }; } const zeroOrOnePath = pathNode.out(ns.sh.zeroOrOnePath); if (zeroOrOnePath.term) { return { zeroOrOne: extractPropertyPath(zeroOrOnePath, ns, allowNamedNodeInList) }; } const inversePath = pathNode.out(ns.sh.inversePath); if (inversePath.term) { return { inverse: extractPropertyPath(inversePath, ns, allowNamedNodeInList) }; } return pathNode.term; } throw new Error(`Unsupported SHACL path: ${pathNode.term.value}`); } /** * Follows a property path in a graph, starting from a given node, and returns * all the nodes it points to. * * @param graph * @param subject - Start node * @param path - Property path object * @return - Nodes that are reachable through the property path */ export function getPathObjects(graph, subject, path) { return [...getPathObjectsSet(graph, subject, path)]; } function getPathObjectsSet(graph, subject, path) { if ('termType' in path && path.termType === 'NamedNode') { return getNamedNodePathObjects(graph, subject, path); } else if (Array.isArray(path)) { return getSequencePathObjects(graph, subject, path); } else if ('or' in path) { return getOrPathObjects(graph, subject, path); } else if ('inverse' in path) { return getInversePathObjects(graph, subject, path); } else if ('zeroOrOne' in path) { return getZeroOrOnePathObjects(graph, subject, path); } else if ('zeroOrMore' in path) { return getZeroOrMorePathObjects(graph, subject, path); } else if ('oneOrMore' in path) { return getOneOrMorePathObjects(graph, subject, path); } else { throw new Error(`Unsupported path object: ${path}`); } } function getNamedNodePathObjects(graph, subject, path) { return new NodeSet(graph.node(subject).out(path).terms); } function getSequencePathObjects(graph, subject, path) { // TODO: This one is really unreadable let subjects = new NodeSet([subject]); for (const pathItem of path) { subjects = new NodeSet(flatMap(subjects, subjectItem => getPathObjects(graph, subjectItem, pathItem))); } return subjects; } function getOrPathObjects(graph, subject, path) { return new NodeSet(flatMap(path.or, pathItem => getPathObjects(graph, subject, pathItem))); } function getInversePathObjects(graph, subject, path) { if (!('termType' in path.inverse) || path.inverse.termType !== 'NamedNode') { throw new Error('Unsupported: Inverse paths only work for named nodes'); } return new NodeSet(graph.node(subject).in(path.inverse).terms); } function getZeroOrOnePathObjects(graph, subject, path) { const pathObjects = getPathObjectsSet(graph, subject, path.zeroOrOne); pathObjects.add(subject); return pathObjects; } function getZeroOrMorePathObjects(graph, subject, path) { const pathObjects = walkPath(graph, subject, path.zeroOrMore); pathObjects.add(subject); return pathObjects; } function getOneOrMorePathObjects(graph, subject, path) { return walkPath(graph, subject, path.oneOrMore); } function walkPath(graph, subject, path, visited = new NodeSet()) { visited.add(subject); const pathValues = getPathObjectsSet(graph, subject, path); const deeperValues = flatMap(pathValues, pathValue => { if (!visited.has(pathValue)) { return [...walkPath(graph, pathValue, path, visited)]; } else { return []; } }); pathValues.addAll(deeperValues); return pathValues; } function flatMap(arr, func) { return [...arr].reduce((acc, x) => acc.concat(func(x)), []); }