@ulb-darmstadt/shacl-form 1.8.4 → 1.9.1

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.8.4",
3
+ "version": "1.9.1",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
@@ -67,11 +67,12 @@
67
67
  "leaflet.fullscreen": "^4.0.0",
68
68
  "mapbox-gl": "^3.13.0",
69
69
  "n3": "^1.26.0",
70
+ "rdfxml-streaming-parser": "^3.1.0",
70
71
  "shacl-engine": "^1.0.2",
71
72
  "uuid": "^11.1.0"
72
73
  },
73
74
  "peerDependencies": {
74
- "@ro-kit/ui-widgets": "^0.0.32",
75
+ "@ro-kit/ui-widgets": "^0.0.33",
75
76
  "mdui": "^2.1.4"
76
77
  }
77
78
  }
package/src/loader.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { Store, Parser, Quad, Prefixes, NamedNode, DataFactory } from 'n3'
2
- import { toRDF } from 'jsonld'
1
+ import { Store, Quad, NamedNode, DataFactory, Parser, Prefixes } from 'n3'
3
2
  import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
4
3
  import { Config } from './config'
5
4
  import { isURL } from './util'
5
+ import { RdfXmlParser } from 'rdfxml-streaming-parser'
6
+ import { toRDF } from 'jsonld'
7
+
6
8
 
7
9
  // cache external data in module scope (and not in Loader instance) to avoid requesting
8
10
  // them multiple times, e.g. when more than one shacl-form element is on the page
@@ -31,9 +33,9 @@ export class Loader {
31
33
  const promises: Promise<void>[] = []
32
34
  if (!store) {
33
35
  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
+ promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? fetchRDF(this.config.attributes.shapesUrl) : '', store, SHAPES_GRAPH))
35
37
  }
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: '' })))
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))
37
39
  await Promise.all(promises)
38
40
 
39
41
  // if shapes graph is empty, but we have the following triples:
@@ -49,7 +51,7 @@ export class Loader {
49
51
  const url = this.toURL(uri.value)
50
52
  if (url && this.loadedExternalUrls.indexOf(url) < 0) {
51
53
  this.loadedExternalUrls.push(url)
52
- promises.push(this.importRDF(this.fetchRDF(url), store, SHAPES_GRAPH))
54
+ promises.push(this.importRDF(fetchRDF(url), store, SHAPES_GRAPH))
53
55
  }
54
56
  }
55
57
  try {
@@ -62,52 +64,82 @@ export class Loader {
62
64
  this.config.store = store
63
65
  }
64
66
 
65
- async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode, parser?: Parser) {
66
- const p = parser || new Parser()
67
- const parse = async (text: string) => {
67
+ async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode) {
68
+ const parse = async (input: string) => {
68
69
  const dependencies: Promise<void>[] = []
69
70
  await new Promise((resolve, reject) => {
70
- p.parse(text, (error: Error, quad: Quad, prefixes: Prefixes) => {
71
- if (error) {
72
- console.warn('failed parsing graph', graph, error.message)
73
- return reject(error)
74
- }
75
- if (quad) {
76
- store.add(new Quad(quad.subject, quad.predicate, quad.object, graph))
77
- // check if this is an owl:imports predicate and try to load the url
78
- if (this.config.attributes.ignoreOwlImports === null && OWL_PREDICATE_IMPORTS.equals(quad.predicate)) {
79
- const url = this.toURL(quad.object.value)
80
- // import url only once
81
- if (url && this.loadedExternalUrls.indexOf(url) < 0) {
82
- this.loadedExternalUrls.push(url)
83
- // import into separate graph
84
- dependencies.push(this.importRDF(this.fetchRDF(url), store, DataFactory.namedNode(url), parser))
85
- }
71
+ const addQuad = (quad: Quad) => {
72
+ store.add(new Quad(quad.subject, quad.predicate, quad.object, graph))
73
+ // check if this is an owl:imports predicate and try to load the url
74
+ if (this.config.attributes.ignoreOwlImports === null && OWL_PREDICATE_IMPORTS.equals(quad.predicate)) {
75
+ const url = this.toURL(quad.object.value)
76
+ // import url only once
77
+ if (url && this.loadedExternalUrls.indexOf(url) < 0) {
78
+ this.loadedExternalUrls.push(url)
79
+ // import into separate graph
80
+ dependencies.push(this.importRDF(fetchRDF(url), store, DataFactory.namedNode(url)))
86
81
  }
87
- // check if this is an sh:class predicate and invoke class instance provider
88
- if (this.config.classInstanceProvider && SHACL_PREDICATE_CLASS.equals(quad.predicate)) {
89
- const className = quad.object.value
90
- // import class definitions only once
91
- if (this.loadedClasses.indexOf(className) < 0) {
92
- let promise: Promise<string>
93
- // check if class is in module scope cache
94
- if (className in loadedClassesCache) {
95
- promise = loadedClassesCache[className]
96
- } else {
97
- promise = this.config.classInstanceProvider(className)
98
- loadedClassesCache[className] = promise
99
- }
100
- this.loadedClasses.push(className)
101
- dependencies.push(this.importRDF(promise, store, graph, parser))
82
+ }
83
+ // check if this is an sh:class predicate and invoke class instance provider
84
+ if (this.config.classInstanceProvider && SHACL_PREDICATE_CLASS.equals(quad.predicate)) {
85
+ const className = quad.object.value
86
+ // import class definitions only once
87
+ if (this.loadedClasses.indexOf(className) < 0) {
88
+ let promise: Promise<string>
89
+ // check if class is in module scope cache
90
+ if (className in loadedClassesCache) {
91
+ promise = loadedClassesCache[className]
92
+ } else {
93
+ promise = this.config.classInstanceProvider(className)
94
+ loadedClassesCache[className] = promise
102
95
  }
96
+ this.loadedClasses.push(className)
97
+ dependencies.push(this.importRDF(promise, store, graph))
103
98
  }
104
- return
105
99
  }
106
- if (prefixes) {
107
- this.config.registerPrefixes(prefixes)
100
+ }
101
+ const type = guessContentType(input)
102
+ if (type === 'xml') {
103
+ const parser = new RdfXmlParser()
104
+ parser.on('data', (quad: Quad) => {
105
+ addQuad(quad)
106
+ })
107
+ .on('error', (error) => {
108
+ console.warn('failed parsing graph', graph, error.message)
109
+ reject(error)
110
+ })
111
+ .on('prefix', (prefix, iri) => {
112
+ // ignore empty (default) namespace
113
+ if (prefix) {
114
+ this.config.prefixes[prefix] = iri
115
+ }
116
+ })
117
+ .on('end', () => {
118
+ resolve(null)
119
+ })
120
+ parser.write(input)
121
+ parser.end()
122
+ } else {
123
+ try {
124
+ console.log('--- parsing', input, graph)
125
+ new Parser().parse(input, (error: Error, quad: Quad, prefixes: Prefixes) => {
126
+ if (error) {
127
+ console.warn('failed parsing graph', graph, error.message)
128
+ return reject(error)
129
+ }
130
+ if (quad) {
131
+ addQuad(quad)
132
+ return
133
+ }
134
+ if (prefixes) {
135
+ this.config.registerPrefixes(prefixes)
136
+ }
137
+ resolve(null)
138
+ })
139
+ } catch(e) {
140
+ reject(e)
108
141
  }
109
- resolve(null)
110
- })
142
+ }
111
143
  })
112
144
  try {
113
145
  await Promise.allSettled(dependencies)
@@ -120,31 +152,18 @@ export class Loader {
120
152
  input = await input
121
153
  }
122
154
  if (input) {
123
- try {
124
- // check if input is JSON
125
- // @ts-ignore, because result of toRDF is a string and not an object
126
- input = await toRDF(JSON.parse(input), { format: 'application/n-quads' }) as string
127
- } catch(_) {
128
- // NOP, it wasn't JSON
155
+ if (guessContentType(input) === 'json') {
156
+ // convert json to n-quads
157
+ try {
158
+ input = await toRDF(JSON.parse(input), { format: 'application/n-quads' }) as string
159
+ } catch(e) {
160
+ console.error(e)
161
+ }
129
162
  }
130
163
  await parse(input)
131
164
  }
132
165
  }
133
166
 
134
- async fetchRDF(url: string): Promise<string> {
135
- // try to load from cache first
136
- if (url in loadedURLCache) {
137
- return loadedURLCache[url]
138
- }
139
- const promise = fetch(url, {
140
- headers: {
141
- 'Accept': 'text/turtle, application/trig, application/n-triples, application/n-quads, text/n3, application/ld+json'
142
- },
143
- }).then(resp => resp.text())
144
- loadedURLCache[url] = promise
145
- return promise
146
- }
147
-
148
167
  toURL(id: string): string | null {
149
168
  if (isURL(id)) {
150
169
  return id
@@ -165,6 +184,31 @@ export class Loader {
165
184
  }
166
185
  return null
167
186
  }
187
+
188
+ }
189
+
190
+ async function fetchRDF(url: string): Promise<string> {
191
+ // try to load from cache first
192
+ if (url in loadedURLCache) {
193
+ return loadedURLCache[url]
194
+ }
195
+ const promise = fetch(url, {
196
+ headers: {
197
+ 'Accept': 'text/turtle, application/trig, application/n-triples, application/n-quads, text/n3, application/ld+json'
198
+ },
199
+ }).then(resp => resp.text())
200
+ loadedURLCache[url] = promise
201
+ return promise
202
+ }
203
+
204
+ /* Can't rely on HTTP content-type header, since many resources are delivered with text/plain */
205
+ function guessContentType(input: string) {
206
+ if (/^\s*\{/.test(input)) {
207
+ return 'json'
208
+ } else if (/^\s*<\?xml/.test(input)) {
209
+ return 'xml'
210
+ }
211
+ return 'ttl'
168
212
  }
169
213
 
170
214
  export function setSharedShapesGraph(graph: Store) {
package/src/util.ts CHANGED
@@ -104,18 +104,21 @@ export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplat
104
104
  nodes.set(instance.id, { value: instance, label: findLabel(template.config.store.getQuads(instance, null, null, null), template.config.languages), children: [] })
105
105
  }
106
106
 
107
- // record broader/narrower relationships (child -> parent)
107
+ // record broader/narrower/subClassOf hierarchical relationships
108
108
  for (const instance of instances) {
109
109
  for (const parent of template.config.store.getObjects(instance, SKOS_PREDICATE_BROADER, null)) {
110
- childToParent.set(instance.id, parent.id)
111
- if (!nodes.has(parent.id)) {
112
- nodes.set(parent.id, { value: parent, label: findLabel(template.config.store.getQuads(parent, null, null, null), template.config.languages), children: [] })
110
+ if (nodes.has(parent.id)) {
111
+ childToParent.set(instance.id, parent.id)
113
112
  }
114
113
  }
115
114
  for (const child of template.config.store.getObjects(instance, SKOS_PREDICATE_NARROWER, null)) {
116
- childToParent.set(child.id, instance.id)
117
- if (!nodes.has(child.id)) {
118
- nodes.set(child.id, { value: child, label: findLabel(template.config.store.getQuads(child, null, null, null), template.config.languages), children: [] })
115
+ if (nodes.has(child.id)) {
116
+ childToParent.set(child.id, instance.id)
117
+ }
118
+ }
119
+ for (const parent of template.config.store.getObjects(instance, RDFS_PREDICATE_SUBCLASS_OF, null)) {
120
+ if (nodes.has(parent.id)) {
121
+ childToParent.set(instance.id, parent.id)
119
122
  }
120
123
  }
121
124
  }