@jeswr/shacl2shex
Version:
Convert SHACL to ShEx
878 lines (870 loc) • 36.9 kB
JavaScript
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 };