Skip to content

Commit

Permalink
Support optional paths, nodeLinks and atLeastOneLists
Browse files Browse the repository at this point in the history
  • Loading branch information
pheyvaer committed May 2, 2024
1 parent b803b8b commit 6f2c3de
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 9 deletions.
96 changes: 87 additions & 9 deletions lib/ShapesGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ const SHACL = createTermNamespace(

export class ShapesGraph {
shapes: RDFMap<ShapeTemplate>;
private counter: number;

constructor(shapeStore: RdfStore) {
this.shapes = this.initializeFromStore(shapeStore);
this.counter = 0;
}

public toMermaid(term: Term): string {
const startShape = this.shapes.get(term);
this.counter = 0;

if (!startShape) {
return ''; // TODO: throw an error.
Expand All @@ -55,27 +58,102 @@ export class ShapesGraph {
}

private toMermaidSingleShape(shape: ShapeTemplate, id: string, name: string): string {
let counter = 0;
let mermaid = ` S${id}((${name}))\n`;
// S1-->|gsp:geo|S3[ ]-->|"asWKT | asGML"|S4[ ]
shape.requiredPaths.forEach(requiredPath => {
let p = requiredPath.toString();
p = p.replace(/</g, '');
p = p.replace(/>/g, '');
let alreadyProcessedPaths: string[] = [];

shape.nodeLinks.forEach(nodeLink => {
let p = nodeLink.pathPattern.toString();
const isPathRequired = this.isPathRequired(p, shape.requiredPaths);
alreadyProcessedPaths.push(p);
p = this.cleanPath(p);
const linkedShape = this.shapes.get(nodeLink.link);

if (!linkedShape) {
return ''; // TODO: throw an error.
}

const linkedShapeId = `${id}_${this.counter}`;

let link = '-->';

if (!isPathRequired) {
link = '-.->';
}
if (p.startsWith('^')) {
p = p.substring(1);
mermaid += ` S${id}_${counter}[ ]-->|"${p}"|S${id}\n`;
mermaid += ` S${linkedShapeId}[ ]${link}|"${p}"|S${id}\n`;
} else {
mermaid += ` S${id}-->|"${p}"|S${id}_${counter}[ ]\n`;
mermaid += ` S${id}${link}|"${p}"|S${linkedShapeId}[ ]\n`;
}

counter ++;
this.counter++;

const linkedShapeMermaid = this.toMermaidSingleShape(linkedShape, linkedShapeId, 'Shape');
mermaid += linkedShapeMermaid;
});

shape.atLeastOneLists.forEach(list => {
if (list.length > 0) {
const xId = `${id}_${this.counter}`;
mermaid += ` S${id}---X${xId}{OR}\n`;

list.forEach(shape => {
const shapeId = `${id}_${this.counter}`;
this.counter ++;

mermaid += ` X${xId}---S${shapeId}\n`;
const linkedShapeMermaid = this.toMermaidSingleShape(shape, shapeId, 'Shape');
mermaid += linkedShapeMermaid;
});
}
});

mermaid += this.simplePathToMermaid(shape.requiredPaths, alreadyProcessedPaths, id, '-->');
mermaid += this.simplePathToMermaid(shape.optionalPaths, alreadyProcessedPaths, id, '-.->');

return mermaid;
}

private cleanPath(path: string) {
path = path.replace(/</g, '');
return path.replace(/>/g, '');
}

private isPathRequired(path:string, requiredPaths: Path[]) {
for (const requiredPath of requiredPaths) {
if (path === requiredPath.toString()) {
return true;
}
}

return false;
}

private simplePathToMermaid(paths: Path[], alreadyProcessedPaths: string[], shapedId: string, link: string) {
let mermaid = '';

paths.forEach(path => {
let p = path.toString();

if (alreadyProcessedPaths.includes(p)) {
return;
}

alreadyProcessedPaths.push(p);
p = this.cleanPath(p);

if (p.startsWith('^')) {
p = p.substring(1);
mermaid += ` S${shapedId}_${this.counter}[ ]${link}|"${p}"|S${shapedId}\n`;
} else {
mermaid += ` S${shapedId}${link}|"${p}"|S${shapedId}_${this.counter}[ ]\n`;
}

this.counter++;
});

return mermaid;
}

protected constructPathPattern(shapeStore: RdfStore, listItem: Term): Path {
if (listItem.termType === "BlankNode") {
Expand Down
24 changes: 24 additions & 0 deletions tests/07 - mermaid/mermaid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ describe("Test whether the correct Mermaid text is generated for a ShapesGraph",
assert.equal(actualMermaid, expectedMermaid);
});

it("Optional sequence path", async () => {
const actualMermaid = shapesGraph.toMermaid(df.namedNode("http://example.org/OptionalSequencePathShape"));
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/optional-sequence-path.txt', 'utf-8');
assert.equal(actualMermaid, expectedMermaid);
});

it("Inverse path", async () => {
const actualMermaid = shapesGraph.toMermaid(df.namedNode("http://example.org/InversePathShape"));
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/inverse-path.txt', 'utf-8');
Expand Down Expand Up @@ -83,4 +89,22 @@ describe("Test whether the correct Mermaid text is generated for a ShapesGraph",
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/nested-shape.txt', 'utf-8');
assert.equal(actualMermaid, expectedMermaid);
});

it("Nested with optional path shape", async () => {
const actualMermaid = shapesGraph.toMermaid(df.namedNode("http://example.org/NestedWithOptionalShape"));
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/nested-with-optional-shape.txt', 'utf-8');
assert.equal(actualMermaid, expectedMermaid);
});

it("Xone with node shape", async () => {
const actualMermaid = shapesGraph.toMermaid(df.namedNode("http://example.org/XoneWithNodeShape"));
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/xone-with-node-shape.txt', 'utf-8');
assert.equal(actualMermaid, expectedMermaid);
});

it("Xone with node shape 2", async () => {
const actualMermaid = shapesGraph.toMermaid(df.namedNode("http://example.org/XoneWithNodeShape2"));
const expectedMermaid = await fs.readFile('./tests/07 - mermaid/xone-with-node-shape-2.txt', 'utf-8');
assert.equal(actualMermaid, expectedMermaid);
});
});
5 changes: 5 additions & 0 deletions tests/07 - mermaid/nested-shape.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
flowchart TD
S1((Shape))
S1-->|"http://example.org/subject"|S1_0[ ]
S1_0((Shape))
S1_0-->|"http://www.w3.org/2000/01/rdf-schema#label"|S1_0_1[ ]
5 changes: 5 additions & 0 deletions tests/07 - mermaid/nested-with-optional-shape.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
flowchart TD
S1((Shape))
S1-.->|"http://example.org/subject"|S1_0[ ]
S1_0((Shape))
S1_0-->|"http://www.w3.org/2000/01/rdf-schema#label"|S1_0_1[ ]
3 changes: 3 additions & 0 deletions tests/07 - mermaid/optional-sequence-path.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flowchart TD
S1((Shape))
S1-.->|"http://example.org/p1/http://example.org/p2"|S1_0[ ]
54 changes: 54 additions & 0 deletions tests/07 - mermaid/shape.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ ex:SequencePathShape a sh:NodeShape ;
sh:minCount 1
] .

# Optional sequence path
ex:OptionalSequencePathShape a sh:NodeShape ;
sh:property [
sh:path (ex:p1 ex:p2 ) ;
sh:minCount 0
] .

# Path
ex:InversePathShape a sh:NodeShape ;
sh:property [
Expand Down Expand Up @@ -83,3 +90,50 @@ ex:NestedShape a sh:NodeShape ;
]
]
] .

ex:XoneWithNodeShape a sh:NodeShape ;
sh:xone ( [
sh:path foaf:name;
sh:minCount 1
] [
sh:property [
sh:path ex:qualifiedName ;
sh:node ex:QualifiedNameShape;
sh:minCount 1
]
]) .


ex:XoneWithNodeShape2 a sh:NodeShape ;
sh:xone ( [
sh:path foaf:name;
sh:minCount 1
] [
sh:property [
sh:path ex:qualifiedName ;
sh:node ex:QualifiedNameShape2;
sh:minCount 1
]
]) .

ex:QualifiedNameShape2 a sh:NodeShape ;
sh:property [
sh:minCount 1 ;
sh:path ex:name ;
], [
sh:path ex:validUntil ;
# ...
] .

# Nested shape with optional path
ex:NestedWithOptionalShape a sh:NodeShape ;
sh:property [
sh:path ex:subject ;
sh:minCount 0 ;
sh:node [
sh:property [
sh:path rdfs:label ;
sh:minCount 1
]
]
] .
12 changes: 12 additions & 0 deletions tests/07 - mermaid/xone-with-node-shape-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
flowchart TD
S1((Shape))
S1---X1_0{OR}
X1_0---S1_0
S1_0((Shape))
S1_0-->|"http://xmlns.com/foaf/0.1/name"|S1_0_1[ ]
X1_0---S1_2
S1_2((Shape))
S1_2-->|"http://example.org/qualifiedName"|S1_2_3[ ]
S1_2_3((Shape))
S1_2_3-->|"http://example.org/name"|S1_2_3_4[ ]
S1_2_3-.->|"http://example.org/validUntil"|S1_2_3_5[ ]
10 changes: 10 additions & 0 deletions tests/07 - mermaid/xone-with-node-shape.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
flowchart TD
S1((Shape))
S1---X1_0{OR}
X1_0---S1_0
S1_0((Shape))
S1_0-->|"http://xmlns.com/foaf/0.1/name"|S1_0_1[ ]
X1_0---S1_2
S1_2((Shape))
S1_2-->|"http://example.org/qualifiedName"|S1_2_3[ ]
S1_2_3((Shape))

0 comments on commit 6f2c3de

Please sign in to comment.