@ulb-darmstadt/shacl-form 1.7.3 → 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 -12
  4. package/dist/constraints.d.ts +5 -4
  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 +5 -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 +2 -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 +26 -14
  25. package/src/config.ts +11 -10
  26. package/src/constants.ts +4 -1
  27. package/src/constraints.ts +75 -31
  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 +88 -63
  34. package/src/plugins/leaflet.ts +2 -2
  35. package/src/plugins/mapbox.ts +4 -4
  36. package/src/property-template.ts +19 -8
  37. package/src/property.ts +168 -63
  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
@@ -28,13 +28,14 @@ export declare class ShaclPropertyTemplate {
28
28
  shaclAnd: string | undefined;
29
29
  shaclIn: string | undefined;
30
30
  shaclOr: Term[] | undefined;
31
+ shaclXone: Term[] | undefined;
31
32
  languageIn: Term[] | undefined;
32
33
  datatype: NamedNode | undefined;
33
34
  hasValue: Term | undefined;
34
35
  qualifiedValueShape: Term | undefined;
35
36
  owlImports: NamedNode[];
36
37
  config: Config;
37
- extendedShapes: NamedNode[] | undefined;
38
+ extendedShapes: NamedNode[];
38
39
  constructor(quads: Quad[], parent: ShaclNode, config: Config);
39
40
  merge(quads: Quad[]): ShaclPropertyTemplate;
40
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.3",
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,38 +33,43 @@
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",
36
- "@types/leaflet": "^1.9.18",
48
+ "@types/leaflet": "^1.9.19",
37
49
  "@types/leaflet-editable": "^1.2.6",
38
50
  "@types/leaflet.fullscreen": "^3.0.2",
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",
52
- "bootstrap": "^5.3.6",
61
+ "bootstrap": "^5.3.7",
53
62
  "jsonld": "^8.3.3",
54
63
  "leaflet": "^1.9.4",
55
64
  "leaflet-editable": "^1.3.1",
56
65
  "leaflet.fullscreen": "^4.0.0",
57
- "mapbox-gl": "^3.12.0",
58
- "mdui": "^2.1.4",
66
+ "mapbox-gl": "^3.13.0",
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')
@@ -20,6 +22,7 @@ export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports
20
22
  export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
21
23
  export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
22
24
  export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
25
+ export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
23
26
  export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
24
27
  export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
25
28
  export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
@@ -3,9 +3,8 @@ import { Term } from '@rdfjs/types'
3
3
  import { ShaclNode } from "./node"
4
4
  import { ShaclProperty, createPropertyInstance } from "./property"
5
5
  import { Config } from './config'
6
- import { PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS, SHACL_PREDICATE_NODE_KIND, SHACL_OBJECT_IRI } from './constants'
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
 
@@ -17,25 +16,52 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
17
16
  optionElements.push({ label: '--- please choose ---', value: '' })
18
17
 
19
18
  if (context instanceof ShaclNode) {
20
- const properties: ShaclProperty[] = []
21
- // expect options to be shacl properties
19
+ const properties: ShaclProperty[][] = []
20
+ // options can be shacl properties or blank nodes referring to (list of) properties
21
+ let optionsAreReferencedProperties = false
22
+ if (options.length) {
23
+ optionsAreReferencedProperties = config.store.countQuads(options[0], SHACL_PREDICATE_PROPERTY, null, null) > 0
24
+ }
22
25
  for (let i = 0; i < options.length; i++) {
23
- const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
24
- properties.push(property)
25
- optionElements.push({ label: property.template.label, value: i.toString() })
26
+ if (optionsAreReferencedProperties) {
27
+ const quads = config.store.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null)
28
+ // option can be single property or list of properties
29
+ const list: ShaclProperty[] = []
30
+ let combinedText = ''
31
+ for (const subject of quads) {
32
+ const property = new ShaclProperty(subject as NamedNode | BlankNode, context, config)
33
+ list.push(property)
34
+ combinedText += (combinedText.length > 1 ? ' / ' : '') + property.template.label
35
+ }
36
+ properties.push(list)
37
+ optionElements.push({ label: combinedText, value: i.toString() })
38
+ } else {
39
+ const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
40
+ properties.push([property])
41
+ optionElements.push({ label: property.template.label, value: i.toString() })
42
+ }
26
43
  }
27
44
  const editor = config.theme.createListEditor('Please choose', null, false, optionElements)
28
45
  const select = editor.querySelector('.editor') as Editor
29
46
  select.onchange = () => {
30
47
  if (select.value) {
31
- constraintElement.replaceWith(properties[parseInt(select.value)])
48
+ const selectedOptions = properties[parseInt(select.value)]
49
+ let lastAddedProperty: ShaclProperty
50
+ if (selectedOptions.length) {
51
+ lastAddedProperty = selectedOptions[0]
52
+ constraintElement.replaceWith(selectedOptions[0])
53
+ }
54
+ for (let i = 1; i < selectedOptions.length; i++) {
55
+ lastAddedProperty!.after(selectedOptions[i])
56
+ lastAddedProperty = selectedOptions[i]
57
+ }
32
58
  }
33
59
  }
34
60
  constraintElement.appendChild(editor)
35
61
  } else {
36
62
  const values: Quad[][] = []
37
63
  for (let i = 0; i < options.length; i++) {
38
- const quads = config.shapesGraph.getQuads(options[i], null, null, null)
64
+ const quads = config.store.getQuads(options[i], null, null, null)
39
65
  if (quads.length) {
40
66
  values.push(quads)
41
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() })
@@ -54,53 +80,71 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
54
80
  return constraintElement
55
81
  }
56
82
 
57
- export function resolveShaclOrConstraint(template: ShaclPropertyTemplate, value: Term): ShaclPropertyTemplate {
58
- if (!template.shaclOr) {
59
- console.warn('can\'t resolve sh:or because template has no options', template)
60
- return template
61
- }
83
+ export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term, config: Config): Quad[] {
62
84
  if (value instanceof Literal) {
63
- // value is a literal, try to resolve sh:or by matching on given value datatype
85
+ // value is a literal, try to resolve sh:or/sh:xone by matching on given value datatype
64
86
  const valueType = value.datatype
65
- for (const subject of template.shaclOr) {
66
- const options = template.config.shapesGraph.getQuads(subject, null, null, null)
87
+ for (const subject of subjects) {
88
+ const options = config.store.getQuads(subject, null, null, null)
67
89
  for (const quad of options) {
68
90
  if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
69
- return template.clone().merge(options)
91
+ return options
70
92
  }
71
93
  }
72
94
  }
73
95
  } else {
74
- // value is a NamedNode or BlankNode, try to resolve sh:or by matching rdf:type of given value with sh:node or sh:class in data graph or shapes graph
75
- let types = template.config.dataGraph.getObjects(value, RDF_PREDICATE_TYPE, null)
76
- types.push(...template.config.shapesGraph.getObjects(value, RDF_PREDICATE_TYPE, null))
77
- for (const subject of template.shaclOr) {
78
- const options = template.config.shapesGraph.getQuads(subject, null, null, null)
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
97
+ const types = config.store.getObjects(value, RDF_PREDICATE_TYPE, null)
98
+ for (const subject of subjects) {
99
+ const options = config.store.getQuads(subject, null, null, null)
79
100
  for (const quad of options) {
80
101
  if (types.length > 0) {
81
- // try to find matching sh:node in sh:or values
102
+ // try to find matching sh:node in sh:or/sh:xone values
82
103
  if (quad.predicate.value === `${PREFIX_SHACL}node`) {
83
104
  for (const type of types) {
84
- if (template.config.shapesGraph.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
85
- return template.clone().merge(options)
105
+ if (config.store.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
106
+ return options
86
107
  }
87
108
  }
88
109
  }
89
- // try to find matching sh:class in sh:or values
110
+ // try to find matching sh:class in sh:or/sh:xone values
90
111
  if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) {
91
112
  for (const type of types) {
92
113
  if (quad.object.equals(type)) {
93
- return template.clone().merge(options)
114
+ return options
94
115
  }
95
116
  }
96
117
  }
97
118
  } else if (quad.predicate.equals(SHACL_PREDICATE_NODE_KIND) && quad.object.equals(SHACL_OBJECT_IRI)) {
98
119
  // if sh:nodeKind is sh:IRI, just use that
99
- return template.clone().merge(options)
120
+ return options
121
+ }
122
+ }
123
+ }
124
+ }
125
+ console.error('couldn\'t resolve sh:or/sh:xone on property for value', value)
126
+ return []
127
+ }
128
+
129
+ export function resolveShaclOrConstraintOnNode(subjects: Term[], value: Term, config: Config): Term[] {
130
+ for (const subject of subjects) {
131
+ let subjectMatches = false
132
+ const propertySubjects = config.store.getObjects(subject, SHACL_PREDICATE_PROPERTY, null)
133
+ for (const propertySubject of propertySubjects) {
134
+ const paths = config.store.getObjects(propertySubject, `${PREFIX_SHACL}path`, null)
135
+ for (const path of paths) {
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) {
139
+ break
100
140
  }
101
141
  }
102
142
  }
143
+ if (subjectMatches) {
144
+ return propertySubjects
145
+ }
103
146
  }
104
- console.error('couldn\'t resolve sh:or for value', value)
105
- return template
147
+
148
+ console.error('couldn\'t resolve sh:or/sh:xone on node for value', value)
149
+ return []
106
150
  }
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) {