From 7cc06656e131bfe9cf760398ca8d3297369e1cef Mon Sep 17 00:00:00 2001 From: "Hofstetter Benjamin (extern)" Date: Tue, 12 Nov 2024 10:33:53 +0100 Subject: [PATCH 1/4] implemented path strategy --- .../factory/incoming-path-factory.spec.ts | 158 ++++++++++++++++++ .../link/factory/incoming-path-factory.ts | 26 +++ .../factory/outgoing-path-factory.spec.ts | 158 ++++++++++++++++++ .../link/factory/outgoing-path-factory.ts | 29 ++++ .../sparql/link/factory/path-factory.ts | 8 + .../sparql/link/strategy/inverse-path.spec.ts | 101 +++++++++++ .../sparql/link/strategy/inverse-path.ts | 39 +++++ .../sparql/link/strategy/list-path.spec.ts | 145 ++++++++++++++++ .../shared/sparql/link/strategy/list-path.ts | 48 ++++++ .../sparql/link/strategy/object-path.ts | 22 +++ .../sparql/link/strategy/path-strategy.ts | 12 ++ .../sparql/link/strategy/simple-path.spec.ts | 99 +++++++++++ .../sparql/link/strategy/simple-path.ts | 35 ++++ 13 files changed, 880 insertions(+) create mode 100644 projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts create mode 100644 projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts b/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts new file mode 100644 index 0000000..c160b14 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts @@ -0,0 +1,158 @@ + +import { shacl } from '@blueprint/ontology'; +import rdfEnvironment from '@zazuko/env'; + +import { Parser } from 'n3'; + +import { IncomingPathFactory } from './incoming-path-factory'; + +const parser = new Parser(); + +const simplePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path ex:prop1 ; + ] +.`; + + +const inversePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path [sh:inversePath ex:prop1] ; + ] +.`; + + +const listPathOneTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1) ; + ] +.`; + + +const listPathThreeTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 ex:prop2 ex:prop3) ; + ] +.`; + + +const listPathMoreInverseTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 [sh:inversePath ex:prop2] ex:prop3) ; + ] +.`; + + +const literalPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path "pathAsString" ; + ] +.`; + +describe('IncomingPathFactory', () => { + + + beforeEach(() => { + + }); + + it('IncomingPathFactory :should transform to SPARQL with a simple path', () => { + const factory = new IncomingPathFactory(); + const simpleNodePathDataset = rdfEnvironment.dataset(parser.parse(simplePathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: simpleNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe('^'); + const expectedOutput = ['^']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + it('IncomingPathFactory: should transform to SPARQL with an inverse path', () => { + const factory = new IncomingPathFactory(); + + const inverseNodePathDataset = rdfEnvironment.dataset(parser.parse(inversePathTtl)); + + const pathGraph = rdfEnvironment.clownface({ dataset: inverseNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe(''); + const expectedOutput = ['']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + }); + + it('IncomingPathFactory: should transform to SPARQL with a list of one ', () => { + const factory = new IncomingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathOneTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe('^'); + const expectedOutput = ['^']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('IncomingPathFactory: should transform to SPARQL with a list of three', () => { + const factory = new IncomingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathThreeTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + const expectedOutput = ['^', '^', '^'].reverse(); + expect(l.toPropertyPath()).toBe(expectedOutput.join('/')); + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('IncomingPathFactory: should transform to SPARQL with a list of three with inverse', () => { + const factory = new IncomingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathMoreInverseTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + const expectedOutput = ['^', '', '^'].reverse(); + expect(l.toPropertyPath()).toBe(expectedOutput.join('/')); + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('IncomingPathFactory: should throw an Exception, when applied to a literal path', () => { + const factory = new IncomingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(literalPathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => factory.createPath(path)).toThrowError(TypeError); + }); + }); + + +}); diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts b/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts new file mode 100644 index 0000000..d0cba57 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts @@ -0,0 +1,26 @@ +import { GraphPointer } from "clownface"; + +import { PathStrategy } from "../strategy/path-strategy"; +import { PathFactory } from "./path-factory"; +import { OutgoingPathFactory } from "./outgoing-path-factory"; +import { ObjectPath } from "../strategy/object-path"; + +export class IncomingPathFactory extends PathFactory { + #outgoingPathFactory = new OutgoingPathFactory(); + + createPath(pathNode: GraphPointer): PathStrategy { + const outgoingPath = this.#outgoingPathFactory.createPath(pathNode); + const pathFragments = outgoingPath.toPathFragments(); + pathFragments.reverse(); + const reversedPathFragment = pathFragments.map((fragment) => { + if (fragment.startsWith('^')) { + return fragment.substring(1); + } + return '^' + fragment; + }); + return new ObjectPath(reversedPathFragment); + + } + + +} \ No newline at end of file diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts b/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts new file mode 100644 index 0000000..986044f --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts @@ -0,0 +1,158 @@ + +import { shacl } from '@blueprint/ontology'; +import rdfEnvironment from '@zazuko/env'; + +import { Parser } from 'n3'; + +import { OutgoingPathFactory } from './outgoing-path-factory'; + +const parser = new Parser(); + +const simplePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path ex:prop1 ; + ] +.`; + + +const inversePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path [sh:inversePath ex:prop1] ; + ] +.`; + + +const listPathOneTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1) ; + ] +.`; + + +const listPathThreeTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 ex:prop2 ex:prop3) ; + ] +.`; + + +const listPathMoreInverseTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 [sh:inversePath ex:prop2] ex:prop3) ; + ] +.`; + + +const literalPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path "pathAsString" ; + ] +.`; + +describe('OutgoingPathFactory', () => { + + + beforeEach(() => { + + }); + + it('OutgoingPathFactory :should transform to SPARQL with a simple path', () => { + const factory = new OutgoingPathFactory(); + const simpleNodePathDataset = rdfEnvironment.dataset(parser.parse(simplePathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: simpleNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe(''); + const expectedOutput = ['']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + it('OutgoingPathFactory: should transform to SPARQL with an inverse path', () => { + const factory = new OutgoingPathFactory(); + + const inverseNodePathDataset = rdfEnvironment.dataset(parser.parse(inversePathTtl)); + + const pathGraph = rdfEnvironment.clownface({ dataset: inverseNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe('^'); + const expectedOutput = ['^']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + }); + + it('OutgoingPathFactory: should transform to SPARQL with a list of one ', () => { + const factory = new OutgoingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathOneTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe(''); + const expectedOutput = ['']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('OutgoingPathFactory: should transform to SPARQL with a list of three', () => { + const factory = new OutgoingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathThreeTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe('//'); + const expectedOutput = ['', '', '']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('OutgoingPathFactory: should transform to SPARQL with a list of three with inverse', () => { + const factory = new OutgoingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(listPathMoreInverseTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = factory.createPath(path); + expect(l.toPropertyPath()).toBe('/^/'); + const expectedOutput = ['', '^', '']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('OutgoingPathFactory: should throw an Exception, when applied to a literal path', () => { + const factory = new OutgoingPathFactory(); + const ds = rdfEnvironment.dataset(parser.parse(literalPathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => factory.createPath(path)).toThrowError(TypeError); + }); + }); + + +}); diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts b/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts new file mode 100644 index 0000000..4aa9447 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts @@ -0,0 +1,29 @@ +import { GraphPointer } from "clownface"; + +import { PathStrategy } from "../strategy/path-strategy"; +import { SimplePath } from "../strategy/simple-path"; +import { InversePath } from "../strategy/inverse-path"; +import { ListPath } from "../strategy/list-path"; +import { PathFactory } from "./path-factory"; + +export class OutgoingPathFactory extends PathFactory { + + createPath(pathNode: GraphPointer): PathStrategy { + if (pathNode.term.termType === 'NamedNode') { + return new SimplePath(pathNode); + } + if (pathNode.isList()) { + return new ListPath(pathNode); + } + if (pathNode.term.termType === 'BlankNode') { + return new InversePath(pathNode); + } + if (pathNode.term.termType === 'Literal') { + throw new TypeError('Path cannot cannot be a literal'); + } + throw new TypeError('Path must be a NamedNode, BlankNode or a list'); + + } + + +} \ No newline at end of file diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts b/projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts new file mode 100644 index 0000000..b1aec92 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts @@ -0,0 +1,8 @@ +import { GraphPointer } from "clownface"; + +import { PathStrategy } from "../strategy/path-strategy"; + + +export abstract class PathFactory { + abstract createPath(pathNode: GraphPointer): PathStrategy; +} \ No newline at end of file diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts new file mode 100644 index 0000000..9711861 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts @@ -0,0 +1,101 @@ + +import { shacl } from '@blueprint/ontology'; +import rdfEnvironment from '@zazuko/env'; + +import { Parser } from 'n3'; +import { InversePath } from './inverse-path'; + +const parser = new Parser(); + +const simplePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path ex:prop1 ; + ] +.`; + + +const inversePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path [sh:inversePath ex:prop1] ; + ] +.`; + + +const listPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1) ; + ] +.`; + + +const literalPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path "pathAsString" ; + ] +.`; + + +describe('Inverse Path Strategy', () => { + + + beforeEach(() => { + + }); + + it('InversePath: should transform to SPARQL ', () => { + const inverseNodePathDataset = rdfEnvironment.dataset(parser.parse(inversePathTtl)); + + const pathGraph = rdfEnvironment.clownface({ dataset: inverseNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = new InversePath(path); + expect(l.toPropertyPath()).toBe('^'); + const expectedOutput = ['^']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + }); + + + it('InversePath: should throw an Exception, when applied to a forward path ', () => { + const ds = rdfEnvironment.dataset(parser.parse(simplePathTtl)); + + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => new InversePath(path)).toThrowError(TypeError); + }); + }); + + it('InversePath: should throw an Exception, when applied to a list path ', () => { + const ds = rdfEnvironment.dataset(parser.parse(listPathTtl)); + const listPathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + + listPathGraph.forEach(path => { + expect(() => new InversePath(path)).toThrowError(TypeError); + }); + }); + + it('InversePath: should throw an Exception, when applied to a literal path ', () => { + const ds = rdfEnvironment.dataset(parser.parse(literalPathTtl)); + const literalPathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + + literalPathGraph.forEach(path => { + expect(() => new InversePath(path)).toThrowError(TypeError); + }); + }); + +}); diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts new file mode 100644 index 0000000..7734442 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts @@ -0,0 +1,39 @@ +import { GraphPointer } from "clownface"; +import { PathStrategy } from "./path-strategy"; +import { shacl } from "@blueprint/ontology"; + + +export class InversePath extends PathStrategy { + readonly #pathFragments: string[]; + + constructor(pathNode: GraphPointer) { + super(pathNode); + if (this._node.isList()) { + throw new TypeError('InversePathLink cannot be a list'); + } + + if (this._node.term.termType === 'NamedNode') { + throw new TypeError('InversePathLink cannot be a named node'); + } + + if (this._node.term.termType === 'Literal') { + throw new TypeError('InversePathLink cannot be a literal'); + } + + const nodeValue = this._node.out(shacl.inversePathNamedNode).value; + + if (!nodeValue) { + throw new TypeError('InversePathLink must have a sh:inversePath'); + } + // SPARQL query generation for a sh:inversePath link + this.#pathFragments = ['^<' + nodeValue + '>']; + } + + toPropertyPath(): string { + return this.#pathFragments[0]; + } + + toPathFragments(): string[] { + return this.#pathFragments + } +} \ No newline at end of file diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts new file mode 100644 index 0000000..49a9fb6 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts @@ -0,0 +1,145 @@ + +import { shacl } from '@blueprint/ontology'; +import rdfEnvironment from '@zazuko/env'; + +import { Parser } from 'n3'; + +import { ListPath } from './list-path'; + +const parser = new Parser(); + +const simplePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path ex:prop1 ; + ] +.`; + + +const inversePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path [sh:inversePath ex:prop1] ; + ] +.`; + + +const listPathOneTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1) ; + ] +.`; + + +const listPathThreeTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 ex:prop2 ex:prop3) ; + ] +.`; + + +const listPathMoreInverseTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1 [sh:inversePath ex:prop2] ex:prop3) ; + ] +.`; + + +const literalPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path "pathAsString" ; + ] +.`; + +describe('ListPath Strategy', () => { + + + beforeEach(() => { + + }); + + it('ListPath :should transform to SPARQL with a list of one ', () => { + const ds = rdfEnvironment.dataset(parser.parse(listPathOneTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = new ListPath(path); + expect(l.toPropertyPath()).toBe(''); + const expectedOutput = ['']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('ListPath :should transform to SPARQL with a list of three', () => { + const ds = rdfEnvironment.dataset(parser.parse(listPathThreeTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = new ListPath(path); + expect(l.toPropertyPath()).toBe('//'); + const expectedOutput = ['', '', '']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('ListPath :should transform to SPARQL with a list of three with inverse', () => { + const ds = rdfEnvironment.dataset(parser.parse(listPathMoreInverseTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = new ListPath(path); + expect(l.toPropertyPath()).toBe('/^/'); + const expectedOutput = ['', '^', '']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('ListPath: should throw an Exception, when applied to a literal path', () => { + const ds = rdfEnvironment.dataset(parser.parse(literalPathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => new ListPath(path)).toThrowError(TypeError); + }); + + }); + + it('ListPath: should throw an Exception, when applied to a inverse path', () => { + const ds = rdfEnvironment.dataset(parser.parse(inversePathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => new ListPath(path)).toThrowError(TypeError); + }); + + }); + + it('ListPath: should throw an Exception, when applied to a simple path', () => { + const ds = rdfEnvironment.dataset(parser.parse(simplePathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: ds }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => new ListPath(path)).toThrowError(TypeError); + }); + + }); +}); diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts new file mode 100644 index 0000000..4f55179 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts @@ -0,0 +1,48 @@ +import { GraphPointer } from "clownface"; +import { PathStrategy } from "./path-strategy"; +import { SimplePath } from "./simple-path"; +import { InversePath } from "./inverse-path"; + +export class ListPath extends PathStrategy { + readonly #pathFragments: string[]; + + constructor(pathNode: GraphPointer) { + super(pathNode); + if (this._node.term.termType === 'NamedNode') { + throw new TypeError('CompositePathLink cannot be a named node'); + } + + if (this._node.term.termType === 'Literal') { + throw new TypeError('CompositePathLink cannot be a literal'); + } + + if (!this._node.isList()) { + throw new TypeError('CompositePathLink must be a list'); + } + + this.#pathFragments = []; + [...this._node.list()].forEach((element) => { + if (element.term.termType === 'NamedNode') { + this.#pathFragments.push(new SimplePath(element).toPropertyPath()); + return; + } + if (element.term.termType === 'BlankNode') { + this.#pathFragments.push(new InversePath(element).toPropertyPath()); + return; + } + if (element.term.termType === 'Literal') { + throw new TypeError('CompositePathLink cannot be a literal'); + } + throw new TypeError('CompositePathLink path element must be a NamedNode or a BlankNode'); + + }); + } + + toPropertyPath(): string { + return this.#pathFragments.join('/'); + } + + toPathFragments(): string[] { + return this.#pathFragments; + } +} diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts new file mode 100644 index 0000000..f1b6cb3 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts @@ -0,0 +1,22 @@ +import { PathStrategy } from "./path-strategy"; + +export class ObjectPath extends PathStrategy { + readonly #pathFragments: string[] = []; + + constructor(fragments: string[]) { + super(null); + if (fragments.length === 0) { + throw new TypeError('ObjectPathLink must have at least one fragment'); + } + this.#pathFragments = fragments; + + } + + toPropertyPath(): string { + return this.#pathFragments.join('/'); + } + + toPathFragments(): string[] { + return this.#pathFragments + } +} diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts new file mode 100644 index 0000000..68182fc --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts @@ -0,0 +1,12 @@ +import { GraphPointer } from "clownface"; + +export abstract class PathStrategy { + protected _node: GraphPointer | null; + + constructor(node: GraphPointer | null) { + this._node = node; + } + + abstract toPropertyPath(): string; + abstract toPathFragments(): string[] +} diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts new file mode 100644 index 0000000..fdf3b20 --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts @@ -0,0 +1,99 @@ + +import { shacl } from '@blueprint/ontology'; +import rdfEnvironment from '@zazuko/env'; + +import { Parser } from 'n3'; + +import { SimplePath } from './simple-path'; + +const parser = new Parser(); + +const simplePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path ex:prop1 ; + ] +.`; + + +const inversePathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path [sh:inversePath ex:prop1] ; + ] +.`; + + +const listPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path (ex:prop1) ; + ] +.`; + + +const literalPathTtl = ` +@prefix sh: . +@prefix ex: . + +ex:SimpleNodeShape a sh:NodeShape ; + sh:property [ + sh:path "pathAsString" ; + ] +.`; + +describe('Simple Path Strategy', () => { + + + beforeEach(() => { + + }); + + it('SimplePath :should transform to SPARQL ', () => { + const simpleNodePathDataset = rdfEnvironment.dataset(parser.parse(simplePathTtl)); + const pathGraph = rdfEnvironment.clownface({ dataset: simpleNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + const l = new SimplePath(path); + expect(l.toPropertyPath()).toBe(''); + const expectedOutput = ['']; + expect(l.toPathFragments()).toEqual(expectedOutput); + }); + + }); + + it('SimplePath: should throw an Exception, when applied to a inverse path ', () => { + const inverseNodePathDataset = rdfEnvironment.dataset(parser.parse(inversePathTtl)); + + const pathGraph = rdfEnvironment.clownface({ dataset: inverseNodePathDataset }).out(shacl.pathNamedNode); + pathGraph.forEach(path => { + expect(() => new SimplePath(path)).toThrowError(TypeError); + }); + }); + + it('SimplePath: should throw an Exception, when applied to a list path ', () => { + const listNodePathDataset = rdfEnvironment.dataset(parser.parse(listPathTtl)); + const listPathGraph = rdfEnvironment.clownface({ dataset: listNodePathDataset }).out(shacl.pathNamedNode); + + listPathGraph.forEach(path => { + expect(() => new SimplePath(path)).toThrowError(TypeError); + }); + }); + + it('SimplePath: should throw an Exception, when applied to a literal path', () => { + const literalNodePathDataset = rdfEnvironment.dataset(parser.parse(literalPathTtl)); + const literalPathGraph = rdfEnvironment.clownface({ dataset: literalNodePathDataset }).out(shacl.pathNamedNode); + + literalPathGraph.forEach(path => { + expect(() => new SimplePath(path)).toThrowError(TypeError); + }); + }); +}); diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts b/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts new file mode 100644 index 0000000..56670bb --- /dev/null +++ b/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts @@ -0,0 +1,35 @@ +import { GraphPointer } from "clownface"; +import { PathStrategy } from "./path-strategy"; + +export class SimplePath extends PathStrategy { + + readonly #pathFragments: string[]; + + constructor(pathNode: GraphPointer) { + super(pathNode); + if (this._node.isList()) { + throw new TypeError('SimpleNodeLink cannot be a list'); + } + + if (this._node.term.termType === 'BlankNode') { + throw new TypeError('SimpleNodeLink cannot be a blank node'); + } + + if (this._node.term.termType === 'Literal') { + throw new TypeError('SimpleNodeLink cannot be a literal'); + } + + this.#pathFragments = ['<' + this._node.value + '>']; + + } + + toPropertyPath(): string { + return this.#pathFragments[0]; + } + + toPathFragments(): string[] { + return this.#pathFragments + } + + +} From 37f824d32f09a209dc8518f4c8734ca209b7d321 Mon Sep 17 00:00:00 2001 From: "Hofstetter Benjamin (extern)" Date: Tue, 12 Nov 2024 10:34:50 +0100 Subject: [PATCH 2/4] rename folder --- .../sparql/{link => path}/factory/incoming-path-factory.spec.ts | 0 .../shared/sparql/{link => path}/factory/incoming-path-factory.ts | 0 .../sparql/{link => path}/factory/outgoing-path-factory.spec.ts | 0 .../shared/sparql/{link => path}/factory/outgoing-path-factory.ts | 0 .../src/app/shared/sparql/{link => path}/factory/path-factory.ts | 0 .../shared/sparql/{link => path}/strategy/inverse-path.spec.ts | 0 .../src/app/shared/sparql/{link => path}/strategy/inverse-path.ts | 0 .../app/shared/sparql/{link => path}/strategy/list-path.spec.ts | 0 .../src/app/shared/sparql/{link => path}/strategy/list-path.ts | 0 .../src/app/shared/sparql/{link => path}/strategy/object-path.ts | 0 .../app/shared/sparql/{link => path}/strategy/path-strategy.ts | 0 .../app/shared/sparql/{link => path}/strategy/simple-path.spec.ts | 0 .../src/app/shared/sparql/{link => path}/strategy/simple-path.ts | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename projects/blueprint/src/app/shared/sparql/{link => path}/factory/incoming-path-factory.spec.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/factory/incoming-path-factory.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/factory/outgoing-path-factory.spec.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/factory/outgoing-path-factory.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/factory/path-factory.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/inverse-path.spec.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/inverse-path.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/list-path.spec.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/list-path.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/object-path.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/path-strategy.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/simple-path.spec.ts (100%) rename projects/blueprint/src/app/shared/sparql/{link => path}/strategy/simple-path.ts (100%) diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts b/projects/blueprint/src/app/shared/sparql/path/factory/incoming-path-factory.spec.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.spec.ts rename to projects/blueprint/src/app/shared/sparql/path/factory/incoming-path-factory.spec.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts b/projects/blueprint/src/app/shared/sparql/path/factory/incoming-path-factory.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/factory/incoming-path-factory.ts rename to projects/blueprint/src/app/shared/sparql/path/factory/incoming-path-factory.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts b/projects/blueprint/src/app/shared/sparql/path/factory/outgoing-path-factory.spec.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.spec.ts rename to projects/blueprint/src/app/shared/sparql/path/factory/outgoing-path-factory.spec.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts b/projects/blueprint/src/app/shared/sparql/path/factory/outgoing-path-factory.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/factory/outgoing-path-factory.ts rename to projects/blueprint/src/app/shared/sparql/path/factory/outgoing-path-factory.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts b/projects/blueprint/src/app/shared/sparql/path/factory/path-factory.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/factory/path-factory.ts rename to projects/blueprint/src/app/shared/sparql/path/factory/path-factory.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/inverse-path.spec.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.spec.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/inverse-path.spec.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/inverse-path.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/inverse-path.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/inverse-path.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/list-path.spec.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/list-path.spec.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/list-path.spec.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/list-path.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/list-path.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/list-path.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/object-path.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/object-path.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/object-path.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/path-strategy.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/path-strategy.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/path-strategy.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/simple-path.spec.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.spec.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/simple-path.spec.ts diff --git a/projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts b/projects/blueprint/src/app/shared/sparql/path/strategy/simple-path.ts similarity index 100% rename from projects/blueprint/src/app/shared/sparql/link/strategy/simple-path.ts rename to projects/blueprint/src/app/shared/sparql/path/strategy/simple-path.ts From 3b8df9e9b6c1d0476a0b91909990d288fa96ef19 Mon Sep 17 00:00:00 2001 From: "Hofstetter Benjamin (extern)" Date: Tue, 12 Nov 2024 12:48:06 +0100 Subject: [PATCH 3/4] add to apply new path strategy --- .../graph/aggregate/aggregate.service.ts | 16 ++++----- .../composition-to-node-link-factory.ts | 7 ++++ ...coming-composition-to-node-link-factory.ts | 12 +++++++ ...tgoing-composition-to-node-link-factory.ts | 10 ++++++ .../graph/aggregate/model/aggregation.ts | 20 +++++------ .../composition-to-composition-link.ts | 4 +-- .../composition/composition-to-node-link.ts | 16 ++++----- .../graph/aggregate/model/path-definition.ts | 33 +++++++++---------- 8 files changed, 70 insertions(+), 48 deletions(-) create mode 100644 projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/composition-to-node-link-factory.ts create mode 100644 projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory.ts create mode 100644 projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory.ts diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts b/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts index 3db0ffa..1ce3281 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts @@ -4,7 +4,8 @@ import { Dataset } from '@rdfjs/types'; import rdfEnvironment from '@zazuko/env'; import { CompositionToNodeLink, ICompositionToNodeLink } from './model/composition/composition-to-node-link'; import { CompositionToCompositionLink, ICompositionToCompositionLink } from './model/composition/composition-to-composition-link'; - +import { OutgoingCompositionToNodeLinkFactory } from './factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory'; +import { IncomingCompositionToNodeLinkFactory } from './factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory'; @Injectable({ providedIn: 'root' }) @@ -67,21 +68,20 @@ export class AggregateService { const linkGraph = rdfEnvironment.clownface({ dataset: linkDataset }); const outLinks: CompositionToNodeLink[] = []; - + const outgoingCompositionToNodeLinkFactory = new OutgoingCompositionToNodeLinkFactory(); classIris.forEach(iri => { - const links = linkGraph.namedNode(iri).in(shacl.targetClassNamedNode).out(shacl.groupNamedNode).in(shacl.targetClassNamedNode).has(rdf.typeNamedNode, blueprint.CompositionToNodeLinkNamedNode).map(link => new CompositionToNodeLink(link)); + const links = linkGraph.namedNode(iri).in(shacl.targetClassNamedNode).out(shacl.groupNamedNode).in(shacl.targetClassNamedNode).has(rdf.typeNamedNode, blueprint.CompositionToNodeLinkNamedNode).map(link => outgoingCompositionToNodeLinkFactory.createCompositionToNodeLink(link)); outLinks.push(...links); }); - const inLinks: CompositionToNodeLink[] = []; - + const inLinks: ICompositionToNodeLink[] = []; + const incomingCompositionToNodeLinkFactory = new IncomingCompositionToNodeLinkFactory(); classIris.forEach(iri => { - const links = linkGraph.namedNode(iri).in(blueprint.targetNamedNode).has(rdf.typeNamedNode, blueprint.CompositionToNodeLinkNamedNode).map(link => new CompositionToNodeLink(link)); + const links = linkGraph.namedNode(iri).in(blueprint.targetNamedNode).has(rdf.typeNamedNode, blueprint.CompositionToNodeLinkNamedNode).map(link => incomingCompositionToNodeLinkFactory.createCompositionToNodeLink(link)); inLinks.push(...links); }); - const invertedLinks: ICompositionToNodeLink[] = inLinks.map(link => link.invert()); - return [...outLinks, ...invertedLinks]; + return [...outLinks, ...inLinks]; } getCompositionToCompositionLinkQueries(viewGraphMetadata: Dataset, classIris: string[], subject: string): string[] { diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/composition-to-node-link-factory.ts b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/composition-to-node-link-factory.ts new file mode 100644 index 0000000..9a8badb --- /dev/null +++ b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/composition-to-node-link-factory.ts @@ -0,0 +1,7 @@ +import { GraphPointer } from "clownface"; + +import { ICompositionToNodeLink } from "../../model/composition/composition-to-node-link"; + +export abstract class CompositionToNodeLinkFactory { + abstract createCompositionToNodeLink(node: GraphPointer): ICompositionToNodeLink; +} \ No newline at end of file diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory.ts b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory.ts new file mode 100644 index 0000000..9262d24 --- /dev/null +++ b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory.ts @@ -0,0 +1,12 @@ +import { GraphPointer } from "clownface"; + +import { CompositionToNodeLink, ICompositionToNodeLink } from "../../model/composition/composition-to-node-link"; +import { CompositionToNodeLinkFactory } from "./composition-to-node-link-factory"; + +export class IncomingCompositionToNodeLinkFactory extends CompositionToNodeLinkFactory { + + createCompositionToNodeLink(node: GraphPointer): ICompositionToNodeLink { + + return new CompositionToNodeLink(node).invert(); + } +} \ No newline at end of file diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory.ts b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory.ts new file mode 100644 index 0000000..c4c9749 --- /dev/null +++ b/projects/blueprint/src/app/core/service/graph/aggregate/factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory.ts @@ -0,0 +1,10 @@ +import { GraphPointer } from "clownface"; +import { CompositionToNodeLink } from "../../model/composition/composition-to-node-link"; +import { CompositionToNodeLinkFactory } from "./composition-to-node-link-factory"; + +export class OutgoingCompositionToNodeLinkFactory extends CompositionToNodeLinkFactory { + + createCompositionToNodeLink(node: GraphPointer): CompositionToNodeLink { + return new CompositionToNodeLink(node); + } +} \ No newline at end of file diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/aggregation.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/aggregation.ts index da19821..007e839 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/aggregation.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/aggregation.ts @@ -45,8 +45,8 @@ export abstract class Aggregation extends ClownfaceObject { /** - * This calss represents an inner node of an Aggregate. - * It provides information aobut the target class and the path to the root. + * This class represents an inner node of an Aggregate. + * It provides information about the target class and the path to the root. * * It also provides information if it's a connection point. An connection point * is a node that is exposed to the outside and can be connected to other nodes or aggregates. @@ -122,10 +122,10 @@ export class AggregateMemberNode extends ClownfaceObject { if (node.in(blueprint.hasRootNamedNode).values.length > 0) { return; } - const pathElement = node.in(shacl.nodeNamedNode); - const cfPath = pathElement.out(shacl.pathNamedNode); + const pathElementNodes = node.in(shacl.nodeNamedNode); + const cfPath = pathElementNodes.out(shacl.pathNamedNode); if (cfPath.values.length !== 1) { - console.warn(`PathElement has no path: ${pathElement}`); + console.warn(`PathElement has no path: ${pathElementNodes.values}`); return; } if (cfPath.term.termType === 'BlankNode') { @@ -136,17 +136,17 @@ export class AggregateMemberNode extends ClownfaceObject { } const inversePath = inversePaths[0]; path.push(new PathDefinition(node.out(shacl.targetClassNamedNode).value, - pathElement.in(shacl.propertyNamedNode).out(shacl.targetClassNamedNode).value, - `<${inversePath}>`)) + pathElementNodes.in(shacl.propertyNamedNode).out(shacl.targetClassNamedNode).value, + [`<${inversePath}>`])) } else { path.push(new PathDefinition( node.out(shacl.targetClassNamedNode).value, - pathElement.in(shacl.propertyNamedNode).out(shacl.targetClassNamedNode).value, - `^<${cfPath.values[0]}>` + pathElementNodes.in(shacl.propertyNamedNode).out(shacl.targetClassNamedNode).value, + [`^<${cfPath.values[0]}>`] )); } - const nextNodeShape = pathElement.in(shacl.propertyNamedNode); + const nextNodeShape = pathElementNodes.in(shacl.propertyNamedNode); this._traversePathToRoot((nextNodeShape as GraphPointer), path); } diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-composition-link.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-composition-link.ts index afc4d90..d3d0ff7 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-composition-link.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-composition-link.ts @@ -82,11 +82,11 @@ export class CompositionToCompositionLink extends ClownfaceObject implements ICo const cfPath = p.out(shacl.pathNamedNode).toArray()[0]; if (cfPath.term.termType === 'NamedNode') { - return [new PathDefinition(targetClass, shClass, `<${cfPath.value}>`)]; + return [new PathDefinition(targetClass, shClass, [`<${cfPath.value}>`])]; } if (cfPath.term.termType === 'BlankNode') { const inversePath = cfPath.out(shacl.inversePathNamedNode).values[0]; - return [new PathDefinition(targetClass, shClass, `<${inversePath}>`)]; + return [new PathDefinition(targetClass, shClass, [`<${inversePath}>`])]; } console.warn(`no path found for path element: ${p}`); return [] as PathDefinition[]; diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts index fca169d..fcc7cfb 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts @@ -3,6 +3,7 @@ import { ClownfaceObject } from "@blueprint/model/clownface-object/clownface-obj import { blueprint, rdf, rdfs, shacl } from "@blueprint/ontology"; import { PathDefinition } from "../path-definition"; import { Composition } from "../composition"; +import { OutgoingPathFactory } from "projects/blueprint/src/app/shared/sparql/path/factory/outgoing-path-factory"; export interface ICompositionToNodeLink { @@ -16,7 +17,6 @@ export interface ICompositionToNodeLink { } export class CompositionToNodeLink extends ClownfaceObject implements ICompositionToNodeLink { - private _sourceComposition: Composition | null | undefined = undefined; private _targetComposition: Composition | null | undefined = undefined; @@ -108,15 +108,11 @@ export class CompositionToNodeLink extends ClownfaceObject implements ICompositi const targetClass = p.out(shacl.targetClassNamedNode).values[0]; const shClass = p.out(shacl.classNamedNode).values[0]; const cfPath = p.out(shacl.pathNamedNode).toArray()[0]; - if (cfPath.term.termType === 'NamedNode') { - return [new PathDefinition(targetClass, shClass, `<${cfPath.value}>`)]; - } - if (cfPath.term.termType === 'BlankNode') { - const inversePath = cfPath.out(shacl.inversePathNamedNode).values[0]; - return [new PathDefinition(targetClass, shClass, `^<${inversePath}>`)]; - } - console.warn(`no path found for path element: ${p}`); - return [] as PathDefinition[]; + const pathFactory = new OutgoingPathFactory(); + debugger; + const path = pathFactory.createPath(cfPath); + return [new PathDefinition(targetClass, shClass, path.toPathFragments())]; + }); return path; diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts index c9b17b5..c7a1a5d 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts @@ -7,27 +7,19 @@ export class PathDefinition { public readonly sourceClassIri: string; public readonly targetClassIri: string; - public readonly path: string; + #pathFragments: string[]; - constructor(sourceClassIri: string, targetClassIri: string, path: string) { + constructor(sourceClassIri: string, targetClassIri: string, pathFragments: string[]) { this.sourceClassIri = sourceClassIri; this.targetClassIri = targetClassIri; - const pathToken1 = path[0]; - const pathToken2 = path[1]; - - if (pathToken1 === '^' && pathToken2 === '<') { - this.path = path; - } else if (pathToken1 === '<') { - this.path = path; - } else { - console.warn(`PathDefinition: path is not valid. It should start with ^< or <. Path is ${path}. Fix it`); - if (pathToken1 === '^') { - this.path = `^<${path.substring(1)}>`; - } else { - this.path = `<${path}>`; - } - + if (pathFragments.length === 0) { + throw new Error('Path must have at least one fragment'); } + this.#pathFragments = pathFragments; + } + + get path(): string { + return this.#pathFragments.join(' '); } /** @@ -36,7 +28,12 @@ export class PathDefinition { * @returns the inverse of the path */ invert(): PathDefinition { - const inversePath = this.path.startsWith('^') ? this.path.substring(1) : `^${this.path}` + const inversePath = this.#pathFragments.map(fragment => { + if (fragment.startsWith('^')) { + return fragment.substring(1); + } + return `^${fragment}`; + }).reverse(); return new PathDefinition(this.targetClassIri, this.sourceClassIri, inversePath); } } From dfbabe6e5816829438e0d5149a87a9be1ab9e717 Mon Sep 17 00:00:00 2001 From: "Hofstetter Benjamin (extern)" Date: Tue, 12 Nov 2024 19:21:13 +0100 Subject: [PATCH 4/4] close #30 --- doc/06_aggregate/aggregate_debug.sparqlbook | 32 ++ .../graph/aggregate/aggregate.service.ts | 275 +++--------------- .../composition/composition-to-node-link.ts | 1 - .../graph/aggregate/model/path-definition.ts | 2 +- ...on-to-composition-links-for-class.query.ts | 141 +++++++++ ...mposition-to-node-links-for-class.query.ts | 134 +++++++++ .../neptune-full-text-search.class.ts | 6 - projects/blueprint/src/config.json | 2 +- 8 files changed, 351 insertions(+), 242 deletions(-) create mode 100644 doc/06_aggregate/aggregate_debug.sparqlbook create mode 100644 projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-composition-links-for-class.query.ts create mode 100644 projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-node-links-for-class.query.ts diff --git a/doc/06_aggregate/aggregate_debug.sparqlbook b/doc/06_aggregate/aggregate_debug.sparqlbook new file mode 100644 index 0000000..8e5b2f2 --- /dev/null +++ b/doc/06_aggregate/aggregate_debug.sparqlbook @@ -0,0 +1,32 @@ +[ + { + "kind": 2, + "language": "sparql", + "value": "# [endpoint=https://test.lindas.admin.ch/query]\n \n\nPREFIX rdf: \n PREFIX rdfs: \n PREFIX blueprint: \n\n CONSTRUCT {\n blueprint:result .\n a blueprint:CompositionLinkResult .\n blueprint:result ?result .\n rdfs:label \"Publisher\" .\n ?result ?resultP ?resultO .\n ?element_1 ?connectionPointP ?connectionPointO .\n ?result blueprint:target ?element_1 .\n \n } WHERE {\n {\n # first path element - form link\n a .\n ^/ ?result .\n ?result a .\n \n VALUES ?resultP {\n rdf:type\n rdfs:label\n }\n ?result ?resultP ?resultO .\n \n }\n }", + "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "# [endpoint=https://test.lindas.admin.ch/query]\n\n PREFIX rdf: \n PREFIX rdfs: \n PREFIX blueprint: \n\n CONSTRUCT {\n blueprint:result .\n a blueprint:CompositionLinkResult .\n blueprint:result ?result .\n rdfs:label \"Publsied by\" .\n ?result ?resultP ?resultO .\n ?element_0_0 ?connectionPointP ?connectionPointO .\n ?result blueprint:source ?element_0_0 . \n } WHERE {\n \n a .\n ^/ ?element_0_1 .\n ?element_0_1 a .\n \n }", + "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "PREFIX sh: \nPREFIX rdf: \nPREFIX rdfs: \nPREFIX bp: \nPREFIX data: \nPREFIX oci: \nPREFIX k8s: \n\nCONSTRUCT {\n ?shape sh:group ?aggregate .\n ?shape sh:targetClass ?type .\n\n ?outgoingLinks ?linkP ?linkO .\n ?propertyShape ?propertyP ?propertyO .\n ?path sh:inversePath ?inversePath .\n\n ?inShape sh:group ?inAggregate .\n ?inShape sh:targetClass ?type.\n\n ?incomingLinks ?linkP ?linkO .\n \n} \nWHERE {\n {\n # outgoing links\n {\n SELECT ?outgoingLinks ?aggregate ?shape ?type WHERE {\n BIND (k8s:Deployment AS ?type)\n ?shape sh:targetClass ?type .\n ?shape sh:group ?aggregate . \n ?aggregate rdf:type/rdfs:subClassOf* bp:Aggregate.\n ?outgoingLinks sh:targetClass ?aggregate .\n ?outgoingLinks a bp:AggregateLink .\n }\n }\n VALUES ?linkP { \n sh:targetClass\n sh:property\n bp:target\n rdfs:label\n }\n ?outgoingLinks ?linkP ?linkO .\n } UNION {\n # outgoing links sh:property\n {\n SELECT ?outgoingLinks WHERE {\n BIND (k8s:Deployment AS ?type)\n ?shape sh:targetClass ?type .\n ?shape sh:group ?aggregate . \n ?aggregate rdf:type/rdfs:subClassOf* bp:Aggregate.\n ?outgoingLinks sh:targetClass ?aggregate .\n ?outgoingLinks a bp:AggregateLink .\n }\n }\n \n ?outgoingLinks sh:property ?propertyShape . \n\n VALUES ?propertyP { \n sh:targetClass\n sh:path\n sh:class\n sh:name\n }\n ?propertyShape ?propertyP ?propertyO .\n OPTIONAL {\n ?propertyShape sh:path ?path .\n ?path sh:inversePath ?inversePath .\n\n }\n } UNION {\n # incoming links\n {\n SELECT ?incomingLinks ?inAggregate ?inShape ?type WHERE {\n BIND (k8s:Deployment AS ?type)\n ?inShape sh:targetClass ?type .\n ?inShape sh:group ?inAggregate . \n ?inAggregate rdf:type/rdfs:subClassOf* bp:Aggregate.\n ?incomingLinks bp:target ?inAggregate .\n ?incomingLinks a bp:AggregateLink .\n }\n }\n VALUES ?linkP { \n sh:targetClass\n sh:property\n bp:target\n rdfs:label\n }\n ?incomingLinks ?linkP ?linkO .\n } UNION {\n # incoming links sh:property\n {\n SELECT ?incomingLinks WHERE {\n BIND (k8s:Deployment AS ?type)\n ?shape sh:targetClass ?type .\n ?shape sh:group ?aggregate . \n ?aggregate rdf:type/rdfs:subClassOf* bp:Aggregate.\n ?incomingLinks bp:target ?aggregate .\n ?incomingLinks a bp:AggregateLink .\n }\n }\n ?incomingLinks sh:property ?propertyShape .\n VALUES ?propertyP { \n sh:targetClass\n sh:path\n sh:class\n sh:name\n }\n ?propertyShape ?propertyP ?propertyO .\n OPTIONAL {\n ?propertyShape sh:path ?path .\n ?path sh:inversePath ?inversePath .\n\n }\n }\n}\n\n\n\n", + "metadata": {} + }, + { + "kind": 1, + "language": "markdown", + "value": "# Bidiractional Link with Two Connectors\n", + "metadata": {} + }, + { + "kind": 2, + "language": "sparql", + "value": "\n PREFIX rdf: \n PREFIX rdfs: \n PREFIX blueprint: \n \n CONSTRUCT {\n blueprint:result .\n a blueprint:CompositionLinkResult .\n blueprint:result ?result .\n rdfs:label \"Sotred Business Objects\" .\n ?result ?resultP ?resultO .\n ?element_0_2 ?connectionPointP ?connectionPointO .\n ?element_0_2 a blueprint:ConnectionPoint .\n ?result blueprint:source ?element_0_2 . \n ?element_0_2 blueprint:target ?element_0_3 . \n ?element_0_3 ?connectionPointP ?connectionPointODest .\n ?element_0_3 a blueprint:ConnectionPoint .\n } WHERE {\n \n a .\n ?element_0_1 .\n \n\n ?element_0_1 ?element_0_2 .\n ?element_0_2 a .\n VALUES ?connectionPointP {\n rdfs:label\n rdf:type\n }\n ?element_0_2 ?connectionPointP ?connectionPointO .\n \n\n ?element_0_2 ^ ?element_0_3 .\n ?element_0_3 a .\n ?element_0_3 ?connectionPointP ?connectionPointODest .\n \n\n ?element_0_3 ^ ?result .\n ?result a .\n VALUES ?resultP {\n rdf:type\n rdfs:label\n }\n ?result ?resultP ?resultO .\n \n }", + "metadata": {} + } +] \ No newline at end of file diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts b/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts index 1ce3281..35c8892 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/aggregate.service.ts @@ -6,6 +6,8 @@ import { CompositionToNodeLink, ICompositionToNodeLink } from './model/compositi import { CompositionToCompositionLink, ICompositionToCompositionLink } from './model/composition/composition-to-composition-link'; import { OutgoingCompositionToNodeLinkFactory } from './factory/composition-to-node-link-factory/outgoing-composition-to-node-link-factory'; import { IncomingCompositionToNodeLinkFactory } from './factory/composition-to-node-link-factory/incoming-composition-to-node-link-factory'; +import { compositionToNodeLinksForClassQuery } from './query/composition-to-node-links-for-class.query'; +import { compositionToCompositionLinksForClassQuery } from './query/composition-to-composition-links-for-class.query'; @Injectable({ providedIn: 'root' }) @@ -382,7 +384,6 @@ export class AggregateService { private _createQueryForRootOfSourceAggregate(link: ICompositionToNodeLink, subject: string): string[] { const linkTargetNodeClass = link.targetNodeIri; const linkSourceComposition = link.sourceComposition; - if (linkSourceComposition === null) { console.warn('No source composition'); return []; @@ -416,7 +417,7 @@ export class AggregateService { const path = [...pathFromRoot, ...linkPath]; const body = path.map((pathElement, index) => { - if (index === pathFromRoot.length - 1) { + if (index === pathFromRoot.length - 1 && pathFromRoot.length != 0) { if (index === 0) { return ` <${subject}> a <${pathElement.sourceClassIri}> . @@ -442,6 +443,23 @@ export class AggregateService { } if (index === 0) { + if (pathFromRoot.length === 0) { + return ` + # first path element - form link + <${subject}> a <${pathElement.sourceClassIri}> . + <${subject}> ${pathElement.path} ?result . + ?result a <${pathElement.targetClassIri}> . + + VALUES ?resultP { + ${rdf.typePrefixed} + ${rdfs.labelPrefixed} + } + ?result ?resultP ?resultO . + BIND(?result as ?connectionPointO) + BIND(?result as ?element_0_0) + + `; + } return ` <${subject}> a <${pathElement.sourceClassIri}> . <${subject}> ${pathElement.path} ?element_${outerIndex}_${index + 1} . @@ -510,9 +528,12 @@ export class AggregateService { const pathToRoot = connectionPoint.pathToRoot; const pathToTarget = [...path, ...pathToRoot]; + const body = pathToTarget.map((pathElement, index) => { + console.log('path to root', pathToRoot.length); + console.log('body count', (pathToTarget.length - pathToRoot.length) - 1); if (index === ((pathToTarget.length - pathToRoot.length) - 1)) { - if (index === (pathToTarget.length - 1)) { + if (index === (pathToTarget.length - 1) && pathToRoot.length != 0) { return ` # connector last ?element_${outerIndex}_${index} ${pathElement.path} ?result . @@ -525,6 +546,23 @@ export class AggregateService { `; } else if (index === 0) { + if (pathToRoot.length === 0) { + return ` + # first path element - form link + <${subject}> a <${pathElement.sourceClassIri}> . + <${subject}> ${pathElement.path} ?result . + ?result a <${pathElement.targetClassIri}> . + + VALUES ?resultP { + ${rdf.typePrefixed} + ${rdfs.labelPrefixed} + } + ?result ?resultP ?resultO . + BIND(?result as ?connectionPointO) + BIND(?result as ?element_1) + + `; + } console.log('pathElement connector is first'); console.log('pathElement', index); return ` @@ -600,6 +638,7 @@ export class AggregateService { ${body} } }`; + console.log('%cquery', 'color: red', query); return [query]; }); @@ -710,234 +749,4 @@ export class AggregateService { } -// sparql template functions -function compositionToCompositionLinksForClassQuery(type: string): string { - const query = ` - ${shacl.sparqlPrefix()} - ${blueprint.sparqlPrefix()} - ${rdfs.sparqlPrefix()} - ${rdf.sparqlPrefix()} - - CONSTRUCT { - ?shape ${shacl.groupPrefixed} ?aggregate . - ?shape ${shacl.targetClassPrefixed} ?type . - - ?outgoingLinks ?linkP ?linkO . - ?propertyShape ?propertyP ?propertyO . - ?path ${shacl.inversePathPrefixed} ?inversePath . - - ?inShape ${shacl.groupPrefixed} ?inAggregate . - ?inShape ${shacl.targetClassPrefixed} ?type. - - ?incomingLinks ?linkP ?linkO . - ?incomingLinks ${rdfs.labelPrefixed} ?inverseLabel . - - } - WHERE { - { - # outgoing links - { - SELECT ?outgoingLinks ?aggregate ?shape ?type WHERE { - BIND (<${type}> AS ?type) - ?shape ${shacl.targetClassPrefixed} ?type . - ?shape ${shacl.groupPrefixed} ?aggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . - ?outgoingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . - } - } - VALUES ?linkP { - ${shacl.targetClassPrefixed} - ${shacl.propertyPrefixed} - ${blueprint.targetPrefixed} - ${rdfs.labelPrefixed} - ${rdf.typePrefixed} - } - ?outgoingLinks ?linkP ?linkO . - } UNION { - # outgoing links sh:property - { - SELECT ?outgoingLinks WHERE { - BIND (<${type}> AS ?type) - ?shape ${shacl.targetClassPrefixed} ?type . - ?shape ${shacl.groupPrefixed} ?aggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . - ?outgoingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . - } - } - - ?outgoingLinks ${shacl.propertyPrefixed} ?propertyShape . - - VALUES ?propertyP { - ${shacl.targetClassPrefixed} - ${shacl.pathPrefixed} - ${shacl.classPrefixed} - ${shacl.namePrefixed} - } - ?propertyShape ?propertyP ?propertyO . - OPTIONAL { - ?propertyShape ${shacl.pathPrefixed} ?path . - ?path ${shacl.inversePathPrefixed} ?inversePath . - } - } UNION { - # incoming links - { - SELECT ?incomingLinks ?inAggregate ?inShape ?type WHERE { - BIND (<${type}> AS ?type) - ?inShape ${shacl.targetClassPrefixed} ?type . - ?inShape ${shacl.groupPrefixed} ?inAggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?incomingLinks ${blueprint.targetPrefixed} ?inAggregate . - ?incomingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . - } - } - VALUES ?linkP { - ${shacl.targetClassPrefixed} - ${shacl.propertyPrefixed} - ${blueprint.targetPrefixed} - ${rdf.typePrefixed} - } - ?incomingLinks ${blueprint.inverseLabelPrefixed} ?inverseLabel . - ?incomingLinks ?linkP ?linkO . - } UNION { - # incoming links sh:property - { - SELECT ?incomingLinks WHERE { - BIND (<${type}> AS ?type) - ?shape ${shacl.targetClassPrefixed} ?type . - ?shape ${shacl.groupPrefixed} ?aggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?incomingLinks ${blueprint.targetPrefixed} ?aggregate . - ?incomingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . - } - } - ?incomingLinks ${shacl.propertyPrefixed} ?propertyShape . - VALUES ?propertyP { - ${shacl.targetClassPrefixed} - ${shacl.pathPrefixed} - ${shacl.classPrefixed} - ${shacl.namePrefixed} - } - ?propertyShape ?propertyP ?propertyO . - OPTIONAL { - ?propertyShape ${shacl.pathPrefixed} ?path . - ?path ${shacl.inversePathPrefixed} ?inversePath . - } - } - } - `; - return query; -} - -function compositionToNodeLinksForClassQuery(type: string): string { - const query = ` - ${shacl.sparqlPrefix()} - ${blueprint.sparqlPrefix()} - ${rdfs.sparqlPrefix()} - ${rdf.sparqlPrefix()} - - CONSTRUCT { - - ?outgoingLinks ?linkP ?linkO . - ?propertyShape ?propertyP ?propertyO . - ?path ${shacl.inversePathPrefixed} ?inversePath . - - ?inShape ${shacl.groupPrefixed} ?inAggregate . - ?inShape ${shacl.targetClassPrefixed} ?type. - - ?incomingLinks ?linkP ?linkO . - ?incomingLinks ${rdfs.labelPrefixed} ?inverseLabel . - - } - WHERE { - { - # outgoing links - if this side is a A - { - SELECT ?outgoingLinks ?aggregate ?shape ?type WHERE { - BIND (<${type}> AS ?type) - ?shape ${shacl.targetClassPrefixed} ?type . - ?shape ${shacl.groupPrefixed} ?aggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . - ?outgoingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . - } - } - VALUES ?linkP { - ${shacl.targetClassPrefixed} - ${shacl.propertyPrefixed} - ${blueprint.targetPrefixed} - ${rdfs.labelPrefixed} - ${rdf.typePrefixed} - } - ?outgoingLinks ?linkP ?linkO . - } UNION { - # outgoing links sh:property - { - SELECT ?outgoingLinks WHERE { - BIND (<${type}> AS ?type) - ?shape ${shacl.targetClassPrefixed} ?type . - ?shape ${shacl.groupPrefixed} ?aggregate . - ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. - ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . - ?outgoingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . - } - } - - ?outgoingLinks ${shacl.propertyPrefixed} ?propertyShape . - - VALUES ?propertyP { - ${shacl.targetClassPrefixed} - ${shacl.pathPrefixed} - ${shacl.classPrefixed} - ${shacl.namePrefixed} - } - ?propertyShape ?propertyP ?propertyO . - OPTIONAL { - ?propertyShape ${shacl.pathPrefixed} ?path . - ?path ${shacl.inversePathPrefixed} ?inversePath . - } - } UNION { - # incoming links - { - SELECT ?incomingLinks ?type WHERE { - BIND (<${type}> AS ?type) - ?incomingLinks ${blueprint.targetPrefixed} ?type . - ?incomingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . - } - } - VALUES ?linkP { - ${shacl.targetClassPrefixed} - ${shacl.propertyPrefixed} - ${blueprint.targetPrefixed} - ${rdf.typePrefixed} - } - ?incomingLinks ${blueprint.inverseLabelPrefixed} ?inverseLabel . - ?incomingLinks ?linkP ?linkO . - } UNION { - # incoming links sh:property - { - SELECT ?incomingLinks WHERE { - BIND (<${type}> AS ?type) - ?incomingLinks ${blueprint.targetPrefixed} ?type . - ?incomingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . - } - } - ?incomingLinks ${shacl.propertyPrefixed} ?propertyShape . - VALUES ?propertyP { - ${shacl.targetClassPrefixed} - ${shacl.pathPrefixed} - ${shacl.classPrefixed} - ${shacl.namePrefixed} - } - ?propertyShape ?propertyP ?propertyO . - OPTIONAL { - ?propertyShape ${shacl.pathPrefixed} ?path . - ?path ${shacl.inversePathPrefixed} ?inversePath . - } - } - } - `; - return query; -} diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts index fcc7cfb..407a508 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/composition/composition-to-node-link.ts @@ -109,7 +109,6 @@ export class CompositionToNodeLink extends ClownfaceObject implements ICompositi const shClass = p.out(shacl.classNamedNode).values[0]; const cfPath = p.out(shacl.pathNamedNode).toArray()[0]; const pathFactory = new OutgoingPathFactory(); - debugger; const path = pathFactory.createPath(cfPath); return [new PathDefinition(targetClass, shClass, path.toPathFragments())]; diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts b/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts index c7a1a5d..42d12c0 100644 --- a/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts +++ b/projects/blueprint/src/app/core/service/graph/aggregate/model/path-definition.ts @@ -19,7 +19,7 @@ export class PathDefinition { } get path(): string { - return this.#pathFragments.join(' '); + return this.#pathFragments.join('/'); } /** diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-composition-links-for-class.query.ts b/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-composition-links-for-class.query.ts new file mode 100644 index 0000000..22d50c8 --- /dev/null +++ b/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-composition-links-for-class.query.ts @@ -0,0 +1,141 @@ +import { shacl, blueprint, rdfs, rdf } from "@blueprint/ontology"; + +export function compositionToCompositionLinksForClassQuery(type: string): string { + const query = ` + ${shacl.sparqlPrefix()} + ${blueprint.sparqlPrefix()} + ${rdfs.sparqlPrefix()} + ${rdf.sparqlPrefix()} + + CONSTRUCT { + ?shape ${shacl.groupPrefixed} ?aggregate . + ?shape ${shacl.targetClassPrefixed} ?type . + + ?outgoingLinks ?linkP ?linkO . + ?propertyShape ?propertyP ?propertyO . + ?path ${shacl.inversePathPrefixed} ?inversePath . + + ?inShape ${shacl.groupPrefixed} ?inAggregate . + ?inShape ${shacl.targetClassPrefixed} ?type. + + ?incomingLinks ?linkP ?linkO . + ?incomingLinks ${rdfs.labelPrefixed} ?inverseLabel . + + ?listRest rdf:first ?head . + ?head ?pHead ?oHead . + ?listRest rdf:rest ?tail . + } + WHERE { + { + # outgoing links + { + SELECT ?outgoingLinks ?aggregate ?shape ?type WHERE { + BIND (<${type}> AS ?type) + ?shape ${shacl.targetClassPrefixed} ?type . + ?shape ${shacl.groupPrefixed} ?aggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . + ?outgoingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . + } + } + VALUES ?linkP { + ${shacl.targetClassPrefixed} + ${shacl.propertyPrefixed} + ${blueprint.targetPrefixed} + ${rdfs.labelPrefixed} + ${rdf.typePrefixed} + } + ?outgoingLinks ?linkP ?linkO . + } UNION { + # outgoing links sh:property + { + SELECT ?outgoingLinks WHERE { + BIND (<${type}> AS ?type) + ?shape ${shacl.targetClassPrefixed} ?type . + ?shape ${shacl.groupPrefixed} ?aggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . + ?outgoingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . + } + } + + ?outgoingLinks ${shacl.propertyPrefixed} ?propertyShape . + + VALUES ?propertyP { + ${shacl.targetClassPrefixed} + ${shacl.pathPrefixed} + ${shacl.classPrefixed} + ${shacl.namePrefixed} + } + ?propertyShape ?propertyP ?propertyO . + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path ${shacl.inversePathPrefixed} ?inversePath . + } + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path rdf:rest* ?listRest . + ?listRest rdf:first ?head . + OPTIONAL { + ?head ?pHead ?oHead . + } + ?listRest rdf:rest ?tail . + } + } UNION { + # incoming links + { + SELECT ?incomingLinks ?inAggregate ?inShape ?type WHERE { + BIND (<${type}> AS ?type) + ?inShape ${shacl.targetClassPrefixed} ?type . + ?inShape ${shacl.groupPrefixed} ?inAggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?incomingLinks ${blueprint.targetPrefixed} ?inAggregate . + ?incomingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . + } + } + VALUES ?linkP { + ${shacl.targetClassPrefixed} + ${shacl.propertyPrefixed} + ${blueprint.targetPrefixed} + ${rdf.typePrefixed} + } + ?incomingLinks ${blueprint.inverseLabelPrefixed} ?inverseLabel . + ?incomingLinks ?linkP ?linkO . + } UNION { + # incoming links sh:property + { + SELECT ?incomingLinks WHERE { + BIND (<${type}> AS ?type) + ?shape ${shacl.targetClassPrefixed} ?type . + ?shape ${shacl.groupPrefixed} ?aggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?incomingLinks ${blueprint.targetPrefixed} ?aggregate . + ?incomingLinks a ${blueprint.CompositionToCompositionLinkPrefixed} . + } + } + ?incomingLinks ${shacl.propertyPrefixed} ?propertyShape . + VALUES ?propertyP { + ${shacl.targetClassPrefixed} + ${shacl.pathPrefixed} + ${shacl.classPrefixed} + ${shacl.namePrefixed} + } + ?propertyShape ?propertyP ?propertyO . + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path ${shacl.inversePathPrefixed} ?inversePath . + } + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path rdf:rest* ?listRest . + ?listRest rdf:first ?head . + OPTIONAL { + ?head ?pHead ?oHead . + } + ?listRest rdf:rest ?tail . + } + } + } + `; + return query; +} diff --git a/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-node-links-for-class.query.ts b/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-node-links-for-class.query.ts new file mode 100644 index 0000000..8d14bda --- /dev/null +++ b/projects/blueprint/src/app/core/service/graph/aggregate/query/composition-to-node-links-for-class.query.ts @@ -0,0 +1,134 @@ +import { shacl, blueprint, rdfs, rdf } from "@blueprint/ontology"; + +export function compositionToNodeLinksForClassQuery(type: string): string { + const query = ` + ${shacl.sparqlPrefix()} + ${blueprint.sparqlPrefix()} + ${rdfs.sparqlPrefix()} + ${rdf.sparqlPrefix()} + + CONSTRUCT { + + ?outgoingLinks ?linkP ?linkO . + ?propertyShape ?propertyP ?propertyO . + ?path ${shacl.inversePathPrefixed} ?inversePath . + + ?inShape ${shacl.groupPrefixed} ?inAggregate . + ?inShape ${shacl.targetClassPrefixed} ?type. + + ?incomingLinks ?linkP ?linkO . + ?incomingLinks ${rdfs.labelPrefixed} ?inverseLabel . + + ?listRest rdf:first ?head . + ?head ?pHead ?oHead . + ?listRest rdf:rest ?tail . + } + WHERE { + { + # outgoing links - if this side is a A + { + SELECT ?outgoingLinks ?aggregate ?shape ?type WHERE { + BIND (<${type}> AS ?type) + ?shape ${shacl.targetClassPrefixed} ?type . + ?shape ${shacl.groupPrefixed} ?aggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . + ?outgoingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . + } + } + VALUES ?linkP { + ${shacl.targetClassPrefixed} + ${shacl.propertyPrefixed} + ${blueprint.targetPrefixed} + ${rdfs.labelPrefixed} + ${rdf.typePrefixed} + } + ?outgoingLinks ?linkP ?linkO . + } UNION { + # outgoing links sh:property + { + SELECT ?outgoingLinks WHERE { + BIND (<${type}> AS ?type) + ?shape ${shacl.targetClassPrefixed} ?type . + ?shape ${shacl.groupPrefixed} ?aggregate . + ?aggregate ${rdf.typePrefixed} ${blueprint.CompositionPrefixed}. + ?outgoingLinks ${shacl.targetClassPrefixed} ?aggregate . + ?outgoingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . + } + } + + ?outgoingLinks ${shacl.propertyPrefixed} ?propertyShape . + + VALUES ?propertyP { + ${shacl.targetClassPrefixed} + ${shacl.pathPrefixed} + ${shacl.classPrefixed} + ${shacl.namePrefixed} + } + ?propertyShape ?propertyP ?propertyO . + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path ${shacl.inversePathPrefixed} ?inversePath . + } + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path rdf:rest* ?listRest . + ?listRest rdf:first ?head . + OPTIONAL { + ?head ?pHead ?oHead . + } + ?listRest rdf:rest ?tail . + } + + } UNION { + # incoming links + { + SELECT ?incomingLinks ?type WHERE { + BIND (<${type}> AS ?type) + ?incomingLinks ${blueprint.targetPrefixed} ?type . + ?incomingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . + } + } + VALUES ?linkP { + ${shacl.targetClassPrefixed} + ${shacl.propertyPrefixed} + ${blueprint.targetPrefixed} + ${rdf.typePrefixed} + } + ?incomingLinks ${blueprint.inverseLabelPrefixed} ?inverseLabel . + ?incomingLinks ?linkP ?linkO . + } UNION { + # incoming links sh:property + { + SELECT ?incomingLinks WHERE { + BIND (<${type}> AS ?type) + ?incomingLinks ${blueprint.targetPrefixed} ?type . + ?incomingLinks a ${blueprint.CompositionToNodeLinkPrefixed} . + } + } + ?incomingLinks ${shacl.propertyPrefixed} ?propertyShape . + VALUES ?propertyP { + ${shacl.targetClassPrefixed} + ${shacl.pathPrefixed} + ${shacl.classPrefixed} + ${shacl.namePrefixed} + } + ?propertyShape ?propertyP ?propertyO . + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path ${shacl.inversePathPrefixed} ?inversePath . + } + OPTIONAL { + ?propertyShape ${shacl.pathPrefixed} ?path . + ?path rdf:rest* ?listRest . + ?listRest rdf:first ?head . + OPTIONAL { + ?head ?pHead ?oHead . + } + ?listRest rdf:rest ?tail . + } + } + } + `; + return query; +} diff --git a/projects/blueprint/src/app/features/search/services/search/full-text-search/neptune-full-text-search/neptune-full-text-search.class.ts b/projects/blueprint/src/app/features/search/services/search/full-text-search/neptune-full-text-search/neptune-full-text-search.class.ts index 81d9f80..022efc4 100644 --- a/projects/blueprint/src/app/features/search/services/search/full-text-search/neptune-full-text-search/neptune-full-text-search.class.ts +++ b/projects/blueprint/src/app/features/search/services/search/full-text-search/neptune-full-text-search/neptune-full-text-search.class.ts @@ -20,11 +20,6 @@ export class NeptuneFullTextSearch extends FullTextSearch { } ).join(' || ')})` : ''; - console.log('sparqlFilterClause', sparqlFilterTerm); - /* const fluxClassQueries = metadata - .map(metaShape => fluxClassSubQuery(metaShape)) - .join(' UNION'); - */ const query = searchQueryWithSearchTerm( this._searchContext.searchTerm.toString(), sparqlFilterTerm, @@ -129,7 +124,6 @@ function searchQueryWithSearchTerm(searchTerm: string, filterTerm: string, pageN } `; - console.log('searchQueryWithSearchTerm', query); return query; } diff --git a/projects/blueprint/src/config.json b/projects/blueprint/src/config.json index 41babc3..be93f89 100644 --- a/projects/blueprint/src/config.json +++ b/projects/blueprint/src/config.json @@ -3,7 +3,7 @@ "endpointUrl": "https://ld.flux.zazuko.com/query", "sparqlConsoleUrl": "https://ld.flux.zazuko.com/sparql/#query", "graphExplorerUrl": "https://ld.flux.zazuko.com/graph-explorer/?resource", - "fullTextSearchDialect": "fuseki", + "fullTextSearchDialect": "stardog", "neptune": { "ftsEndpoint": " https://vpc-opensearch-zazuko-blueprint-glbaecqrcqwr5om3z5jj2duuiq.eu-central-1.es.amazonaws.com" }