UNPKG

@jeswr/shacl2shex

Version:
323 lines (322 loc) 15.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.writeShapeMap = exports.shapeMapFromDataset = void 0; exports.shaclStoreToShexSchema = shaclStoreToShexSchema; exports.writeShexSchema = writeShexSchema; const writer_1 = __importDefault(require("@shexjs/writer")); const n3_1 = require("n3"); const rdf_namespaces_1 = require("rdf-namespaces"); const Shacl_shapeTypes_1 = require("./ldo/Shacl.shapeTypes"); const shapeFromDataset_1 = require("./shapeFromDataset"); var shapeMapFromDataset_1 = require("./shapeMapFromDataset"); Object.defineProperty(exports, "shapeMapFromDataset", { enumerable: true, get: function () { return shapeMapFromDataset_1.shapeMapFromDataset; } }); Object.defineProperty(exports, "writeShapeMap", { enumerable: true, get: function () { return shapeMapFromDataset_1.writeShapeMap; } }); const { namedNode, defaultGraph } = n3_1.DataFactory; function getSingleObjectOfType(store, subject, predicate) { const objects = [...store.match(subject, predicate, null, defaultGraph())]; if (objects.length !== 1) { console.warn('Expected exactly one object', objects); return undefined; } if (objects[0].object.termType !== 'NamedNode') { console.warn('Expected object to be a NamedNode', objects[0]); return undefined; } return objects[0].object; } async function shaclStoreToShexSchema(shapeStore) { const shexShapes = []; // First pass: collect all shapes and their target classes for reference resolution const shapeTargetMap = new Map(); // target class -> shape IRI for (const { subject: shape } of shapeStore.match(null, namedNode(rdf_namespaces_1.rdf.type), namedNode(rdf_namespaces_1.shacl.NodeShape), defaultGraph())) { const targetClasses = shapeStore.getObjects(shape, namedNode(rdf_namespaces_1.shacl.targetClass), defaultGraph()); for (const targetClass of targetClasses) { if (targetClass.termType === 'NamedNode') { shapeTargetMap.set(targetClass.value, shape.value); } } } for (const { subject: shape } of shapeStore.match(null, namedNode(rdf_namespaces_1.rdf.type), namedNode(rdf_namespaces_1.shacl.NodeShape), defaultGraph())) { // Extract shape-level constraints const shapeNodeKind = shapeStore.getObjects(shape, namedNode(rdf_namespaces_1.shacl.nodeKind), defaultGraph()); const eachOf = []; for (const property of shapeStore.getObjects(shape, namedNode(rdf_namespaces_1.shacl.property), defaultGraph())) { if (property.termType !== 'NamedNode' && property.termType !== 'BlankNode') { console.warn('Unsupported property', property); continue; } const shapeData = (0, shapeFromDataset_1.shapeFromDataset)(Shacl_shapeTypes_1.ShapeShapeShapeType, shapeStore, property); const inValues = shapeStore.getObjects(property, namedNode(rdf_namespaces_1.shacl.in__workaround), defaultGraph()); const shapeRef = shapeStore.getObjects(property, namedNode(rdf_namespaces_1.shacl.node), defaultGraph()); const path = shapeStore.getObjects(property, namedNode(rdf_namespaces_1.shacl.path), defaultGraph()); let valueExpr = { type: 'NodeConstraint', }; if (shapeData.nodeKind) { valueExpr.nodeKind = { IRI: 'iri', Literal: 'literal', BlankNode: 'bnode', BlankNodeOrIRI: 'nonliteral', IRIOrLiteral: undefined, BlankNodeOrLiteral: undefined, }[shapeData.nodeKind['@id']]; } if (inValues.length === 1) { const list = shapeStore.extractLists()[inValues[0].value]; if (list) { const [firstTerm] = list; // TODO: Make this just the else case once https://github.com/o-development/ldo/issues/31 is resolved if (firstTerm && firstTerm.termType === 'Literal' && list.every((v) => v.termType === 'Literal' && v.datatype.equals(firstTerm.datatype))) { valueExpr.datatype = firstTerm.datatype.value; } else { valueExpr.values = list.map((v) => (v.termType === 'Literal' ? { value: v.value, type: v.datatype.value, } : v.value)); } } } if (shapeData.datatype) { valueExpr.datatype = shapeData.datatype['@id']; } else if (shapeData.class && shapeData.class.length > 0) { // Check if there's a shape that targets this class const classIri = shapeData.class[0]['@id']; const targetShape = shapeTargetMap.get(classIri); if (targetShape && shapeData.class.length === 1) { // Use shape reference if there's a shape targeting this class if (valueExpr.nodeKind) { // Combine nodeKind constraint with shape reference valueExpr = { type: 'ShapeAnd', shapeExprs: [ { type: 'NodeConstraint', nodeKind: valueExpr.nodeKind, }, targetShape, ], }; } else { // Just the shape reference valueExpr = targetShape; } } else // Handle sh:class constraint by creating a nested shape with rdf:type // If there's also a nodeKind constraint, we need to combine them if (valueExpr.nodeKind) { // Create a ShapeAnd that combines the nodeKind constraint with the class shape valueExpr = { type: 'ShapeAnd', shapeExprs: [ { type: 'NodeConstraint', nodeKind: valueExpr.nodeKind, }, { type: 'Shape', expression: { type: 'TripleConstraint', predicate: rdf_namespaces_1.rdf.type, valueExpr: { type: 'NodeConstraint', values: shapeData.class.map((cls) => cls['@id']), }, }, }, ], }; } else { // Just the class constraint without nodeKind valueExpr = { type: 'Shape', expression: { type: 'TripleConstraint', predicate: rdf_namespaces_1.rdf.type, valueExpr: { type: 'NodeConstraint', values: shapeData.class.map((cls) => cls['@id']), }, }, }; } } else if (shapeRef.length === 1) { // TODO: Error if there are any other constraints valueExpr = shapeRef[0].value; } else if ((typeof valueExpr === 'object' && 'nodeKind' in valueExpr && valueExpr.nodeKind) || (typeof valueExpr === 'object' && 'values' in valueExpr && valueExpr.values)) { // Noop - keep the existing NodeConstraint } else { console.warn('Unsupported property', property); continue; } function toTripleConstraint(pathElem) { if (pathElem.termType === 'NamedNode') { return { type: 'TripleConstraint', predicate: pathElem.value, valueExpr, // FIXME: Int checks etc should be done here min: shapeData.minCount ?? 0, max: shapeData.maxCount ?? -1, }; } const inversePath = getSingleObjectOfType(shapeStore, pathElem, namedNode(rdf_namespaces_1.shacl.inversePath)); if (inversePath) { return { type: 'TripleConstraint', predicate: pathElem.value, valueExpr, inverse: true, // FIXME: Int checks etc should be done here min: shapeData.minCount ?? 0, max: shapeData.maxCount ?? -1, }; } const oneOrMorePath = getSingleObjectOfType(shapeStore, pathElem, namedNode(rdf_namespaces_1.shacl.oneOrMorePath)); if (oneOrMorePath) { return { type: 'TripleConstraint', predicate: pathElem.value, valueExpr: { type: 'ShapeOr', shapeExprs: [{ type: 'Shape', expression: toTripleConstraint(oneOrMorePath), }, valueExpr], }, min: shapeData.minCount ?? 0, max: shapeData.maxCount ?? -1, }; } console.log(shape, getSingleObjectOfType(shapeStore, pathElem, namedNode('http://www.w3.org/ns/shacl#alternativePath')), getSingleObjectOfType(shapeStore, pathElem, namedNode('http://www.w3.org/ns/shacl#zeroOrMorePath')), getSingleObjectOfType(shapeStore, pathElem, namedNode('http://www.w3.org/ns/shacl#oneOrMorePath')), getSingleObjectOfType(shapeStore, pathElem, namedNode('http://www.w3.org/ns/shacl#zeroOrOnePath')), getSingleObjectOfType(shapeStore, pathElem, namedNode('http://www.w3.org/ns/shacl#inversePath')), shapeStore.extractLists()[pathElem.value]); throw new Error('Unsupported path'); } try { eachOf.push(toTripleConstraint(path[0])); } catch (e) { console.warn('Error processing property', property, e); } } // Handle shape-level nodeKind constraint let shapeNodeKindConstraint; if (shapeNodeKind.length === 1 && shapeNodeKind[0].termType === 'NamedNode') { const nodeKindValue = shapeNodeKind[0].value; const nodeKindMapping = { 'http://www.w3.org/ns/shacl#IRI': 'iri', 'http://www.w3.org/ns/shacl#Literal': 'literal', 'http://www.w3.org/ns/shacl#BlankNode': 'bnode', 'http://www.w3.org/ns/shacl#BlankNodeOrIRI': 'nonliteral', }; if (nodeKindValue in nodeKindMapping) { shapeNodeKindConstraint = nodeKindMapping[nodeKindValue]; } } // Create the shape expression let shapeExpr; if (eachOf.length === 0) { // If there are no properties but there is a nodeKind constraint, create a simple nodeKind constraint if (shapeNodeKindConstraint) { shapeExpr = { type: 'NodeConstraint', nodeKind: shapeNodeKindConstraint, }; } else { console.warn('No properties found in shape', shape); continue; } } else { // If there are properties, create the shape with properties const shapeWithProperties = { type: 'Shape', expression: { type: 'EachOf', expressions: eachOf, }, }; // If there's a nodeKind constraint, wrap with ShapeAnd if (shapeNodeKindConstraint) { shapeExpr = { type: 'ShapeAnd', shapeExprs: [ { type: 'NodeConstraint', nodeKind: shapeNodeKindConstraint, }, shapeWithProperties, ], }; } else { shapeExpr = shapeWithProperties; } } shexShapes.push({ id: shape.value, type: 'ShapeDecl', shapeExpr, }); } const filteredShapes = []; const shapes = new Set(shexShapes.map((s) => s.id)); // TODO: Apply this recursively // TODO: Add warnings for (const shape of shexShapes) { let shouldInclude = true; // Handle different shape types if (shape.shapeExpr.type === 'NodeConstraint') { // NodeConstraint shapes are always included shouldInclude = true; } else if (shape.shapeExpr.type === 'ShapeAnd') { // ShapeAnd shapes need to check their component shapes // For now, we'll include them all, but this could be refined shouldInclude = true; } else if (shape.shapeExpr.type === 'Shape' && shape.shapeExpr?.expression && typeof shape.shapeExpr?.expression === 'object' && shape.shapeExpr?.expression.type === 'EachOf' && shape.shapeExpr?.expression.expressions) { // Original logic for Shape with EachOf expressions shape.shapeExpr.expression.expressions = shape.shapeExpr.expression.expressions.filter((eachOf) => typeof eachOf !== 'string' && eachOf.type === 'TripleConstraint' && ((typeof eachOf.valueExpr !== 'string') || shapes.has(eachOf.valueExpr))); shouldInclude = shape.shapeExpr.expression.expressions.length > 0; } if (shouldInclude) { filteredShapes.push(shape); } } return { type: 'Schema', shapes: filteredShapes, }; } function writeShexSchema(schema, prefixes) { const shexWriter = new writer_1.default({ prefixes }, {}); return new Promise((resolve, reject) => { shexWriter.writeSchema(schema, (error, text) => { if (error) reject(error); else if (text !== undefined) resolve(text); }); }); }