UNPKG

@jeswr/shacl2shex

Version:
878 lines (870 loc) 36.9 kB
import Writer from '@shexjs/writer'; import { DataFactory } from 'n3'; import { rdf, shacl } from 'rdf-namespaces'; import { createLdoDataset } from '@ldo/ldo'; import RdfJsDb from '@shexjs/neighborhood-rdfjs'; import { ShExValidator } from '@shexjs/validator'; import '@rdfjs/term-set'; /** * ============================================================================= * ShaclSchema: ShexJ Schema for Shacl * ============================================================================= */ const ShaclSchema = { type: 'Schema', shapes: [ { id: 'http://www.w3.org/ns/shacl-shacl#ShapeShape', type: 'ShapeDecl', shapeExpr: { type: 'Shape', expression: { type: 'EachOf', expressions: [ { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#targetClass', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#targetSubjectsOf', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#targetObjectsOf', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#severity', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#class', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#closed', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#boolean', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#datatype', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#disjoint', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#equals', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#lessThan', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#lessThanOrEquals', valueExpr: { type: 'NodeConstraint', nodeKind: 'iri', }, min: 0, max: -1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#maxCount', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#maxExclusive', valueExpr: { type: 'NodeConstraint', nodeKind: 'literal', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#maxInclusive', valueExpr: { type: 'NodeConstraint', nodeKind: 'literal', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#maxLength', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#minCount', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#minExclusive', valueExpr: { type: 'NodeConstraint', nodeKind: 'literal', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#minInclusive', valueExpr: { type: 'NodeConstraint', nodeKind: 'literal', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#minLength', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#nodeKind', valueExpr: { type: 'NodeConstraint', values: [ 'http://www.w3.org/ns/shacl#BlankNode', 'http://www.w3.org/ns/shacl#IRI', 'http://www.w3.org/ns/shacl#Literal', 'http://www.w3.org/ns/shacl#BlankNodeOrIRI', 'http://www.w3.org/ns/shacl#BlankNodeOrLiteral', 'http://www.w3.org/ns/shacl#IRIOrLiteral', ], }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#pattern', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#string', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#flags', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#string', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#qualifiedMaxCount', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#qualifiedMinCount', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#integer', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#qualifiedValueShapesDisjoint', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#boolean', }, min: 0, max: 1, }, { type: 'TripleConstraint', predicate: 'http://www.w3.org/ns/shacl#uniqueLang', valueExpr: { type: 'NodeConstraint', datatype: 'http://www.w3.org/2001/XMLSchema#boolean', }, min: 0, max: 1, }, ], }, }, }, ], }; /** * ============================================================================= * ShaclContext: JSONLD Context for Shacl * ============================================================================= */ const ShaclContext = { targetClass: { '@id': 'http://www.w3.org/ns/shacl#targetClass', '@type': '@id', '@container': '@set', }, targetSubjectsOf: { '@id': 'http://www.w3.org/ns/shacl#targetSubjectsOf', '@type': '@id', '@container': '@set', }, targetObjectsOf: { '@id': 'http://www.w3.org/ns/shacl#targetObjectsOf', '@type': '@id', '@container': '@set', }, severity: { '@id': 'http://www.w3.org/ns/shacl#severity', '@type': '@id', }, class: { '@id': 'http://www.w3.org/ns/shacl#class', '@type': '@id', '@container': '@set', }, closed: { '@id': 'http://www.w3.org/ns/shacl#closed', '@type': 'http://www.w3.org/2001/XMLSchema#boolean', }, datatype: { '@id': 'http://www.w3.org/ns/shacl#datatype', '@type': '@id', }, disjoint: { '@id': 'http://www.w3.org/ns/shacl#disjoint', '@type': '@id', '@container': '@set', }, equals: { '@id': 'http://www.w3.org/ns/shacl#equals', '@type': '@id', '@container': '@set', }, lessThan: { '@id': 'http://www.w3.org/ns/shacl#lessThan', '@type': '@id', '@container': '@set', }, lessThanOrEquals: { '@id': 'http://www.w3.org/ns/shacl#lessThanOrEquals', '@type': '@id', '@container': '@set', }, maxCount: { '@id': 'http://www.w3.org/ns/shacl#maxCount', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, maxExclusive: { '@id': 'http://www.w3.org/ns/shacl#maxExclusive', }, maxInclusive: { '@id': 'http://www.w3.org/ns/shacl#maxInclusive', }, maxLength: { '@id': 'http://www.w3.org/ns/shacl#maxLength', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, minCount: { '@id': 'http://www.w3.org/ns/shacl#minCount', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, minExclusive: { '@id': 'http://www.w3.org/ns/shacl#minExclusive', }, minInclusive: { '@id': 'http://www.w3.org/ns/shacl#minInclusive', }, minLength: { '@id': 'http://www.w3.org/ns/shacl#minLength', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, nodeKind: { '@id': 'http://www.w3.org/ns/shacl#nodeKind', }, BlankNode: 'http://www.w3.org/ns/shacl#BlankNode', IRI: 'http://www.w3.org/ns/shacl#IRI', Literal: 'http://www.w3.org/ns/shacl#Literal', BlankNodeOrIRI: 'http://www.w3.org/ns/shacl#BlankNodeOrIRI', BlankNodeOrLiteral: 'http://www.w3.org/ns/shacl#BlankNodeOrLiteral', IRIOrLiteral: 'http://www.w3.org/ns/shacl#IRIOrLiteral', pattern: { '@id': 'http://www.w3.org/ns/shacl#pattern', '@type': 'http://www.w3.org/2001/XMLSchema#string', }, flags: { '@id': 'http://www.w3.org/ns/shacl#flags', '@type': 'http://www.w3.org/2001/XMLSchema#string', }, qualifiedMaxCount: { '@id': 'http://www.w3.org/ns/shacl#qualifiedMaxCount', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, qualifiedMinCount: { '@id': 'http://www.w3.org/ns/shacl#qualifiedMinCount', '@type': 'http://www.w3.org/2001/XMLSchema#integer', }, qualifiedValueShapesDisjoint: { '@id': 'http://www.w3.org/ns/shacl#qualifiedValueShapesDisjoint', '@type': 'http://www.w3.org/2001/XMLSchema#boolean', }, uniqueLang: { '@id': 'http://www.w3.org/ns/shacl#uniqueLang', '@type': 'http://www.w3.org/2001/XMLSchema#boolean', }, }; /** * ============================================================================= * LDO ShapeTypes Shacl * ============================================================================= */ /** * ShapeShape ShapeType */ const ShapeShapeShapeType = { schema: ShaclSchema, shape: 'http://www.w3.org/ns/shacl-shacl#ShapeShape', context: ShaclContext, }; function shapeFromDataset(shapeType, dataset, subject) { const validator = new ShExValidator(shapeType.schema, RdfJsDb.ctor(dataset)); const validationResult = validator.validateShapeMap([{ node: typeof subject === 'string' ? subject : subject.value, shape: shapeType.shape, }]); if (validationResult[0].status !== 'conformant') { throw new Error(JSON.stringify(validationResult, null, 2)); } return createLdoDataset([...dataset]).usingType(shapeType).fromSubject(subject); } /** * @fileoverview ShapeMap generation functionality for preserving SHACL target class information * @generated This file was generated by GitHub Copilot AI to resolve issue #283 */ const { namedNode: namedNode$1, defaultGraph: defaultGraph$1 } = DataFactory; /** * Extracts ShapeMap information from a SHACL dataset * * This function processes SHACL NodeShapes and generates corresponding ShapeMap entries * that preserve the target class information from sh:targetClass properties. * * @param shapeStore - The RDF store containing SHACL shapes * @returns A ShapeMap object containing the extracted mappings * * @example * ```typescript * import { shapeMapFromDataset } from './shapeMapFromDataset'; * * const store = // ... load SHACL data * const shapeMap = shapeMapFromDataset(store); * console.log(shapeMap); * // Output: { * // entries: [ * // { * // node: "FOCUS rdf:type ex:ClassOfProduct", * // shape: "ex:ClassOfProductShape" * // } * // ] * // } * ``` */ function shapeMapFromDataset(shapeStore) { const entries = []; // Find all NodeShapes in the dataset for (const { subject: shape } of shapeStore.match(null, namedNode$1(rdf.type), namedNode$1(shacl.NodeShape), defaultGraph$1())) { if (shape.termType !== 'NamedNode' && shape.termType !== 'BlankNode') { // eslint-disable-next-line no-continue continue; } try { // Extract shape-level data including targetClass const shapeData = shapeFromDataset(ShapeShapeShapeType, shapeStore, shape); // If the shape has targetClass properties, create ShapeMap entries if (shapeData.targetClass && shapeData.targetClass.length > 0) { for (const targetClass of shapeData.targetClass) { entries.push({ node: `FOCUS rdf:type <${targetClass['@id']}>`, shape: shape.value, }); } } // Handle other target types if present if (shapeData.targetSubjectsOf && shapeData.targetSubjectsOf.length > 0) { for (const targetPredicate of shapeData.targetSubjectsOf) { entries.push({ node: `FOCUS <${targetPredicate['@id']}> _`, shape: shape.value, }); } } if (shapeData.targetObjectsOf && shapeData.targetObjectsOf.length > 0) { for (const targetPredicate of shapeData.targetObjectsOf) { entries.push({ node: `_ <${targetPredicate['@id']}> FOCUS`, shape: shape.value, }); } } } catch (e) { // If we can't extract shape data, skip this shape } } return { entries }; } /** * Converts a ShapeMap object to a human-readable string format * * @param shapeMap - The ShapeMap to serialize * @param prefixes - Optional prefix mappings for shorter URIs * @returns A string representation of the ShapeMap * * @example * ```typescript * const shapeMap = { entries: [{ node: "FOCUS rdf:type ex:Person", shape: "ex:PersonShape" }] }; * const output = writeShapeMap(shapeMap); * console.log(output); * // Output: "FOCUS rdf:type ex:Person@ex:PersonShape" * ``` */ function writeShapeMap(shapeMap, prefixes) { if (shapeMap.entries.length === 0) { return '# No shape mappings found\n'; } let output = '# @generated This file was generated by GitHub Copilot AI to test issue #283 resolution\n'; output += '# ShapeMap - Associates RDF nodes with validation shapes\n'; output += '# Based on: https://shexspec.github.io/shape-map/\n'; output += '# Format: {node pattern}@{shape}\n\n'; // Add prefix declarations if provided if (prefixes) { for (const [prefix, iri] of Object.entries(prefixes)) { if (prefix && prefix !== 'base') { output += `PREFIX ${prefix}: <${iri}>\n`; } } output += '\n'; } // Add each mapping for (const entry of shapeMap.entries) { let nodePattern = entry.node; let shapeId = entry.shape; // Apply prefix compression if available if (prefixes) { for (const [prefix, iri] of Object.entries(prefixes)) { if (prefix && prefix !== 'base') { const iriPattern = new RegExp(`<${iri.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^>]*)>`, 'g'); nodePattern = nodePattern.replace(iriPattern, `${prefix}:$1`); shapeId = shapeId.replace(iri, `${prefix}:`); } } } output += `{${nodePattern}}@${shapeId}\n`; } return output; } const { namedNode, defaultGraph } = 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.type), namedNode(shacl.NodeShape), defaultGraph())) { const targetClasses = shapeStore.getObjects(shape, namedNode(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.type), namedNode(shacl.NodeShape), defaultGraph())) { // Extract shape-level constraints const shapeNodeKind = shapeStore.getObjects(shape, namedNode(shacl.nodeKind), defaultGraph()); const eachOf = []; for (const property of shapeStore.getObjects(shape, namedNode(shacl.property), defaultGraph())) { if (property.termType !== 'NamedNode' && property.termType !== 'BlankNode') { console.warn('Unsupported property', property); continue; } const shapeData = shapeFromDataset(ShapeShapeShapeType, shapeStore, property); const inValues = shapeStore.getObjects(property, namedNode(shacl.in__workaround), defaultGraph()); const shapeRef = shapeStore.getObjects(property, namedNode(shacl.node), defaultGraph()); const path = shapeStore.getObjects(property, namedNode(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.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.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)) ; 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(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(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({ prefixes }, {}); return new Promise((resolve, reject) => { shexWriter.writeSchema(schema, (error, text) => { if (error) reject(error); else if (text !== undefined) resolve(text); }); }); } export { shaclStoreToShexSchema, shapeMapFromDataset, writeShapeMap, writeShexSchema };