@ulb-darmstadt/shacl-form 1.7.4 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +13 -3
  2. package/dist/config.d.ts +4 -5
  3. package/dist/constants.d.ts +15 -13
  4. package/dist/constraints.d.ts +2 -2
  5. package/dist/exports.d.ts +2 -1
  6. package/dist/form-bootstrap.d.ts +1 -1
  7. package/dist/form-bootstrap.js +361 -2
  8. package/dist/form-default.d.ts +1 -1
  9. package/dist/form-default.js +350 -2
  10. package/dist/form-material.d.ts +1 -1
  11. package/dist/form-material.js +670 -2
  12. package/dist/form.d.ts +3 -2
  13. package/dist/node-template.d.ts +17 -0
  14. package/dist/node.d.ts +2 -1
  15. package/dist/plugins/leaflet.d.ts +2 -4
  16. package/dist/plugins/leaflet.js +720 -2
  17. package/dist/plugins/mapbox.d.ts +2 -2
  18. package/dist/plugins/mapbox.js +2764 -2
  19. package/dist/property-template.d.ts +1 -1
  20. package/dist/property.d.ts +6 -2
  21. package/dist/theme.d.ts +2 -2
  22. package/dist/themes/default.d.ts +3 -3
  23. package/dist/themes/material.d.ts +2 -3
  24. package/package.json +23 -11
  25. package/src/config.ts +11 -10
  26. package/src/constants.ts +3 -1
  27. package/src/constraints.ts +15 -18
  28. package/src/exports.ts +2 -1
  29. package/src/form.ts +32 -17
  30. package/src/group.ts +1 -1
  31. package/src/loader.ts +12 -13
  32. package/src/node-template.ts +82 -0
  33. package/src/node.ts +40 -38
  34. package/src/plugins/leaflet.ts +2 -2
  35. package/src/plugins/mapbox.ts +4 -4
  36. package/src/property-template.ts +4 -5
  37. package/src/property.ts +154 -56
  38. package/src/serialize.ts +14 -1
  39. package/src/styles.css +8 -10
  40. package/src/theme.ts +5 -5
  41. package/src/themes/bootstrap.ts +1 -1
  42. package/src/themes/default.css +2 -2
  43. package/src/themes/default.ts +12 -3
  44. package/src/themes/material.ts +12 -3
  45. package/src/util.ts +12 -14
  46. package/dist/form-bootstrap.js.LICENSE.txt +0 -69
  47. package/dist/form-default.js.LICENSE.txt +0 -69
  48. package/dist/form-material.js.LICENSE.txt +0 -69
  49. package/dist/plugins/file-upload.js +0 -1
  50. package/dist/plugins/fixed-list.js +0 -1
  51. package/dist/plugins/leaflet.js.LICENSE.txt +0 -4
  52. package/dist/plugins/mapbox.js.LICENSE.txt +0 -10
@@ -35,7 +35,7 @@ export declare class ShaclPropertyTemplate {
35
35
  qualifiedValueShape: Term | undefined;
36
36
  owlImports: NamedNode[];
37
37
  config: Config;
38
- extendedShapes: NamedNode[] | undefined;
38
+ extendedShapes: NamedNode[];
39
39
  constructor(quads: Quad[], parent: ShaclNode, config: Config);
40
40
  merge(quads: Quad[]): ShaclPropertyTemplate;
41
41
  clone(): ShaclPropertyTemplate;
@@ -3,12 +3,16 @@ import { Term } from '@rdfjs/types';
3
3
  import { ShaclNode } from './node';
4
4
  import { Config } from './config';
5
5
  import { ShaclPropertyTemplate } from './property-template';
6
+ import { RokitSelect } from '@ro-kit/ui-widgets';
6
7
  export declare class ShaclProperty extends HTMLElement {
7
8
  template: ShaclPropertyTemplate;
8
- addButton: HTMLElement | undefined;
9
+ addButton: RokitSelect | undefined;
9
10
  constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode);
10
11
  addPropertyInstance(value?: Term): HTMLElement;
11
12
  updateControls(): void;
12
13
  toRDF(graph: Store, subject: NamedNode | BlankNode): void;
14
+ getRdfClassToLinkOrCreate(): NamedNode<string> | undefined;
15
+ isValueValid(value: Term): boolean;
16
+ createAddButton(): RokitSelect;
13
17
  }
14
- export declare function createPropertyInstance(template: ShaclPropertyTemplate, value?: Term, forceRemovable?: boolean): HTMLElement;
18
+ export declare function createPropertyInstance(template: ShaclPropertyTemplate, value?: Term, forceRemovable?: boolean, linked?: boolean): HTMLElement;
package/dist/theme.d.ts CHANGED
@@ -17,7 +17,7 @@ export type InputListEntry = {
17
17
  export declare abstract class Theme {
18
18
  stylesheet: CSSStyleSheet;
19
19
  constructor(styles?: string);
20
- apply(root: HTMLFormElement): void;
20
+ apply(_: HTMLFormElement): void;
21
21
  createViewer(label: string, value: Term, template: ShaclPropertyTemplate): HTMLElement;
22
22
  abstract createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
23
23
  abstract createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
@@ -28,4 +28,4 @@ export declare abstract class Theme {
28
28
  abstract createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
29
29
  abstract createButton(label: string, primary: boolean): HTMLElement;
30
30
  }
31
- export declare function fieldFactory(template: ShaclPropertyTemplate, value: Term | null): HTMLElement;
31
+ export declare function fieldFactory(template: ShaclPropertyTemplate, value: Term | null, editable: boolean): HTMLElement;
@@ -1,6 +1,6 @@
1
1
  import { Term } from '@rdfjs/types';
2
- import { ShaclPropertyTemplate } from "../property-template";
3
- import { Editor, InputListEntry, Theme } from "../theme";
2
+ import { ShaclPropertyTemplate } from '../property-template';
3
+ import { Editor, InputListEntry, Theme } from '../theme';
4
4
  export declare class DefaultTheme extends Theme {
5
5
  idCtr: number;
6
6
  constructor(overiddenCss?: string);
@@ -12,5 +12,5 @@ export declare class DefaultTheme extends Theme {
12
12
  createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
13
13
  createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
14
14
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
15
- createButton(label: string, primary: boolean): HTMLElement;
15
+ createButton(label: string, _: boolean): HTMLElement;
16
16
  }
@@ -1,7 +1,6 @@
1
1
  import { ShaclPropertyTemplate } from '../property-template';
2
2
  import { Term } from '@rdfjs/types';
3
- import { Theme } from '../theme';
4
- import { InputListEntry, Editor } from '../theme';
3
+ import { Theme, InputListEntry, Editor } from '../theme';
5
4
  export declare class MaterialTheme extends Theme {
6
5
  constructor();
7
6
  createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement;
@@ -9,7 +8,7 @@ export declare class MaterialTheme extends Theme {
9
8
  createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
10
9
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
11
10
  createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
12
- createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
11
+ createDateEditor(_: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
13
12
  createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
14
13
  createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
15
14
  createButton(label: string, primary: boolean): HTMLElement;
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
7
7
  "types": "dist/form-default.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/form-default.js",
12
+ "types": "./dist/form-default.d.ts"
13
+ }
14
+ },
8
15
  "files": [
9
16
  "dist",
10
17
  "src"
@@ -26,10 +33,15 @@
26
33
  "viewer"
27
34
  ],
28
35
  "scripts": {
29
- "build": "npm run clean && webpack --mode production",
30
- "clean": "rimraf dist",
36
+ "dev": "vite serve ./demo --host",
37
+ "build": "tsc && npm run build-default && npm run build-bootstrap && npm run build-material && npm run build-leaflet && npm run build-mapbox",
38
+ "preview": "vite preview",
31
39
  "test": "echo \"no tests specified\" && exit 0",
32
- "serve": "webpack serve --mode development"
40
+ "build-default": "vite build -c ./vite.form-default.config.ts",
41
+ "build-bootstrap": "vite build -c ./vite.form-bootstrap.config.ts",
42
+ "build-material": "vite build -c ./vite.form-material.config.ts",
43
+ "build-leaflet": "vite build -c ./vite.plugin-leaflet.config.ts",
44
+ "build-mapbox": "vite build -c ./vite.plugin-mapbox.config.ts"
33
45
  },
34
46
  "devDependencies": {
35
47
  "@types/jsonld": "^1.5.15",
@@ -39,13 +51,10 @@
39
51
  "@types/mapbox__mapbox-gl-draw": "^1.4.9",
40
52
  "@types/n3": "^1.26.0",
41
53
  "@types/uuid": "^10.0.0",
42
- "raw-loader": "^4.0.2",
43
- "rimraf": "^6.0.1",
44
- "ts-loader": "^9.5.2",
54
+ "rollup-plugin-peer-deps-external": "^2.2.4",
45
55
  "typescript": "^5.8.3",
46
- "webpack": ">=5.99.9",
47
- "webpack-cli": "^6.0.1",
48
- "webpack-dev-server": "^5.2.2"
56
+ "vite": "^6.2.5",
57
+ "vite-plugin-dts": "^4.5.3"
49
58
  },
50
59
  "dependencies": {
51
60
  "@mapbox/mapbox-gl-draw": "^1.5.0",
@@ -55,9 +64,12 @@
55
64
  "leaflet-editable": "^1.3.1",
56
65
  "leaflet.fullscreen": "^4.0.0",
57
66
  "mapbox-gl": "^3.13.0",
58
- "mdui": "^2.1.4",
59
67
  "n3": "^1.26.0",
60
68
  "shacl-engine": "^1.0.2",
61
69
  "uuid": "^11.1.0"
70
+ },
71
+ "peerDependencies": {
72
+ "@ro-kit/ui-widgets": "^0.0.27",
73
+ "mdui": "^2.1.4"
62
74
  }
63
75
  }
package/src/config.ts CHANGED
@@ -36,14 +36,13 @@ export class Config {
36
36
  editMode = true
37
37
  languages: string[]
38
38
 
39
- dataGraph = new Store()
40
39
  lists: Record<string, Term[]> = {}
41
40
  groups: Array<string> = []
42
41
  theme: Theme
43
42
  form: HTMLElement
44
43
  renderedNodes = new Set<string>()
45
- valuesGraph: NamedNode | undefined
46
- private _shapesGraph = new Store()
44
+ valuesGraphId: NamedNode | undefined
45
+ private _store = new Store()
47
46
 
48
47
  constructor(theme: Theme, form: HTMLElement) {
49
48
  this.theme = theme
@@ -80,7 +79,9 @@ export class Config {
80
79
  // now prepend preferred language at start of the list of languages
81
80
  this.languages.unshift(atts.language)
82
81
  }
83
- this.valuesGraph = atts.valuesGraph ? DataFactory.namedNode(atts.valuesGraph) : undefined
82
+ if (atts.valuesGraph) {
83
+ this.valuesGraphId = DataFactory.namedNode(atts.valuesGraph)
84
+ }
84
85
  }
85
86
 
86
87
  static dataAttributes(): Array<string> {
@@ -92,15 +93,15 @@ export class Config {
92
93
  })
93
94
  }
94
95
 
95
- get shapesGraph() {
96
- return this._shapesGraph
96
+ get store() {
97
+ return this._store
97
98
  }
98
99
 
99
- set shapesGraph(graph: Store) {
100
- this._shapesGraph = graph
101
- this.lists = graph.extractLists()
100
+ set store(store: Store) {
101
+ this._store = store
102
+ this.lists = store.extractLists()
102
103
  this.groups = []
103
- graph.forSubjects(subject => {
104
+ store.forSubjects(subject => {
104
105
  this.groups.push(subject.id)
105
106
  }, RDF_PREDICATE_TYPE, `${PREFIX_SHACL}PropertyGroup`, null)
106
107
  }
package/src/constants.ts CHANGED
@@ -9,8 +9,10 @@ export const PREFIX_SKOS = 'http://www.w3.org/2004/02/skos/core#'
9
9
  export const PREFIX_OWL = 'http://www.w3.org/2002/07/owl#'
10
10
  export const PREFIX_OA = 'http://www.w3.org/ns/oa#'
11
11
  export const PREFIX_DCTERMS = 'http://purl.org/dc/terms/'
12
+ export const PREFIX_FOAF = 'http://xmlns.com/foaf/0.1/'
12
13
 
13
- export const SHAPES_GRAPH = DataFactory.namedNode('shapes')
14
+ export const SHAPES_GRAPH = DataFactory.namedNode('loaded-shapes')
15
+ export const DATA_GRAPH = DataFactory.namedNode('loaded-data')
14
16
 
15
17
  export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
16
18
  export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
@@ -5,7 +5,6 @@ import { ShaclProperty, createPropertyInstance } from "./property"
5
5
  import { Config } from './config'
6
6
  import { PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS, SHACL_PREDICATE_NODE_KIND, SHACL_OBJECT_IRI, SHACL_PREDICATE_PROPERTY } from './constants'
7
7
  import { findLabel, removePrefixes } from './util'
8
- import { ShaclPropertyTemplate } from './property-template'
9
8
  import { Editor, InputListEntry } from './theme'
10
9
 
11
10
 
@@ -21,11 +20,11 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
21
20
  // options can be shacl properties or blank nodes referring to (list of) properties
22
21
  let optionsAreReferencedProperties = false
23
22
  if (options.length) {
24
- optionsAreReferencedProperties = config.shapesGraph.getObjects(options[0] , SHACL_PREDICATE_PROPERTY, null).length > 0
23
+ optionsAreReferencedProperties = config.store.countQuads(options[0], SHACL_PREDICATE_PROPERTY, null, null) > 0
25
24
  }
26
25
  for (let i = 0; i < options.length; i++) {
27
26
  if (optionsAreReferencedProperties) {
28
- const quads = config.shapesGraph.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null)
27
+ const quads = config.store.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null)
29
28
  // option can be single property or list of properties
30
29
  const list: ShaclProperty[] = []
31
30
  let combinedText = ''
@@ -53,8 +52,8 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
53
52
  constraintElement.replaceWith(selectedOptions[0])
54
53
  }
55
54
  for (let i = 1; i < selectedOptions.length; i++) {
56
- lastAddedProperty!.after(selectedOptions[1])
57
- lastAddedProperty = selectedOptions[1]
55
+ lastAddedProperty!.after(selectedOptions[i])
56
+ lastAddedProperty = selectedOptions[i]
58
57
  }
59
58
  }
60
59
  }
@@ -62,7 +61,7 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
62
61
  } else {
63
62
  const values: Quad[][] = []
64
63
  for (let i = 0; i < options.length; i++) {
65
- const quads = config.shapesGraph.getQuads(options[i], null, null, null)
64
+ const quads = config.store.getQuads(options[i], null, null, null)
66
65
  if (quads.length) {
67
66
  values.push(quads)
68
67
  optionElements.push({ label: findLabel(quads, config.languages) || (removePrefixes(quads[0].predicate.value, config.prefixes) + ' = ' + removePrefixes(quads[0].object.value, config.prefixes)), value: i.toString() })
@@ -86,7 +85,7 @@ export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term
86
85
  // value is a literal, try to resolve sh:or/sh:xone by matching on given value datatype
87
86
  const valueType = value.datatype
88
87
  for (const subject of subjects) {
89
- const options = config.shapesGraph.getQuads(subject, null, null, null)
88
+ const options = config.store.getQuads(subject, null, null, null)
90
89
  for (const quad of options) {
91
90
  if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
92
91
  return options
@@ -95,16 +94,15 @@ export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term
95
94
  }
96
95
  } else {
97
96
  // value is a NamedNode or BlankNode, try to resolve sh:or/sh:xone by matching rdf:type of given value with sh:node or sh:class in data graph or shapes graph
98
- const types = config.dataGraph.getObjects(value, RDF_PREDICATE_TYPE, null)
99
- types.push(...config.shapesGraph.getObjects(value, RDF_PREDICATE_TYPE, null))
97
+ const types = config.store.getObjects(value, RDF_PREDICATE_TYPE, null)
100
98
  for (const subject of subjects) {
101
- const options = config.shapesGraph.getQuads(subject, null, null, null)
99
+ const options = config.store.getQuads(subject, null, null, null)
102
100
  for (const quad of options) {
103
101
  if (types.length > 0) {
104
102
  // try to find matching sh:node in sh:or/sh:xone values
105
103
  if (quad.predicate.value === `${PREFIX_SHACL}node`) {
106
104
  for (const type of types) {
107
- if (config.shapesGraph.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
105
+ if (config.store.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
108
106
  return options
109
107
  }
110
108
  }
@@ -130,15 +128,14 @@ export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term
130
128
 
131
129
  export function resolveShaclOrConstraintOnNode(subjects: Term[], value: Term, config: Config): Term[] {
132
130
  for (const subject of subjects) {
133
- let subjectMatches = true
134
- const propertySubjects = config.shapesGraph.getObjects(subject, SHACL_PREDICATE_PROPERTY, null)
131
+ let subjectMatches = false
132
+ const propertySubjects = config.store.getObjects(subject, SHACL_PREDICATE_PROPERTY, null)
135
133
  for (const propertySubject of propertySubjects) {
136
- const paths = config.shapesGraph.getObjects(propertySubject, `${PREFIX_SHACL}path`, null)
134
+ const paths = config.store.getObjects(propertySubject, `${PREFIX_SHACL}path`, null)
137
135
  for (const path of paths) {
138
- const values = config.dataGraph.getObjects(value, path, null)
139
- values.push(...config.shapesGraph.getObjects(value, path, null))
140
- if (!values.length) {
141
- subjectMatches = false
136
+ // this allows partial matches in data or shapes graph on properties
137
+ subjectMatches = config.store.countQuads(value, path, null, null) > 0
138
+ if (subjectMatches) {
142
139
  break
143
140
  }
144
141
  }
package/src/exports.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { Theme, InputListEntry, Editor } from './theme'
1
+ export type { InputListEntry, Editor } from './theme'
2
+ export { Theme } from './theme'
2
3
  export { Loader, setSharedShapesGraph } from './loader'
3
4
  export { Config } from './config'
4
5
  export { Plugin, registerPlugin } from './plugin'
package/src/form.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { ShaclNode } from './node'
2
2
  import { Config } from './config'
3
3
  import { ClassInstanceProvider, Plugin, listPlugins, registerPlugin } from './plugin'
4
- import { Store, NamedNode, DataFactory, Quad } from 'n3'
5
- import { DCTERMS_PREDICATE_CONFORMS_TO, PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
4
+ import { Store, NamedNode, DataFactory, Quad, BlankNode } from 'n3'
5
+ import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS, SHAPES_GRAPH } from './constants'
6
6
  import { Editor, Theme } from './theme'
7
7
  import { serialize } from './serialize'
8
8
  import { Validator } from 'shacl-engine'
@@ -95,9 +95,13 @@ export class ShaclForm extends HTMLElement {
95
95
  })
96
96
  this.form.appendChild(button)
97
97
  }
98
+ // delete bound values from data graph, otherwise validation would be confused
99
+ if (this.config.attributes.valuesSubject) {
100
+ this.removeFromDataGraph(DataFactory.namedNode(this.config.attributes.valuesSubject))
101
+ }
98
102
  await this.validate(true)
99
103
  }
100
- } else if (this.config.shapesGraph.size > 0) {
104
+ } else if (this.config.store.countQuads(null, null, null, SHAPES_GRAPH) > 0) {
101
105
  // raise error only when shapes graph is not empty
102
106
  throw new Error('shacl root node shape not found')
103
107
  }
@@ -154,14 +158,14 @@ export class ShaclForm extends HTMLElement {
154
158
  }
155
159
  }
156
160
 
157
- this.config.shapesGraph.deleteGraph('')
158
- this.shape?.toRDF(this.config.shapesGraph)
161
+ this.config.store.deleteGraph(this.config.valuesGraphId || '')
162
+ this.shape?.toRDF(this.config.store)
159
163
  if (this.shape) {
160
164
  // add node target for validation. this is required in case of missing sh:targetClass in root shape
161
- this.config.shapesGraph.add(new Quad(this.shape.shaclSubject, DataFactory.namedNode(PREFIX_SHACL + 'targetNode'), this.shape.nodeId, undefined))
165
+ this.config.store.add(new Quad(this.shape.shaclSubject, DataFactory.namedNode(PREFIX_SHACL + 'targetNode'), this.shape.nodeId, this.config.valuesGraphId))
162
166
  }
163
167
  try {
164
- const dataset = this.config.shapesGraph
168
+ const dataset = this.config.store
165
169
  const report = await new Validator(dataset, { details: true, factory: DataFactory }).validate({ dataset })
166
170
 
167
171
  for (const result of report.results) {
@@ -172,10 +176,10 @@ export class ShaclForm extends HTMLElement {
172
176
  if (result.path?.length) {
173
177
  const path = result.path[0].predicates[0]
174
178
  // try to find most specific editor elements first
175
- let invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}'] > .editor`)
179
+ let invalidElements = this.form.querySelectorAll(`:scope shacl-node[data-node-id='${focusNode.id}'] > shacl-property > .property-instance[data-path='${path.id}'] > .editor, :scope shacl-node[data-node-id='${focusNode.id}'] > .shacl-group > shacl-property > .property-instance[data-path='${path.id}'] > .editor`)
176
180
  if (invalidElements.length === 0) {
177
181
  // if no editors found, select respective node. this will be the case for node shape violations.
178
- invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}']`)
182
+ invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] > * > [data-path='${path.id}']`)
179
183
  }
180
184
 
181
185
  for (const invalidElement of invalidElements) {
@@ -236,18 +240,18 @@ export class ShaclForm extends HTMLElement {
236
240
  // if data-shape-subject is set, use that
237
241
  if (this.config.attributes.shapeSubject) {
238
242
  rootShapeShaclSubject = DataFactory.namedNode(this.config.attributes.shapeSubject)
239
- if (this.config.shapesGraph.getQuads(rootShapeShaclSubject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
243
+ if (this.config.store.getQuads(rootShapeShaclSubject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
240
244
  console.warn(`shapes graph does not contain requested root shape ${this.config.attributes.shapeSubject}`)
241
245
  return
242
246
  }
243
247
  }
244
248
  else {
245
249
  // if we have a data graph and data-values-subject is set, use shape of that
246
- if (this.config.attributes.valuesSubject && this.config.dataGraph.size > 0) {
250
+ if (this.config.attributes.valuesSubject && this.config.store.countQuads(null, null, null, DATA_GRAPH) > 0) {
247
251
  const rootValueSubject = DataFactory.namedNode(this.config.attributes.valuesSubject)
248
252
  const rootValueSubjectTypes = [
249
- ...this.config.dataGraph.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, null),
250
- ...this.config.dataGraph.getQuads(rootValueSubject, DCTERMS_PREDICATE_CONFORMS_TO, null, null)
253
+ ...this.config.store.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, DATA_GRAPH),
254
+ ...this.config.store.getQuads(rootValueSubject, DCTERMS_PREDICATE_CONFORMS_TO, null, DATA_GRAPH)
251
255
  ]
252
256
  if (rootValueSubjectTypes.length === 0) {
253
257
  console.warn(`value subject '${this.config.attributes.valuesSubject}' has neither ${RDF_PREDICATE_TYPE.id} nor ${DCTERMS_PREDICATE_CONFORMS_TO.id} statement`)
@@ -255,13 +259,13 @@ export class ShaclForm extends HTMLElement {
255
259
  }
256
260
  // if type/conformsTo refers to a node shape, prioritize that over targetClass resolution
257
261
  for (const rootValueSubjectType of rootValueSubjectTypes) {
258
- if (this.config.shapesGraph.getQuads(rootValueSubjectType.object as NamedNode, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length > 0) {
262
+ if (this.config.store.getQuads(rootValueSubjectType.object as NamedNode, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length > 0) {
259
263
  rootShapeShaclSubject = rootValueSubjectType.object as NamedNode
260
264
  break
261
265
  }
262
266
  }
263
267
  if (!rootShapeShaclSubject) {
264
- const rootShapes = this.config.shapesGraph.getQuads(null, SHACL_PREDICATE_TARGET_CLASS, rootValueSubjectTypes[0].object, null)
268
+ const rootShapes = this.config.store.getQuads(null, SHACL_PREDICATE_TARGET_CLASS, rootValueSubjectTypes[0].object, null)
265
269
  if (rootShapes.length === 0) {
266
270
  console.error(`value subject '${this.config.attributes.valuesSubject}' has no shacl shape definition in the shapes graph`)
267
271
  return
@@ -269,7 +273,7 @@ export class ShaclForm extends HTMLElement {
269
273
  if (rootShapes.length > 1) {
270
274
  console.warn(`value subject '${this.config.attributes.valuesSubject}' has multiple shacl shape definitions in the shapes graph, choosing the first found (${rootShapes[0].subject})`)
271
275
  }
272
- if (this.config.shapesGraph.getQuads(rootShapes[0].subject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
276
+ if (this.config.store.getQuads(rootShapes[0].subject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
273
277
  console.error(`value subject '${this.config.attributes.valuesSubject}' references a shape which is not a NodeShape (${rootShapes[0].subject})`)
274
278
  return
275
279
  }
@@ -278,7 +282,7 @@ export class ShaclForm extends HTMLElement {
278
282
  }
279
283
  else {
280
284
  // choose first of all defined root shapes
281
- const rootShapes = this.config.shapesGraph.getQuads(null, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null)
285
+ const rootShapes = this.config.store.getQuads(null, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null)
282
286
  if (rootShapes.length == 0) {
283
287
  console.warn('shapes graph does not contain any root shapes')
284
288
  return
@@ -292,4 +296,15 @@ export class ShaclForm extends HTMLElement {
292
296
  }
293
297
  return rootShapeShaclSubject
294
298
  }
299
+
300
+ private removeFromDataGraph(subject: NamedNode | BlankNode) {
301
+ this.config.attributes.valuesSubject
302
+ for (const quad of this.config.store.getQuads(subject, null, null, DATA_GRAPH)) {
303
+ this.config.store.delete(quad)
304
+ if (quad.object.termType === 'NamedNode' || quad.object.termType === 'BlankNode') {
305
+ // recurse
306
+ this.removeFromDataGraph(quad.object)
307
+ }
308
+ }
309
+ }
295
310
  }
package/src/group.ts CHANGED
@@ -7,7 +7,7 @@ export function createShaclGroup(groupSubject: string, config: Config): HTMLElem
7
7
  group.dataset['subject'] = groupSubject
8
8
  group.classList.add('shacl-group')
9
9
  let name = groupSubject
10
- const quads = config.shapesGraph.getQuads(groupSubject, null, null, null)
10
+ const quads = config.store.getQuads(groupSubject, null, null, null)
11
11
  const label = findObjectValueByPredicate(quads, "label", PREFIX_RDFS, config.languages)
12
12
  if (label) {
13
13
  name = label
package/src/loader.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Store, Parser, Quad, Prefixes, NamedNode, DataFactory } from 'n3'
2
2
  import { toRDF } from 'jsonld'
3
- import { DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
3
+ import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
4
4
  import { Config } from './config'
5
5
  import { isURL } from './util'
6
6
 
@@ -25,31 +25,31 @@ export class Loader {
25
25
  this.loadedExternalUrls = []
26
26
  this.loadedClasses = []
27
27
 
28
- let shapesStore = sharedShapesGraph
29
- const valuesStore = new Store()
28
+ let store = sharedShapesGraph
30
29
  this.config.prefixes = {}
31
30
 
32
- const promises = [ this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ? this.fetchRDF(this.config.attributes.valuesUrl) : '', valuesStore, undefined, new Parser({ blankNodePrefix: '' })) ]
33
- if (!shapesStore) {
34
- shapesStore = new Store()
35
- promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '', shapesStore, SHAPES_GRAPH))
31
+ const promises: Promise<void>[] = []
32
+ if (!store) {
33
+ store = new Store()
34
+ promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '', store, SHAPES_GRAPH))
36
35
  }
36
+ promises.push(this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ? this.fetchRDF(this.config.attributes.valuesUrl) : '', store, DATA_GRAPH, new Parser({ blankNodePrefix: '' })))
37
37
  await Promise.all(promises)
38
38
 
39
39
  // if shapes graph is empty, but we have the following triples:
40
40
  // <valueSubject> a <uri> or <valueSubject> dcterms:conformsTo <uri>
41
41
  // then try to load the referenced object into the shapes graph
42
- if (!sharedShapesGraph && shapesStore?.size == 0 && this.config.attributes.valuesSubject) {
42
+ if (!sharedShapesGraph && store?.size == 0 && this.config.attributes.valuesSubject) {
43
43
  const shapeCandidates = [
44
- ...valuesStore.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, null),
45
- ...valuesStore.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, null)
44
+ ...store.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, DATA_GRAPH),
45
+ ...store.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, DATA_GRAPH)
46
46
  ]
47
47
  const promises: Promise<void>[] = []
48
48
  for (const uri of shapeCandidates) {
49
49
  const url = this.toURL(uri.value)
50
50
  if (url && this.loadedExternalUrls.indexOf(url) < 0) {
51
51
  this.loadedExternalUrls.push(url)
52
- promises.push(this.importRDF(this.fetchRDF(url), shapesStore, SHAPES_GRAPH))
52
+ promises.push(this.importRDF(this.fetchRDF(url), store, SHAPES_GRAPH))
53
53
  }
54
54
  }
55
55
  try {
@@ -59,8 +59,7 @@ export class Loader {
59
59
  }
60
60
  }
61
61
 
62
- this.config.shapesGraph = shapesStore
63
- this.config.dataGraph = valuesStore
62
+ this.config.store = store
64
63
  }
65
64
 
66
65
  async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode, parser?: Parser) {
@@ -0,0 +1,82 @@
1
+ import { BlankNode, NamedNode, Quad } from "n3"
2
+ import { Config } from "./config"
3
+ import { OWL_PREDICATE_IMPORTS, PREFIX_SHACL } from "./constants";
4
+ import { Term } from "@rdfjs/types";
5
+
6
+ const mappers: Record<string, (template: ShaclNodeTemplate, term: Term) => void> = {
7
+ [`${PREFIX_SHACL}property`]: (template, term) => { template.properties.push(term as (BlankNode | NamedNode)) },
8
+ [`${PREFIX_SHACL}and`]: (template, term) => { template.shaclAnd = term.value },
9
+ [`${PREFIX_SHACL}node`]: (template, term) => { template.node = term as NamedNode },
10
+ [`${PREFIX_SHACL}targetClass`]: (template, term) => { template.targetClass = term as NamedNode },
11
+ [`${PREFIX_SHACL}or`]: (template, term) => {
12
+ const list = template.config.lists[term.value]
13
+ if (list?.length) {
14
+ template.shaclOr = list
15
+ } else {
16
+ console.error('list for sh:or not found:', term.value, 'existing lists:', template.config.lists)
17
+ }
18
+ },
19
+ [`${PREFIX_SHACL}xone`]: (template, term) => {
20
+ const list = template.config.lists[term.value]
21
+ if (list?.length) {
22
+ template.shaclXone = list
23
+ } else {
24
+ console.error('list for sh:xone not found:', term.value, 'existing lists:', template.config.lists)
25
+ }
26
+ },
27
+ [OWL_PREDICATE_IMPORTS.id]: (template, term) => { template.owlImports.push(term as NamedNode) }
28
+ }
29
+
30
+ export class ShaclNodeTemplate {
31
+ properties: (NamedNode | BlankNode)[] = []
32
+ node: NamedNode | undefined
33
+ shaclAnd: string | undefined
34
+ shaclOr: Term[] | undefined
35
+ shaclXone: Term[] | undefined
36
+ targetClass: NamedNode | undefined
37
+
38
+ owlImports: NamedNode[] = []
39
+ config: Config
40
+ extendedShapes: NamedNode[] = []
41
+
42
+ constructor(quads: Quad[], config: Config) {
43
+ this.config = config
44
+ this.merge(quads)
45
+ }
46
+
47
+ merge(quads: Quad[]): ShaclNodeTemplate {
48
+ for (const quad of quads) {
49
+ mappers[quad.predicate.id]?.call(this, this, quad.object)
50
+ }
51
+ // resolve extended shapes
52
+ if (this.node || this.shaclAnd) {
53
+ if (this.node) {
54
+ this.extendedShapes.push(this.node)
55
+ }
56
+ if (this.shaclAnd) {
57
+ const list = this.config.lists[this.shaclAnd]
58
+ if (list?.length) {
59
+ for (const node of list) {
60
+ this.extendedShapes.push(node as NamedNode)
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return this
66
+ }
67
+
68
+ clone(): ShaclNodeTemplate {
69
+ const copy = Object.assign({}, this)
70
+ // arrays are not cloned but referenced, so create them manually
71
+ copy.owlImports = [ ...this.owlImports ]
72
+ if (this.shaclOr) {
73
+ copy.shaclOr = [ ...this.shaclOr ]
74
+ }
75
+ if (this.shaclXone) {
76
+ copy.shaclXone = [ ...this.shaclXone ]
77
+ }
78
+ copy.merge = this.merge.bind(copy)
79
+ copy.clone = this.clone.bind(copy)
80
+ return copy
81
+ }
82
+ }