diff --git a/packages/schema-converter-shex/src/schema/ShexJSchemaTransformerCompact.ts b/packages/schema-converter-shex/src/schema/ShexJSchemaTransformerCompact.ts index 20621a5..f1c5a8b 100644 --- a/packages/schema-converter-shex/src/schema/ShexJSchemaTransformerCompact.ts +++ b/packages/schema-converter-shex/src/schema/ShexJSchemaTransformerCompact.ts @@ -2,23 +2,34 @@ import type { ObjectLiteral } from "@ldo/traverser-shexj"; import ShexJTraverser from "@ldo/traverser-shexj"; export interface CompactShape { - schemaUri: string; + iri: string; predicates: CompactSchemaProperty[]; } -type NodeConstraintRet = { +type CompactSchemaValue = { literals?: number[] | string[] | boolean; type: "number" | "string" | "boolean" | "literal"; }; interface CompactSchemaProperty { - type: "number" | "string" | "boolean" | "nested" | "literal"; + /** Type of property. */ + type: "number" | "string" | "boolean" | "literal" | "nested" | "eitherOf"; + /** The RDF predicate URI. */ predicateUri: string; + /** The alias of the `predicateUri` when serialized to a JSON object. */ readablePredicate: string; + /** The required literal value(s), if type is `literal`. Others are allowed, if `extra` is true. */ literalValue?: number | string | boolean | number[] | string[]; + /** If type is `nested`, the shape or its IRI. */ nestedSchema?: string | CompactShape; + /** Maximum allowed number of values. `-1` means infinite. */ maxCardinality: number; + /** Minimum required number of values */ minCardinality: number; + /** If type is `eitherOf`, specifies multiple allowed types (CompactSchemaValue, shapes, or shape IRI). */ + eitherOf?: (CompactSchemaValue | CompactShape | string)[]; + /** If other (additional) values are permitted. Useful for literals. */ + extra?: boolean; } export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< @@ -28,15 +39,14 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< Shape: { return: CompactShape }; EachOf: { return: CompactShape }; TripleConstraint: { return: CompactSchemaProperty }; - NodeConstraint: { return: NodeConstraintRet }; - ShapeOr: { return: never }; + NodeConstraint: { return: CompactSchemaValue }; + ShapeOr: { return: (CompactSchemaValue | CompactShape | string)[] }; ShapeAnd: { return: never }; ShapeNot: { return: never }; ShapeExternal: { return: never }; }, null >({ - // Transformer from Schema to interfaces Schema: { transformer: async (_schema, getTransformedChildren) => { const transformedChildren = await getTransformedChildren(); @@ -45,35 +55,41 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< }, }, - // Transformer from ShapeDecl to interface ShapeDecl: { transformer: async (shapeDecl, getTransformedChildren) => { const schema = await getTransformedChildren(); + const shape = schema.shapeExpr as CompactShape; - return { ...schema.shapeExpr, schemaUri: shapeDecl.id } as CompactShape; + return { ...shape, iri: shapeDecl.id } as CompactShape; }, }, - // Transformer from Shape to interface Shape: { - transformer: async (_shape, getTransformedChildren, setReturnPointer) => { + transformer: async (_shape, getTransformedChildren) => { // TODO: We don't handles those _shape.closed; - _shape.extra; const transformedChildren = await getTransformedChildren(); - // Return prelim or expression or assign things? - return transformedChildren.expression as CompactShape; + const compactShape = transformedChildren.expression as CompactShape; + + for (const extra of _shape.extra || []) { + const extraPredicate = compactShape.predicates.find( + (p) => p.predicateUri === extra, + ); + if (extraPredicate) extraPredicate.extra = true; + } + + return compactShape; }, }, - // Transformer from EachOf to object type. EachOf contains the `expressions` array of properties (TripleConstraint) + // EachOf contains the `expressions` array of properties (TripleConstraint) EachOf: { - transformer: async (eachOf, getTransformedChildren, setReturnPointer) => { + transformer: async (eachOf, getTransformedChildren) => { const transformedChildren = await getTransformedChildren(); return { - schemaUri: "", + iri: "", predicates: transformedChildren.expressions.map( // We disregard cases where properties are referenced (strings) // or where they consist of Unions or Intersections (not supported). @@ -83,7 +99,6 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< }, }, - // Transformer from triple constraints to type properties. TripleConstraint: { transformer: async ( tripleConstraint, @@ -116,10 +131,16 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< nestedSchema: transformedChildren.valueExpr as CompactShape, ...commonProperties, } satisfies CompactSchemaProperty; + } else if (Array.isArray(transformedChildren.valueExpr)) { + return { + type: "eitherOf", + eitherOf: transformedChildren.valueExpr, + ...commonProperties, + }; } else { // type or literal const nodeConstraint = - transformedChildren.valueExpr as NodeConstraintRet; + transformedChildren.valueExpr as CompactSchemaValue; return { type: nodeConstraint.type, literalValue: nodeConstraint.literals, @@ -129,7 +150,6 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< }, }, - // Transformer from node constraint to type NodeConstraint: { transformer: async (nodeConstraint) => { if (nodeConstraint.datatype) { @@ -179,28 +199,34 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer< }, }, - // Transformer from ShapeOr to union type + // Transformer from ShapeOr ShapeOr: { - transformer: async () => { - throw new Error("ShapeOr not supported (compact)"); + transformer: async (shapeOr, getTransformedChildren) => { + const tc = await getTransformedChildren(); + // Either a shape IRI, a nested shape or a node CompactSchemaValue (node constraint). + return (Array.isArray(tc) ? tc : [tc]) as ( + | string + | CompactShape + | CompactSchemaValue + )[]; }, }, - // Transformer from ShapeAnd to intersection type + // Transformer from ShapeAnd ShapeAnd: { transformer: async () => { throw new Error("ShapeAnd not supported (compact)"); }, }, - // Transformer from ShapeNot to type - not supported. + // Transformer from ShapeNot - not supported. ShapeNot: { transformer: async () => { throw new Error("ShapeNot not supported (compact)"); }, }, - // Transformer from ShapeExternal to type - not supported. + // Transformer from ShapeExternal - not supported. ShapeExternal: { transformer: async () => { throw new Error("ShapeExternal not supported (compact)"); diff --git a/packages/schema-converter-shex/src/typing/shexjToTypingCompact.ts b/packages/schema-converter-shex/src/typing/shexjToTypingCompact.ts index d136f21..a3c9b38 100644 --- a/packages/schema-converter-shex/src/typing/shexjToTypingCompact.ts +++ b/packages/schema-converter-shex/src/typing/shexjToTypingCompact.ts @@ -69,7 +69,7 @@ function flattenSchema(shapes: CompactShape[]): CompactSchema { let schema: CompactSchema = {}; for (const shape of shapes) { - schema[shape.schemaUri] = shape; + schema[shape.iri] = shape; // Find nested, unflattened (i.e. anonymous) schemas in properties. const nestedSchemaPredicates = shape.predicates.filter( @@ -77,13 +77,13 @@ function flattenSchema(shapes: CompactShape[]): CompactSchema { ); for (const pred of nestedSchemaPredicates) { - const newId = shape.schemaUri + "::" + pred.predicateUri; + const newId = shape.iri + "||" + pred.predicateUri; // Recurse const flattened = flattenSchema([ { ...(pred.nestedSchema as CompactShape), - schemaUri: newId, + iri: newId, }, ]); // Replace the nested schema with its new id.