@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/README.md +14 -1
- package/dist/form-bootstrap.js +99 -73
- package/dist/form-default.js +87 -61
- package/dist/form-material.js +130 -104
- package/dist/loader.d.ts +2 -3
- package/dist/plugins/leaflet.js +6 -6
- package/dist/plugins/mapbox.js +10 -10
- package/package.json +3 -2
- package/src/loader.ts +108 -64
- package/src/util.ts +10 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulb-darmstadt/shacl-form",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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,
|
|
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 ?
|
|
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 ?
|
|
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(
|
|
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
|
|
66
|
-
const
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
}
|