@ulb-darmstadt/shacl-form 1.8.4 → 1.9.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/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 +105 -65
- 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.0",
|
|
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,14 +1,17 @@
|
|
|
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
|
|
9
11
|
// that import the same resources
|
|
10
12
|
const loadedURLCache: Record<string, Promise<string>> = {}
|
|
11
13
|
const loadedClassesCache: Record<string, Promise<string>> = {}
|
|
14
|
+
const ttlParser = new Parser()
|
|
12
15
|
let sharedShapesGraph: Store | undefined
|
|
13
16
|
|
|
14
17
|
export class Loader {
|
|
@@ -31,9 +34,9 @@ export class Loader {
|
|
|
31
34
|
const promises: Promise<void>[] = []
|
|
32
35
|
if (!store) {
|
|
33
36
|
store = new Store()
|
|
34
|
-
promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ?
|
|
37
|
+
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
38
|
}
|
|
36
|
-
promises.push(this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ?
|
|
39
|
+
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
40
|
await Promise.all(promises)
|
|
38
41
|
|
|
39
42
|
// if shapes graph is empty, but we have the following triples:
|
|
@@ -49,7 +52,7 @@ export class Loader {
|
|
|
49
52
|
const url = this.toURL(uri.value)
|
|
50
53
|
if (url && this.loadedExternalUrls.indexOf(url) < 0) {
|
|
51
54
|
this.loadedExternalUrls.push(url)
|
|
52
|
-
promises.push(this.importRDF(
|
|
55
|
+
promises.push(this.importRDF(fetchRDF(url), store, SHAPES_GRAPH))
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
try {
|
|
@@ -62,52 +65,77 @@ export class Loader {
|
|
|
62
65
|
this.config.store = store
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode
|
|
66
|
-
const
|
|
67
|
-
const parse = async (text: string) => {
|
|
68
|
+
async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode) {
|
|
69
|
+
const parse = async (input: string) => {
|
|
68
70
|
const dependencies: Promise<void>[] = []
|
|
69
71
|
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
|
-
}
|
|
72
|
+
const addQuad = (quad: Quad) => {
|
|
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)))
|
|
86
82
|
}
|
|
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))
|
|
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
|
|
102
96
|
}
|
|
97
|
+
this.loadedClasses.push(className)
|
|
98
|
+
dependencies.push(this.importRDF(promise, store, graph))
|
|
103
99
|
}
|
|
104
|
-
return
|
|
105
100
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
}
|
|
102
|
+
const type = guessContentType(input)
|
|
103
|
+
if (type === 'xml') {
|
|
104
|
+
const parser = new RdfXmlParser()
|
|
105
|
+
parser.on('data', (quad: Quad) => {
|
|
106
|
+
addQuad(quad)
|
|
107
|
+
})
|
|
108
|
+
.on('error', (error) => {
|
|
109
|
+
console.warn('failed parsing graph', graph, error.message)
|
|
110
|
+
reject(error)
|
|
111
|
+
})
|
|
112
|
+
.on('prefix', (prefix, iri) => {
|
|
113
|
+
// ignore empty (default) namespace
|
|
114
|
+
if (prefix) {
|
|
115
|
+
this.config.prefixes[prefix] = iri
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.on('end', () => {
|
|
119
|
+
resolve(null)
|
|
120
|
+
})
|
|
121
|
+
parser.write(input)
|
|
122
|
+
parser.end()
|
|
123
|
+
} else {
|
|
124
|
+
ttlParser.parse(input, (error: Error, quad: Quad, prefixes: Prefixes) => {
|
|
125
|
+
if (error) {
|
|
126
|
+
console.warn('failed parsing graph', graph, error.message)
|
|
127
|
+
return reject(error)
|
|
128
|
+
}
|
|
129
|
+
if (quad) {
|
|
130
|
+
addQuad(quad)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
if (prefixes) {
|
|
134
|
+
this.config.registerPrefixes(prefixes)
|
|
135
|
+
}
|
|
136
|
+
resolve(null)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
111
139
|
})
|
|
112
140
|
try {
|
|
113
141
|
await Promise.allSettled(dependencies)
|
|
@@ -120,31 +148,18 @@ export class Loader {
|
|
|
120
148
|
input = await input
|
|
121
149
|
}
|
|
122
150
|
if (input) {
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
if (guessContentType(input) === 'json') {
|
|
152
|
+
// convert json to n-quads
|
|
153
|
+
try {
|
|
154
|
+
input = await toRDF(JSON.parse(input), { format: 'application/n-quads' }) as string
|
|
155
|
+
} catch(e) {
|
|
156
|
+
console.error(e)
|
|
157
|
+
}
|
|
129
158
|
}
|
|
130
159
|
await parse(input)
|
|
131
160
|
}
|
|
132
161
|
}
|
|
133
162
|
|
|
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
163
|
toURL(id: string): string | null {
|
|
149
164
|
if (isURL(id)) {
|
|
150
165
|
return id
|
|
@@ -165,6 +180,31 @@ export class Loader {
|
|
|
165
180
|
}
|
|
166
181
|
return null
|
|
167
182
|
}
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function fetchRDF(url: string): Promise<string> {
|
|
187
|
+
// try to load from cache first
|
|
188
|
+
if (url in loadedURLCache) {
|
|
189
|
+
return loadedURLCache[url]
|
|
190
|
+
}
|
|
191
|
+
const promise = fetch(url, {
|
|
192
|
+
headers: {
|
|
193
|
+
'Accept': 'text/turtle, application/trig, application/n-triples, application/n-quads, text/n3, application/ld+json'
|
|
194
|
+
},
|
|
195
|
+
}).then(resp => resp.text())
|
|
196
|
+
loadedURLCache[url] = promise
|
|
197
|
+
return promise
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Can't rely on HTTP content-type header, since many resources are delivered with text/plain */
|
|
201
|
+
function guessContentType(input: string) {
|
|
202
|
+
if (/^\s*\{/.test(input)) {
|
|
203
|
+
return 'json'
|
|
204
|
+
} else if (/^\s*<\?xml/.test(input)) {
|
|
205
|
+
return 'xml'
|
|
206
|
+
}
|
|
207
|
+
return 'ttl'
|
|
168
208
|
}
|
|
169
209
|
|
|
170
210
|
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
|
}
|