@ulb-darmstadt/shacl-form 1.9.3 → 1.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.9.3",
3
+ "version": "1.10.0",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
@@ -54,7 +54,7 @@
54
54
  "@types/n3": "^1.26.0",
55
55
  "@types/uuid": "^10.0.0",
56
56
  "happy-dom": "^18.0.1",
57
- "jest": "^29.7.0",
57
+ "jest": "^30.0.5",
58
58
  "rdf-isomorphic": "^2.0.1",
59
59
  "rollup-plugin-peer-deps-external": "^2.2.4",
60
60
  "typescript": "^5.8.3",
@@ -69,15 +69,15 @@
69
69
  "leaflet": "^1.9.4",
70
70
  "leaflet-editable": "^1.3.2",
71
71
  "leaflet.fullscreen": "^4.0.0",
72
- "lit": "^3.3.1",
73
- "mapbox-gl": "^3.13.0",
72
+ "mapbox-gl": "^3.14.0",
74
73
  "n3": "^1.26.0",
75
74
  "rdfxml-streaming-parser": "^3.1.0",
76
75
  "shacl-engine": "^1.0.2",
77
76
  "uuid": "^11.1.0"
78
77
  },
79
78
  "peerDependencies": {
80
- "@ro-kit/ui-widgets": "^0.0.34",
79
+ "lit": "^3.3.1",
80
+ "@ro-kit/ui-widgets": "^0.0.43",
81
81
  "mdui": "^2.1.4"
82
82
  }
83
83
  }
package/src/config.ts CHANGED
@@ -22,6 +22,7 @@ export class ElementAttributes {
22
22
  view: string | null = null
23
23
  language: string | null = null
24
24
  loading: string = 'Loading\u2026'
25
+ proxy: string | null = null
25
26
  ignoreOwlImports: string | null = null
26
27
  collapse: string | null = null
27
28
  submitButton: string | null = null
package/src/constants.ts CHANGED
@@ -24,6 +24,7 @@ export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'Nod
24
24
  export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
25
25
  export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
26
26
  export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
27
+ export const SHACL_PREDICATE_NODE = DataFactory.namedNode(PREFIX_SHACL + 'node')
27
28
  export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
28
29
  export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
29
30
  export const XSD_DATATYPE_STRING = DataFactory.namedNode(PREFIX_XSD + 'string')
package/src/exports.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type { InputListEntry, Editor } from './theme'
2
2
  export { Theme } from './theme'
3
- export { Loader, setSharedShapesGraph } from './loader'
3
+ export { Loader } from './loader'
4
4
  export { Config } from './config'
5
5
  export { Plugin, registerPlugin } from './plugin'
6
6
  export { ShaclPropertyTemplate } from './property-template'
package/src/form.ts CHANGED
@@ -6,7 +6,6 @@ import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, PREFIX_SHACL, RDF_PREDICATE_
6
6
  import { Editor, Theme } from './theme'
7
7
  import { serialize } from './serialize'
8
8
  import { Validator } from 'shacl-engine'
9
- import { setSharedShapesGraph } from './loader'
10
9
  import { RokitCollapsible } from '@ro-kit/ui-widgets'
11
10
 
12
11
  export class ShaclForm extends HTMLElement {
@@ -140,11 +139,6 @@ export class ShaclForm extends HTMLElement {
140
139
  this.initialize()
141
140
  }
142
141
 
143
- public setSharedShapesGraph(graph: Store) {
144
- setSharedShapesGraph(graph)
145
- this.initialize()
146
- }
147
-
148
142
  public setClassInstanceProvider(provider: ClassInstanceProvider) {
149
143
  this.config.classInstanceProvider = provider
150
144
  this.initialize()
package/src/loader.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Store, Quad, NamedNode, DataFactory, StreamParser } from 'n3'
2
- import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
1
+ import { Store, Quad, NamedNode, DataFactory, StreamParser, Term } from 'n3'
2
+ import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_NODE, SHACL_PREDICATE_TARGET_CLASS, SHAPES_GRAPH } from './constants'
3
3
  import { Config } from './config'
4
4
  import { isURL } from './util'
5
5
  import { RdfXmlParser } from 'rdfxml-streaming-parser'
@@ -11,12 +11,10 @@ import { toRDF } from 'jsonld'
11
11
  // that import the same resources
12
12
  const loadedURLCache: Record<string, Promise<string>> = {}
13
13
  const loadedClassesCache: Record<string, Promise<string>> = {}
14
- let sharedShapesGraph: Store | undefined
15
14
 
16
15
  export class Loader {
17
16
  private config: Config
18
17
  private loadedExternalUrls: string[] = []
19
- private loadedClasses: string[] = []
20
18
 
21
19
  constructor(config: Config) {
22
20
  this.config = config
@@ -25,33 +23,35 @@ export class Loader {
25
23
  async loadGraphs() {
26
24
  // clear local caches
27
25
  this.loadedExternalUrls = []
28
- this.loadedClasses = []
29
-
30
- let store = sharedShapesGraph
31
26
  this.config.prefixes = {}
32
27
 
33
28
  const promises: Promise<void>[] = []
34
- if (!store) {
35
- store = new Store()
36
- promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? fetchRDF(this.config.attributes.shapesUrl) : '', store, SHAPES_GRAPH))
37
- }
38
- promises.push(this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ? fetchRDF(this.config.attributes.valuesUrl) : '', store, DATA_GRAPH))
29
+ const store = new Store()
30
+ 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))
31
+ // load data graph
32
+ 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))
39
33
  await Promise.all(promises)
34
+ await this.fetchOwlImports(store)
35
+ await this.fetchClassInstances(store)
40
36
 
41
37
  // if shapes graph is empty, but we have the following triples:
42
38
  // <valueSubject> a <uri> or <valueSubject> dcterms:conformsTo <uri>
43
- // then try to load the referenced object into the shapes graph
44
- if (!sharedShapesGraph && store.countQuads(null, null, null, SHAPES_GRAPH) === 0 && this.config.attributes.valuesSubject) {
45
- const shapeCandidates = [
46
- ...store.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, DATA_GRAPH),
47
- ...store.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, DATA_GRAPH)
48
- ]
39
+ // or if we have data-shape-subject set on the form,
40
+ // then try to load the referenced object(s) into the shapes graph
41
+ if (store.countQuads(null, null, null, SHAPES_GRAPH) === 0) {
42
+ const shapeCandidates = new Set<Term>()
43
+ if (this.config.attributes.valuesSubject) {
44
+ store.forObjects((object) => shapeCandidates.add(object), this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, DATA_GRAPH)
45
+ }
46
+ if (this.config.attributes.shapeSubject) {
47
+ shapeCandidates.add(DataFactory.namedNode(this.config.attributes.shapeSubject))
48
+ }
49
49
  const promises: Promise<void>[] = []
50
50
  for (const uri of shapeCandidates) {
51
51
  const url = this.toURL(uri.value)
52
52
  if (url && this.loadedExternalUrls.indexOf(url) < 0) {
53
53
  this.loadedExternalUrls.push(url)
54
- promises.push(this.importRDF(fetchRDF(url), store, SHAPES_GRAPH))
54
+ promises.push(this.importRDF(this.fetchRDF(url), store, SHAPES_GRAPH))
55
55
  }
56
56
  }
57
57
  try {
@@ -71,33 +71,6 @@ export class Loader {
71
71
  const parser = guessContentType(input) === 'xml' ? new RdfXmlParser() : new StreamParser()
72
72
  parser.on('data', (quad: Quad) => {
73
73
  store.add(new Quad(quad.subject, quad.predicate, quad.object, graph))
74
- // check if this is an owl:imports predicate and try to load the url
75
- if (this.config.attributes.ignoreOwlImports === null && OWL_PREDICATE_IMPORTS.equals(quad.predicate)) {
76
- const url = this.toURL(quad.object.value)
77
- // import url only once
78
- if (url && this.loadedExternalUrls.indexOf(url) < 0) {
79
- this.loadedExternalUrls.push(url)
80
- // import into separate graph
81
- dependencies.push(this.importRDF(fetchRDF(url), store, DataFactory.namedNode(url)))
82
- }
83
- }
84
- // check if this is an sh:class predicate and invoke class instance provider
85
- if (this.config.classInstanceProvider && SHACL_PREDICATE_CLASS.equals(quad.predicate)) {
86
- const className = quad.object.value
87
- // import class definitions only once
88
- if (this.loadedClasses.indexOf(className) < 0) {
89
- let promise: Promise<string>
90
- // check if class is in module scope cache
91
- if (className in loadedClassesCache) {
92
- promise = loadedClassesCache[className]
93
- } else {
94
- promise = this.config.classInstanceProvider(className)
95
- loadedClassesCache[className] = promise
96
- }
97
- this.loadedClasses.push(className)
98
- dependencies.push(this.importRDF(promise, store, graph))
99
- }
100
- }
101
74
  })
102
75
  .on('error', (error) => {
103
76
  console.warn('failed parsing graph', graph, error.message)
@@ -138,6 +111,78 @@ export class Loader {
138
111
  }
139
112
  }
140
113
 
114
+ async fetchRDF(url: string): Promise<string> {
115
+ // try to load from cache first
116
+ if (url in loadedURLCache) {
117
+ console.log('--- cache hit', url)
118
+ return loadedURLCache[url]
119
+ }
120
+ let proxiedURL = url
121
+ // if we have a proxy configured, then load url via proxy
122
+ if (this.config.attributes.proxy) {
123
+ proxiedURL = this.config.attributes.proxy + encodeURIComponent(url)
124
+ }
125
+ const promise = fetch(proxiedURL, {
126
+ headers: {
127
+ 'Accept': 'text/turtle, application/trig, application/n-triples, application/n-quads, text/n3, application/ld+json'
128
+ },
129
+ }).then(resp => resp.text())
130
+ loadedURLCache[url] = promise
131
+ return promise
132
+ }
133
+
134
+ async fetchOwlImports(store: Store) {
135
+ if (this.config.attributes.ignoreOwlImports === null) {
136
+ const urls = new Set<string>()
137
+ // find all triples in all graphs of the form <s> <sh:class> <:className>
138
+ store.forObjects((url) => {
139
+ urls.add(url.value)
140
+ }, null, OWL_PREDICATE_IMPORTS, null)
141
+
142
+ const dependencies: Promise<void>[] = []
143
+ for (const url of urls) {
144
+ const convertedURL = this.toURL(url)
145
+ // import url only once
146
+ if (convertedURL) {
147
+ // import into separate graph
148
+ dependencies.push(this.importRDF(this.fetchRDF(convertedURL), store, DataFactory.namedNode(convertedURL)))
149
+ }
150
+ }
151
+ return Promise.allSettled(dependencies)
152
+ }
153
+ }
154
+
155
+ async fetchClassInstances(store: Store) {
156
+ if (this.config.classInstanceProvider) {
157
+ const classNames = new Set<string>()
158
+ // find all triples in all graphs of the form <s> <sh:class> <:className>
159
+ store.forObjects((clazz) => {
160
+ classNames.add(clazz.value)
161
+ }, null, SHACL_PREDICATE_CLASS, null)
162
+ // find all triples in all graphs of the form <s> <sh:node> <o> and <o> <sh:targetClass> <:className>
163
+ store.forObjects((node) => {
164
+ store.forObjects((clazz) => {
165
+ classNames.add(clazz.value)
166
+ }, node, SHACL_PREDICATE_TARGET_CLASS, null)
167
+ }, null, SHACL_PREDICATE_NODE, null)
168
+
169
+ const dependencies: Promise<void>[] = []
170
+ for (const className of classNames) {
171
+ let promise: Promise<string>
172
+ // check if class is in module scope cache
173
+ if (className in loadedClassesCache) {
174
+ console.log('--- class cache hit', className)
175
+ promise = loadedClassesCache[className]
176
+ } else {
177
+ promise = this.config.classInstanceProvider(className)
178
+ loadedClassesCache[className] = promise
179
+ }
180
+ dependencies.push(this.importRDF(promise, store, SHAPES_GRAPH))
181
+ }
182
+ return Promise.all(dependencies)
183
+ }
184
+ }
185
+
141
186
  toURL(id: string): string | null {
142
187
  if (isURL(id)) {
143
188
  return id
@@ -158,21 +203,6 @@ export class Loader {
158
203
  }
159
204
  return null
160
205
  }
161
-
162
- }
163
-
164
- async function fetchRDF(url: string): Promise<string> {
165
- // try to load from cache first
166
- if (url in loadedURLCache) {
167
- return loadedURLCache[url]
168
- }
169
- const promise = fetch(url, {
170
- headers: {
171
- 'Accept': 'text/turtle, application/trig, application/n-triples, application/n-quads, text/n3, application/ld+json'
172
- },
173
- }).then(resp => resp.text())
174
- loadedURLCache[url] = promise
175
- return promise
176
206
  }
177
207
 
178
208
  /* Can't rely on HTTP content-type header, since many resources are delivered with text/plain */
@@ -184,7 +214,3 @@ function guessContentType(input: string) {
184
214
  }
185
215
  return 'ttl'
186
216
  }
187
-
188
- export function setSharedShapesGraph(graph: Store) {
189
- sharedShapesGraph = graph
190
- }
package/src/node.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BlankNode, DataFactory, NamedNode, Store } from 'n3'
2
2
  import { Term } from '@rdfjs/types'
3
- import { PREFIX_SHACL, RDF_PREDICATE_TYPE, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_PROPERTY } from './constants'
3
+ import { PREFIX_SHACL, RDF_PREDICATE_TYPE, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_PROPERTY, SHACL_PREDICATE_NODE } from './constants'
4
4
  import { ShaclProperty } from './property'
5
5
  import { createShaclGroup } from './group'
6
6
  import { v4 as uuidv4 } from 'uuid'
@@ -97,7 +97,7 @@ export class ShaclNode extends HTMLElement {
97
97
  console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
98
98
  }
99
99
  break;
100
- case `${PREFIX_SHACL}node`:
100
+ case SHACL_PREDICATE_NODE.id:
101
101
  // inheritance via sh:node
102
102
  this.prepend(new ShaclNode(quad.object as NamedNode, config, valueSubject, this))
103
103
  break;
package/src/property.ts CHANGED
@@ -135,9 +135,6 @@ export class ShaclProperty extends HTMLElement {
135
135
  const mayAdd = this.template.maxCount === undefined || instanceCount < this.template.maxCount
136
136
  this.classList.toggle('may-remove', mayRemove)
137
137
  this.classList.toggle('may-add', mayAdd)
138
- if (mayAdd && this.addButton?.input) {
139
- this.addButton.input.updateMinWidth()
140
- }
141
138
  }
142
139
 
143
140
  toRDF(graph: Store, subject: NamedNode | BlankNode) {
@@ -195,6 +192,7 @@ export class ShaclProperty extends HTMLElement {
195
192
  addButton.dense = true
196
193
  addButton.label = "+ " + this.template.label
197
194
  addButton.title = 'Add ' + this.template.label
195
+ addButton.autoGrowLabelWidth = true
198
196
  addButton.classList.add('add-button')
199
197
 
200
198
  // load potential value candidates for linking