@ulb-darmstadt/shacl-form 1.10.3 → 2.0.0-rc1
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 +1 -1
- package/dist/bundle.js +412 -0
- package/dist/constants.d.ts +16 -16
- package/dist/constraints.d.ts +2 -2
- package/dist/form.d.ts +4 -3
- package/dist/index.js +62 -0
- package/dist/plugins/assets/plugin-VN3CfgGe.js +1 -0
- package/dist/plugins/file-upload.js +1 -0
- package/dist/plugins/leaflet.d.ts +3 -1
- package/dist/plugins/leaflet.js +5 -708
- package/dist/plugins/map-util.js +1 -0
- package/dist/{themes/default.d.ts → theme.default.d.ts} +2 -2
- package/package.json +28 -33
- package/dist/form-bootstrap.d.ts +0 -5
- package/dist/form-bootstrap.js +0 -413
- package/dist/form-default.d.ts +0 -5
- package/dist/form-default.js +0 -402
- package/dist/form-material.d.ts +0 -5
- package/dist/form-material.js +0 -722
- package/dist/plugins/fixed-list.d.ts +0 -9
- package/dist/plugins/mapbox.d.ts +0 -19
- package/dist/plugins/mapbox.js +0 -2870
- package/dist/themes/bootstrap.d.ts +0 -10
- package/dist/themes/material.d.ts +0 -15
- package/src/config.ts +0 -110
- package/src/constants.ts +0 -30
- package/src/constraints.ts +0 -149
- package/src/exports.ts +0 -7
- package/src/form-bootstrap.ts +0 -12
- package/src/form-default.ts +0 -12
- package/src/form-material.ts +0 -12
- package/src/form.ts +0 -319
- package/src/globals.d.ts +0 -2
- package/src/group.ts +0 -34
- package/src/loader.ts +0 -187
- package/src/node.ts +0 -192
- package/src/plugin.ts +0 -60
- package/src/plugins/file-upload.ts +0 -26
- package/src/plugins/fixed-list.ts +0 -19
- package/src/plugins/leaflet.ts +0 -196
- package/src/plugins/map-util.ts +0 -41
- package/src/plugins/mapbox.ts +0 -157
- package/src/property-template.ts +0 -151
- package/src/property.ts +0 -309
- package/src/serialize.ts +0 -96
- package/src/shacl-engine.d.ts +0 -2
- package/src/styles.css +0 -49
- package/src/theme.ts +0 -132
- package/src/themes/bootstrap.css +0 -6
- package/src/themes/bootstrap.ts +0 -44
- package/src/themes/default.css +0 -4
- package/src/themes/default.ts +0 -255
- package/src/themes/material.css +0 -14
- package/src/themes/material.ts +0 -250
- package/src/util.ts +0 -275
package/src/themes/material.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { ShaclPropertyTemplate } from '../property-template'
|
|
2
|
-
import { Term } from '@rdfjs/types'
|
|
3
|
-
import { Button, TextField, Checkbox } from 'mdui'
|
|
4
|
-
import { Theme } from '../theme'
|
|
5
|
-
import { InputListEntry, Editor } from '../theme'
|
|
6
|
-
import { Literal, NamedNode } from 'n3'
|
|
7
|
-
import { Term as N3Term } from 'n3'
|
|
8
|
-
import css from './material.css?raw'
|
|
9
|
-
import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants'
|
|
10
|
-
import { RokitSelect } from '@ro-kit/ui-widgets'
|
|
11
|
-
|
|
12
|
-
export class MaterialTheme extends Theme {
|
|
13
|
-
constructor() {
|
|
14
|
-
super(css)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement {
|
|
18
|
-
editor.classList.add('editor')
|
|
19
|
-
if (template?.datatype) {
|
|
20
|
-
// store datatype on editor, this is used for RDF serialization
|
|
21
|
-
editor.shaclDatatype = template.datatype
|
|
22
|
-
} else if (value instanceof Literal) {
|
|
23
|
-
editor.shaclDatatype = value.datatype
|
|
24
|
-
}
|
|
25
|
-
if (template?.minCount !== undefined) {
|
|
26
|
-
editor.dataset.minCount = String(template.minCount)
|
|
27
|
-
}
|
|
28
|
-
if (template?.class) {
|
|
29
|
-
editor.dataset.class = template.class.value
|
|
30
|
-
}
|
|
31
|
-
if (template?.nodeKind) {
|
|
32
|
-
editor.dataset.nodeKind = template.nodeKind.value
|
|
33
|
-
} else if (value instanceof NamedNode) {
|
|
34
|
-
editor.dataset.nodeKind = PREFIX_SHACL + 'IRI'
|
|
35
|
-
}
|
|
36
|
-
if (template?.hasValue || template?.readonly) {
|
|
37
|
-
editor.disabled = true
|
|
38
|
-
}
|
|
39
|
-
editor.value = value?.value || template?.defaultValue?.value || ''
|
|
40
|
-
|
|
41
|
-
const placeholder = template?.description ? template.description.value : template?.pattern ? template.pattern : null
|
|
42
|
-
if (placeholder) {
|
|
43
|
-
editor.setAttribute('placeholder', placeholder)
|
|
44
|
-
}
|
|
45
|
-
if (required) {
|
|
46
|
-
editor.setAttribute('required', 'true')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const result = document.createElement('div')
|
|
50
|
-
if (label) {
|
|
51
|
-
const labelElem = document.createElement('label')
|
|
52
|
-
labelElem.htmlFor = editor.id
|
|
53
|
-
labelElem.innerText = label
|
|
54
|
-
result.appendChild(labelElem)
|
|
55
|
-
}
|
|
56
|
-
result.appendChild(editor)
|
|
57
|
-
return result
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
61
|
-
const editor = new TextField()
|
|
62
|
-
editor.variant = 'outlined'
|
|
63
|
-
editor.label = label
|
|
64
|
-
editor.type = 'text'
|
|
65
|
-
if (template.description) {
|
|
66
|
-
editor.helper = template.description.value
|
|
67
|
-
}
|
|
68
|
-
if (template.singleLine === false) {
|
|
69
|
-
editor.rows = 5
|
|
70
|
-
}
|
|
71
|
-
if (template.pattern) {
|
|
72
|
-
editor.pattern = template.pattern
|
|
73
|
-
}
|
|
74
|
-
return this.createDefaultTemplate('', value, required, editor, template)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
78
|
-
const editor = new TextField()
|
|
79
|
-
editor.variant = 'outlined'
|
|
80
|
-
editor.type = 'number'
|
|
81
|
-
editor.label = label
|
|
82
|
-
editor.helper = template.description ?.value || ''
|
|
83
|
-
const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
|
|
84
|
-
const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
|
|
85
|
-
if (min !== undefined) {
|
|
86
|
-
editor.setAttribute('min', String(min))
|
|
87
|
-
}
|
|
88
|
-
if (max !== undefined) {
|
|
89
|
-
editor.setAttribute('max', String(max))
|
|
90
|
-
}
|
|
91
|
-
if (template.datatype?.value !== PREFIX_XSD + 'integer') {
|
|
92
|
-
editor.setAttribute('step', '0.1')
|
|
93
|
-
}
|
|
94
|
-
return this.createDefaultTemplate('', value, required, editor, template)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
|
|
98
|
-
const editor = new RokitSelect()
|
|
99
|
-
editor.clearable = true
|
|
100
|
-
const result = this.createDefaultTemplate(label, null, required, editor, template)
|
|
101
|
-
const ul = document.createElement('ul')
|
|
102
|
-
let isFlatList = true
|
|
103
|
-
|
|
104
|
-
const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
|
|
105
|
-
const li = document.createElement('li')
|
|
106
|
-
if (typeof entry.value === 'string') {
|
|
107
|
-
li.dataset.value = entry.value
|
|
108
|
-
li.innerText = entry.label ? entry.label : entry.value
|
|
109
|
-
} else {
|
|
110
|
-
if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) {
|
|
111
|
-
li.dataset.value = entry.value.value
|
|
112
|
-
} else {
|
|
113
|
-
// this is needed for typed rdf literals
|
|
114
|
-
li.dataset.value = (entry.value as N3Term).id
|
|
115
|
-
}
|
|
116
|
-
li.innerText = entry.label ? entry.label : entry.value.value
|
|
117
|
-
}
|
|
118
|
-
parent.appendChild(li)
|
|
119
|
-
if (entry.children?.length) {
|
|
120
|
-
isFlatList = false
|
|
121
|
-
const ul = document.createElement('ul')
|
|
122
|
-
li.appendChild(ul)
|
|
123
|
-
for (const child of entry.children) {
|
|
124
|
-
appendListEntry(child, ul)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
for (const item of listEntries) {
|
|
130
|
-
appendListEntry(item, ul)
|
|
131
|
-
}
|
|
132
|
-
if (!isFlatList) {
|
|
133
|
-
editor.collapse = true
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
editor.appendChild(ul)
|
|
137
|
-
if (value) {
|
|
138
|
-
editor.value = value.value
|
|
139
|
-
}
|
|
140
|
-
return result
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
144
|
-
const editor = new Checkbox()
|
|
145
|
-
const result = this.createDefaultTemplate('', value, required, editor, template)
|
|
146
|
-
// 'required' on checkboxes forces the user to tick the checkbox, which is not what we want here
|
|
147
|
-
editor.removeAttribute('required')
|
|
148
|
-
if (value instanceof Literal) {
|
|
149
|
-
editor.checked = value.value === 'true'
|
|
150
|
-
}
|
|
151
|
-
editor.innerText = label
|
|
152
|
-
return result
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
createDateEditor(_: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
156
|
-
const editor = new TextField()
|
|
157
|
-
editor.variant = 'outlined'
|
|
158
|
-
editor.helper = template?.description?.value || template?.label || ''
|
|
159
|
-
if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
|
|
160
|
-
editor.type = 'datetime-local'
|
|
161
|
-
// this enables seconds in dateTime input
|
|
162
|
-
editor.setAttribute('step', '1')
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
editor.type = 'date'
|
|
166
|
-
}
|
|
167
|
-
editor.classList.add('pr-0')
|
|
168
|
-
const result = this.createDefaultTemplate('', null, required, editor, template)
|
|
169
|
-
if (value) {
|
|
170
|
-
try {
|
|
171
|
-
let isoDate = new Date(value.value).toISOString()
|
|
172
|
-
if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
|
|
173
|
-
isoDate = isoDate.slice(0, 19)
|
|
174
|
-
} else {
|
|
175
|
-
isoDate = isoDate.slice(0, 10)
|
|
176
|
-
}
|
|
177
|
-
editor.value = isoDate
|
|
178
|
-
} catch(ex) {
|
|
179
|
-
console.error(ex, value)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return result
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
186
|
-
const result = this.createTextEditor(label, value, required, template)
|
|
187
|
-
const editor = result.querySelector(':scope .editor') as Editor
|
|
188
|
-
let langChooser: HTMLSelectElement | HTMLInputElement
|
|
189
|
-
if (template.languageIn?.length) {
|
|
190
|
-
langChooser = document.createElement('select')
|
|
191
|
-
for (const lang of template.languageIn) {
|
|
192
|
-
const option = document.createElement('option')
|
|
193
|
-
option.innerText = lang.value
|
|
194
|
-
langChooser.appendChild(option)
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
langChooser = document.createElement('input')
|
|
198
|
-
langChooser.maxLength = 5 // e.g. en-US
|
|
199
|
-
langChooser.placeholder = 'lang?'
|
|
200
|
-
}
|
|
201
|
-
langChooser.title = 'Language of the text'
|
|
202
|
-
langChooser.classList.add('lang-chooser')
|
|
203
|
-
// if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
|
|
204
|
-
langChooser.addEventListener('change', (ev) => {
|
|
205
|
-
ev.stopPropagation();
|
|
206
|
-
if (editor) {
|
|
207
|
-
editor.dataset.lang = langChooser.value
|
|
208
|
-
editor.dispatchEvent(new Event('change', { bubbles: true }))
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
if (value instanceof Literal) {
|
|
212
|
-
langChooser.value = value.language
|
|
213
|
-
}
|
|
214
|
-
editor.dataset.lang = langChooser.value
|
|
215
|
-
editor.after(langChooser)
|
|
216
|
-
return result
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
|
|
220
|
-
const editor = document.createElement('input')
|
|
221
|
-
editor.type = 'file'
|
|
222
|
-
editor.addEventListener('change', (e) => {
|
|
223
|
-
if (editor.files?.length) {
|
|
224
|
-
e.stopPropagation()
|
|
225
|
-
const reader = new FileReader()
|
|
226
|
-
reader.readAsDataURL(editor.files[0])
|
|
227
|
-
reader.onload = () => {
|
|
228
|
-
(editor as Editor)['binaryData'] = btoa(reader.result as string)
|
|
229
|
-
editor.parentElement?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
(editor as Editor)['binaryData'] = undefined
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
return this.createDefaultTemplate(label, value, required, editor, template)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
createButton(label: string, primary: boolean): HTMLElement {
|
|
239
|
-
let button
|
|
240
|
-
if (primary) {
|
|
241
|
-
button = new Button()
|
|
242
|
-
button.classList.add('primary')
|
|
243
|
-
} else {
|
|
244
|
-
button = new Button()
|
|
245
|
-
button.classList.add('secondary')
|
|
246
|
-
}
|
|
247
|
-
button.innerHTML = label
|
|
248
|
-
return button
|
|
249
|
-
}
|
|
250
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { Literal, NamedNode, Prefixes, Quad, Store } from 'n3'
|
|
2
|
-
import { DATA_GRAPH, PREFIX_FOAF, PREFIX_RDF, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH, SKOS_PREDICATE_BROADER, SKOS_PREDICATE_NARROWER } from './constants'
|
|
3
|
-
import { Term } from '@rdfjs/types'
|
|
4
|
-
import { InputListEntry } from './theme'
|
|
5
|
-
import { ShaclPropertyTemplate } from './property-template'
|
|
6
|
-
import { ShaclNode } from './node'
|
|
7
|
-
|
|
8
|
-
export function findObjectValueByPredicate(quads: Quad[], predicate: string, prefix: string = PREFIX_SHACL, languages?: string[]): string {
|
|
9
|
-
let result = ''
|
|
10
|
-
const object = findObjectByPredicate(quads, predicate, prefix, languages)
|
|
11
|
-
if (object) {
|
|
12
|
-
result = object.value
|
|
13
|
-
}
|
|
14
|
-
return result
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function findObjectByPredicate(quads: Quad[], predicate: string, prefix: string = PREFIX_SHACL, languages?: string[]): Term | undefined {
|
|
18
|
-
let candidate: Term | undefined
|
|
19
|
-
const prefixedPredicate = prefix + predicate
|
|
20
|
-
|
|
21
|
-
if (languages?.length) {
|
|
22
|
-
for (const language of languages) {
|
|
23
|
-
for (const quad of quads) {
|
|
24
|
-
if (quad.predicate.value === prefixedPredicate) {
|
|
25
|
-
if (quad.object.id.endsWith(`@${language}`)) {
|
|
26
|
-
return quad.object
|
|
27
|
-
}
|
|
28
|
-
else if (quad.object.id.indexOf('@') < 0) {
|
|
29
|
-
candidate = quad.object
|
|
30
|
-
} else if (!candidate) {
|
|
31
|
-
candidate = quad.object
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
for (const quad of quads) {
|
|
38
|
-
if (quad.predicate.value === prefixedPredicate) {
|
|
39
|
-
return quad.object
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return candidate
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function focusFirstInputElement(context: HTMLElement) {
|
|
47
|
-
(context.querySelector('.editor') as HTMLElement)?.focus()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function findLabel(quads: Quad[], languages: string[]): string {
|
|
51
|
-
return findObjectValueByPredicate(quads, 'prefLabel', PREFIX_SKOS, languages) ||
|
|
52
|
-
findObjectValueByPredicate(quads, 'label', PREFIX_RDFS, languages) ||
|
|
53
|
-
findObjectValueByPredicate(quads, 'name', PREFIX_FOAF, languages)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[]): InputListEntry[] {
|
|
57
|
-
const entries: InputListEntry[] = []
|
|
58
|
-
for (const subject of subjects) {
|
|
59
|
-
entries.push({ value: subject, label: findLabel(shapesGraph.getQuads(subject, null, null, null), languages), children: [] })
|
|
60
|
-
}
|
|
61
|
-
return entries
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function removePrefixes(id: string, prefixes: Prefixes): string {
|
|
65
|
-
for (const key in prefixes) {
|
|
66
|
-
// need to ignore type check. 'prefix' is a string and not a NamedNode<string> (seems to be a bug in n3 typings)
|
|
67
|
-
// @ts-ignore
|
|
68
|
-
id = id.replace(prefixes[key], '')
|
|
69
|
-
}
|
|
70
|
-
return id
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode | ShaclPropertyTemplate, shapesGraph: Store, instances: Term[], alreadyCheckedImports = new Set<string>()) {
|
|
74
|
-
for (const owlImport of context.owlImports) {
|
|
75
|
-
if (!alreadyCheckedImports.has(owlImport.id)) {
|
|
76
|
-
alreadyCheckedImports.add(owlImport.id)
|
|
77
|
-
instances.push(...shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, owlImport))
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (context.parent) {
|
|
81
|
-
findClassInstancesFromOwlImports(clazz, context.parent, shapesGraph, instances, alreadyCheckedImports)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate): InputListEntry[] {
|
|
86
|
-
// if template has sh:in, then just use that as class instances
|
|
87
|
-
if (template.shaclIn) {
|
|
88
|
-
const list = template.config.lists[template.shaclIn]
|
|
89
|
-
return createInputListEntries(list?.length ? list : [], template.config.store, template.config.languages)
|
|
90
|
-
} else {
|
|
91
|
-
// find instances in the shapes graph
|
|
92
|
-
const instances = template.config.store.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
|
|
93
|
-
// find instances in the data graph
|
|
94
|
-
instances.push(...template.config.store.getSubjects(RDF_PREDICATE_TYPE, clazz, DATA_GRAPH))
|
|
95
|
-
// find instances in imported taxonomies
|
|
96
|
-
findClassInstancesFromOwlImports(clazz, template, template.config.store, instances)
|
|
97
|
-
|
|
98
|
-
// initialize structures needed for building a class instance hierarchy
|
|
99
|
-
const nodes = new Map<string, InputListEntry>() // URI -> InputListEntry
|
|
100
|
-
const childToParent = new Map<string, string>() // URI -> parentURI
|
|
101
|
-
|
|
102
|
-
// initialize all instances as InputListEntry's with no children
|
|
103
|
-
for (const instance of instances) {
|
|
104
|
-
nodes.set(instance.id, { value: instance, label: findLabel(template.config.store.getQuads(instance, null, null, null), template.config.languages), children: [] })
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// record broader/narrower/subClassOf hierarchical relationships
|
|
108
|
-
for (const instance of instances) {
|
|
109
|
-
for (const parent of template.config.store.getObjects(instance, SKOS_PREDICATE_BROADER, null)) {
|
|
110
|
-
if (nodes.has(parent.id)) {
|
|
111
|
-
childToParent.set(instance.id, parent.id)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
for (const child of template.config.store.getObjects(instance, SKOS_PREDICATE_NARROWER, null)) {
|
|
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)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// build hierarchy by nesting children under parents
|
|
127
|
-
for (const [child, parent] of childToParent.entries()) {
|
|
128
|
-
nodes.get(parent)?.children?.push(nodes.get(child)!)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// find root nodes (no parent relationship)
|
|
132
|
-
const roots: InputListEntry[] = []
|
|
133
|
-
for (const [uri, node] of nodes.entries()) {
|
|
134
|
-
if (!childToParent.has(uri)) {
|
|
135
|
-
roots.push(node)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// add sub class instances
|
|
140
|
-
for (const subClass of template.config.store.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
|
|
141
|
-
roots.push(...findInstancesOf(subClass as NamedNode, template))
|
|
142
|
-
}
|
|
143
|
-
return roots
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function isURL(input: string): boolean {
|
|
148
|
-
let url: URL
|
|
149
|
-
try {
|
|
150
|
-
url = new URL(input)
|
|
151
|
-
} catch (_) {
|
|
152
|
-
return false
|
|
153
|
-
}
|
|
154
|
-
return url.protocol === 'http:' || url.protocol === 'https:'
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function prioritizeByLanguage(languages: string[], text1?: Literal, text2?: Literal): Literal | undefined {
|
|
158
|
-
if (text1 === undefined) {
|
|
159
|
-
return text2
|
|
160
|
-
}
|
|
161
|
-
if (text2 === undefined) {
|
|
162
|
-
return text1
|
|
163
|
-
}
|
|
164
|
-
const index1 = languages.indexOf(text1.language)
|
|
165
|
-
if (index1 < 0) {
|
|
166
|
-
return text2
|
|
167
|
-
}
|
|
168
|
-
const index2 = languages.indexOf(text2.language)
|
|
169
|
-
if (index2 < 0) {
|
|
170
|
-
return text1
|
|
171
|
-
}
|
|
172
|
-
return index2 > index1 ? text1 : text2
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/*
|
|
176
|
-
This code is taken from https://github.com/rdfjs/N3.js/blob/main/src/N3Store.js and adapted to allow rdf:type triples in lists.
|
|
177
|
-
Can be removed as soon as https://github.com/rdfjs/N3.js/issues/546 is fixed.
|
|
178
|
-
*/
|
|
179
|
-
export function extractLists(store: Store, { remove = false, ignoreErrors = false } = {}) {
|
|
180
|
-
const lists: Record<string, Term[]> = {} // has scalar keys so could be a simple Object
|
|
181
|
-
const onError = ignoreErrors ? (() => true) :
|
|
182
|
-
((node: Term, message: string) => { throw new Error(`${node.value} ${message}`) })
|
|
183
|
-
|
|
184
|
-
// Traverse each list from its tail
|
|
185
|
-
const tails = store.getQuads(null, PREFIX_RDF + 'rest', PREFIX_RDF + 'nil', null)
|
|
186
|
-
const toRemove = remove ? [...tails] : []
|
|
187
|
-
tails.forEach(tailQuad => {
|
|
188
|
-
const items = [] // the members found as objects of rdf:first quads
|
|
189
|
-
let malformed = false // signals whether the current list is malformed
|
|
190
|
-
let head // the head of the list (_:b1 in above example)
|
|
191
|
-
let headPos: string // set to subject or object when head is set
|
|
192
|
-
const graph = tailQuad.graph // make sure list is in exactly one graph
|
|
193
|
-
|
|
194
|
-
// Traverse the list from tail to end
|
|
195
|
-
let current: Term | null = tailQuad.subject
|
|
196
|
-
while (current && !malformed) {
|
|
197
|
-
const objectQuads = store.getQuads(null, null, current, null)
|
|
198
|
-
const subjectQuads = store.getQuads(current, null, null, null).filter(quad => !quad.predicate.equals(RDF_PREDICATE_TYPE))
|
|
199
|
-
let quad, first = null, rest = null, parent = null
|
|
200
|
-
|
|
201
|
-
// Find the first and rest of this list node
|
|
202
|
-
for (let i = 0; i < subjectQuads.length && !malformed; i++) {
|
|
203
|
-
quad = subjectQuads[i]
|
|
204
|
-
if (!quad.graph.equals(graph))
|
|
205
|
-
malformed = onError(current, 'not confined to single graph')
|
|
206
|
-
else if (head)
|
|
207
|
-
malformed = onError(current, 'has non-list arcs out')
|
|
208
|
-
|
|
209
|
-
// one rdf:first
|
|
210
|
-
else if (quad.predicate.value === PREFIX_RDF + 'first') {
|
|
211
|
-
if (first)
|
|
212
|
-
malformed = onError(current, 'has multiple rdf:first arcs')
|
|
213
|
-
else
|
|
214
|
-
toRemove.push(first = quad)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// one rdf:rest
|
|
218
|
-
else if (quad.predicate.value === PREFIX_RDF + 'rest') {
|
|
219
|
-
if (rest)
|
|
220
|
-
malformed = onError(current, 'has multiple rdf:rest arcs')
|
|
221
|
-
else
|
|
222
|
-
toRemove.push(rest = quad)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// alien triple
|
|
226
|
-
else if (objectQuads.length)
|
|
227
|
-
malformed = onError(current, 'can\'t be subject and object')
|
|
228
|
-
else {
|
|
229
|
-
head = quad // e.g. { (1 2 3) :p :o }
|
|
230
|
-
headPos = 'subject'
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// { :s :p (1 2) } arrives here with no head
|
|
235
|
-
// { (1 2) :p :o } arrives here with head set to the list.
|
|
236
|
-
for (let i = 0; i < objectQuads.length && !malformed; ++i) {
|
|
237
|
-
quad = objectQuads[i]
|
|
238
|
-
if (head)
|
|
239
|
-
malformed = onError(current, 'can\'t have coreferences')
|
|
240
|
-
// one rdf:rest
|
|
241
|
-
else if (quad.predicate.value === PREFIX_RDF + 'rest') {
|
|
242
|
-
if (parent)
|
|
243
|
-
malformed = onError(current, 'has incoming rdf:rest arcs')
|
|
244
|
-
else
|
|
245
|
-
parent = quad
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
head = quad // e.g. { :s :p (1 2) }
|
|
249
|
-
headPos = 'object'
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Store the list item and continue with parent
|
|
254
|
-
if (!first)
|
|
255
|
-
malformed = onError(current, 'has no list head')
|
|
256
|
-
else
|
|
257
|
-
items.unshift(first.object)
|
|
258
|
-
current = parent && parent.subject
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Don't remove any quads if the list is malformed
|
|
262
|
-
if (malformed)
|
|
263
|
-
remove = false
|
|
264
|
-
// Store the list under the value of its head
|
|
265
|
-
else if (head) {
|
|
266
|
-
// @ts-ignore
|
|
267
|
-
lists[head[headPos].value] = items
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
// Remove list quads if requested
|
|
272
|
-
if (remove)
|
|
273
|
-
store.removeQuads(toRemove)
|
|
274
|
-
return lists
|
|
275
|
-
}
|