@ulb-darmstadt/shacl-form 1.10.0 → 1.10.2

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/dist/loader.d.ts CHANGED
@@ -3,11 +3,10 @@ import { Config } from './config';
3
3
  export declare class Loader {
4
4
  private config;
5
5
  private loadedExternalUrls;
6
+ private loadedClasses;
6
7
  constructor(config: Config);
7
8
  loadGraphs(): Promise<void>;
8
9
  importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode): Promise<void>;
9
- fetchRDF(url: string): Promise<string>;
10
- fetchOwlImports(store: Store): Promise<PromiseSettledResult<void>[] | undefined>;
11
- fetchClassInstances(store: Store): Promise<void[] | undefined>;
12
10
  toURL(id: string): string | null;
11
+ fetchRDF(url: string): Promise<string>;
13
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
package/src/config.ts CHANGED
@@ -101,7 +101,7 @@ export class Config {
101
101
 
102
102
  set store(store: Store) {
103
103
  this._store = store
104
- this.lists = extractLists(store)
104
+ this.lists = extractLists(store, { ignoreErrors: true })
105
105
  this.groups = []
106
106
  store.forSubjects(subject => {
107
107
  this.groups.push(subject.id)
package/src/form.ts CHANGED
@@ -44,6 +44,8 @@ export class ShaclForm extends HTMLElement {
44
44
  private initialize() {
45
45
  clearTimeout(this.initDebounceTimeout)
46
46
  this.initDebounceTimeout = setTimeout(async () => {
47
+ // set loading attribute on element so that hosting app can apply special css rules
48
+ this.setAttribute('loading', '')
47
49
  // remove all child elements from form and show loading indicator
48
50
  this.form.replaceChildren(document.createTextNode(this.config.attributes.loading))
49
51
  try {
@@ -116,6 +118,7 @@ export class ShaclForm extends HTMLElement {
116
118
  errorDisplay.innerText = String(e)
117
119
  this.form.replaceChildren(errorDisplay)
118
120
  }
121
+ this.removeAttribute('loading')
119
122
  }, 200)
120
123
  }
121
124
 
@@ -159,8 +162,8 @@ export class ShaclForm extends HTMLElement {
159
162
  }
160
163
 
161
164
  this.config.store.deleteGraph(this.config.valuesGraphId || '')
162
- this.shape?.toRDF(this.config.store)
163
165
  if (this.shape) {
166
+ this.shape.toRDF(this.config.store)
164
167
  // add node target for validation. this is required in case of missing sh:targetClass in root shape
165
168
  this.config.store.add(new Quad(this.shape.shaclSubject, DataFactory.namedNode(PREFIX_SHACL + 'targetNode'), this.shape.nodeId, this.config.valuesGraphId))
166
169
  }
package/src/loader.ts CHANGED
@@ -1,5 +1,5 @@
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'
1
+ import { Store, Quad, NamedNode, DataFactory, StreamParser } from 'n3'
2
+ import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_CLASS, 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'
@@ -15,6 +15,7 @@ const loadedClassesCache: Record<string, Promise<string>> = {}
15
15
  export class Loader {
16
16
  private config: Config
17
17
  private loadedExternalUrls: string[] = []
18
+ private loadedClasses: string[] = []
18
19
 
19
20
  constructor(config: Config) {
20
21
  this.config = config
@@ -23,29 +24,25 @@ export class Loader {
23
24
  async loadGraphs() {
24
25
  // clear local caches
25
26
  this.loadedExternalUrls = []
27
+ this.loadedClasses = []
26
28
  this.config.prefixes = {}
27
29
 
30
+
28
31
  const promises: Promise<void>[] = []
29
- const store = new Store()
32
+ const store = new Store()
30
33
  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
34
  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))
33
35
  await Promise.all(promises)
34
- await this.fetchOwlImports(store)
35
- await this.fetchClassInstances(store)
36
36
 
37
37
  // if shapes graph is empty, but we have the following triples:
38
38
  // <valueSubject> a <uri> or <valueSubject> dcterms:conformsTo <uri>
39
39
  // or if we have data-shape-subject set on the form,
40
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
- }
41
+ if (store.countQuads(null, null, null, SHAPES_GRAPH) === 0 && this.config.attributes.valuesSubject) {
42
+ const shapeCandidates = [
43
+ // ...store.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, DATA_GRAPH),
44
+ ...store.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, DATA_GRAPH)
45
+ ]
49
46
  const promises: Promise<void>[] = []
50
47
  for (const uri of shapeCandidates) {
51
48
  const url = this.toURL(uri.value)
@@ -71,6 +68,33 @@ export class Loader {
71
68
  const parser = guessContentType(input) === 'xml' ? new RdfXmlParser() : new StreamParser()
72
69
  parser.on('data', (quad: Quad) => {
73
70
  store.add(new Quad(quad.subject, quad.predicate, quad.object, graph))
71
+ // check if this is an owl:imports predicate and try to load the url
72
+ if (this.config.attributes.ignoreOwlImports === null && OWL_PREDICATE_IMPORTS.equals(quad.predicate)) {
73
+ const url = this.toURL(quad.object.value)
74
+ // import url only once
75
+ if (url && this.loadedExternalUrls.indexOf(url) < 0) {
76
+ this.loadedExternalUrls.push(url)
77
+ // import into separate graph
78
+ dependencies.push(this.importRDF(this.fetchRDF(url), store, DataFactory.namedNode(url)))
79
+ }
80
+ }
81
+ // check if this is an sh:class predicate and invoke class instance provider
82
+ if (this.config.classInstanceProvider && (SHACL_PREDICATE_CLASS.equals(quad.predicate) || SHACL_PREDICATE_TARGET_CLASS.equals(quad.predicate))) {
83
+ const className = quad.object.value
84
+ // import class definitions only once
85
+ if (this.loadedClasses.indexOf(className) < 0) {
86
+ let promise: Promise<string>
87
+ // check if class is in module scope cache
88
+ if (className in loadedClassesCache) {
89
+ promise = loadedClassesCache[className]
90
+ } else {
91
+ promise = this.config.classInstanceProvider(className)
92
+ loadedClassesCache[className] = promise
93
+ }
94
+ this.loadedClasses.push(className)
95
+ dependencies.push(this.importRDF(promise, store, graph))
96
+ }
97
+ }
74
98
  })
75
99
  .on('error', (error) => {
76
100
  console.warn('failed parsing graph', graph, error.message)
@@ -111,78 +135,6 @@ export class Loader {
111
135
  }
112
136
  }
113
137
 
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
-
186
138
  toURL(id: string): string | null {
187
139
  if (isURL(id)) {
188
140
  return id
@@ -203,6 +155,25 @@ export class Loader {
203
155
  }
204
156
  return null
205
157
  }
158
+
159
+ async fetchRDF(url: string): Promise<string> {
160
+ // try to load from cache first
161
+ if (url in loadedURLCache) {
162
+ return loadedURLCache[url]
163
+ }
164
+ let proxiedURL = url
165
+ // if we have a proxy configured, then load url via proxy
166
+ if (this.config.attributes.proxy) {
167
+ proxiedURL = this.config.attributes.proxy + encodeURIComponent(url)
168
+ }
169
+ const promise = fetch(proxiedURL, {
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
179
  /* Can't rely on HTTP content-type header, since many resources are delivered with text/plain */
@@ -115,17 +115,15 @@ export class ShaclPropertyTemplate {
115
115
  if (!this.label && !this.shaclAnd) {
116
116
  this.label = this.path ? removePrefixes(this.path, this.config.prefixes) : 'unknown'
117
117
  }
118
- // resolve extended shapes
119
- if (this.node || this.shaclAnd) {
120
- if (this.node) {
121
- this.extendedShapes.push(this.node)
122
- }
123
- if (this.shaclAnd) {
124
- const list = this.config.lists[this.shaclAnd]
125
- if (list?.length) {
126
- for (const node of list) {
127
- this.extendedShapes.push(node as NamedNode)
128
- }
118
+ // register extended shapes
119
+ if (this.node) {
120
+ this.extendedShapes.push(this.node)
121
+ }
122
+ if (this.shaclAnd) {
123
+ const list = this.config.lists[this.shaclAnd]
124
+ if (list?.length) {
125
+ for (const node of list) {
126
+ this.extendedShapes.push(node as NamedNode)
129
127
  }
130
128
  }
131
129
  }
package/src/property.ts CHANGED
@@ -19,14 +19,13 @@ export class ShaclProperty extends HTMLElement {
19
19
  constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode) {
20
20
  super()
21
21
  this.template = new ShaclPropertyTemplate(config.store.getQuads(shaclSubject, null, null, null), parent, config)
22
+ this.container = this
22
23
  if (this.template.extendedShapes.length && this.template.config.attributes.collapse !== null && (!this.template.maxCount || this.template.maxCount > 1)) {
23
24
  const collapsible = new RokitCollapsible()
24
25
  collapsible.classList.add('collapsible', 'shacl-group');
25
26
  collapsible.open = config.attributes.collapse === 'open';
26
27
  collapsible.label = this.template.label;
27
28
  this.container = collapsible
28
- } else {
29
- this.container = this
30
29
  }
31
30
 
32
31
  if (this.template.order !== undefined) {
@@ -35,7 +34,6 @@ export class ShaclProperty extends HTMLElement {
35
34
  if (this.template.cssClass) {
36
35
  this.classList.add(this.template.cssClass)
37
36
  }
38
-
39
37
  if (config.editMode && !parent.linked) {
40
38
  this.addButton = this.createAddButton()
41
39
  this.container.appendChild(this.addButton)
package/src/serialize.ts CHANGED
@@ -49,7 +49,9 @@ export function toRDF(editor: Editor): Literal | NamedNode | undefined {
49
49
  let languageOrDatatype: NamedNode<string> | string | undefined = editor.shaclDatatype
50
50
  let value: number | string = editor.value
51
51
  if (value) {
52
- if (editor.dataset.class || editor.dataset.nodeKind === PREFIX_SHACL + 'IRI') {
52
+ if (value.startsWith('<') && value.endsWith('>') && value.indexOf(':') > -1) {
53
+ return DataFactory.namedNode(value.substring(1, value.length - 1))
54
+ } else if (editor.dataset.class || editor.dataset.nodeKind === PREFIX_SHACL + 'IRI') {
53
55
  return DataFactory.namedNode(value)
54
56
  } else if (editor.dataset.link) {
55
57
  return JSON.parse(editor.dataset.link)
@@ -220,8 +220,10 @@ export class DefaultTheme extends Theme {
220
220
  } else {
221
221
  if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) {
222
222
  li.dataset.value = entry.value.value
223
+ } else if (entry.value instanceof NamedNode) {
224
+ li.dataset.value = '<' + entry.value.value + ">"
223
225
  } else {
224
- // this is needed for typed rdf literals
226
+ // this is needed for typed rdf literals e.g. "ex"^^xsd:anyUri
225
227
  li.dataset.value = (entry.value as N3Term).id
226
228
  }
227
229
  li.innerText = entry.label ? entry.label : entry.value.value