@ulb-darmstadt/shacl-form 1.6.2 → 1.6.4

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.
@@ -0,0 +1,240 @@
1
+ import { Term } from '@rdfjs/types'
2
+ import { ShaclPropertyTemplate } from "../property-template"
3
+ import { Editor, InputListEntry, Theme } from "../theme"
4
+ import { PREFIX_XSD } from '../constants'
5
+ import { Literal } from 'n3'
6
+ import css from './default.css?raw'
7
+
8
+ export class DefaultTheme extends Theme {
9
+ idCtr = 0
10
+
11
+ constructor(overiddenCss?: string) {
12
+ super(overiddenCss ? overiddenCss : css)
13
+ }
14
+
15
+ createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement {
16
+ editor.id = `e${this.idCtr++}`
17
+ editor.classList.add('editor')
18
+ if (template?.datatype) {
19
+ // store datatype on editor, this is used for RDF serialization
20
+ editor['shaclDatatype'] = template.datatype
21
+ }
22
+ if (template?.minCount !== undefined) {
23
+ editor.dataset.minCount = String(template.minCount)
24
+ }
25
+ if (template?.class) {
26
+ editor.dataset.class = template.class.value
27
+ }
28
+ if (template?.nodeKind) {
29
+ editor.dataset.nodeKind = template.nodeKind.value
30
+ }
31
+ if (template?.hasValue) {
32
+ editor.disabled = true
33
+ }
34
+ editor.value = value?.value || template?.defaultValue?.value || ''
35
+
36
+ const labelElem = document.createElement('label')
37
+ labelElem.htmlFor = editor.id
38
+ labelElem.innerText = label
39
+ if (template?.description) {
40
+ labelElem.setAttribute('title', template.description.value)
41
+ }
42
+
43
+ const placeholder = template?.description ? template.description.value : template?.pattern ? template.pattern : null
44
+ if (placeholder) {
45
+ editor.setAttribute('placeholder', placeholder)
46
+ }
47
+ if (required) {
48
+ editor.setAttribute('required', 'true')
49
+ labelElem.classList.add('required')
50
+ }
51
+
52
+ const result = document.createElement('div')
53
+ result.appendChild(labelElem)
54
+ result.appendChild(editor)
55
+ return result
56
+ }
57
+
58
+ createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
59
+ const editor: Editor = document.createElement('input')
60
+ if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
61
+ editor.type = 'datetime-local'
62
+ // this enables seconds in dateTime input
63
+ editor.setAttribute('step', '1')
64
+ }
65
+ else {
66
+ editor.type = 'date'
67
+ }
68
+ editor.classList.add('pr-0')
69
+ const result = this.createDefaultTemplate(label, null, required, editor, template)
70
+ if (value) {
71
+ try {
72
+ let isoDate = new Date(value.value).toISOString()
73
+ if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
74
+ isoDate = isoDate.slice(0, 19)
75
+ } else {
76
+ isoDate = isoDate.slice(0, 10)
77
+ }
78
+ editor.value = isoDate
79
+ } catch(ex) {
80
+ console.error(ex, value)
81
+ }
82
+ }
83
+ return result
84
+ }
85
+
86
+ createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
87
+ let editor
88
+ if (template.singleLine === false) {
89
+ editor = document.createElement('textarea')
90
+ editor.rows = 5
91
+ }
92
+ else {
93
+ editor = document.createElement('input')
94
+ editor.type = 'text'
95
+ if (template.pattern) {
96
+ editor.pattern = template.pattern
97
+ }
98
+ }
99
+
100
+ if (template.minLength) {
101
+ editor.minLength = template.minLength
102
+ }
103
+ if (template.maxLength) {
104
+ editor.maxLength = template.maxLength
105
+ }
106
+ return this.createDefaultTemplate(label, value, required, editor, template)
107
+ }
108
+
109
+ createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
110
+ const result = this.createTextEditor(label, value, required, template)
111
+ const editor = result.querySelector(':scope .editor') as Editor
112
+ let langChooser: HTMLSelectElement | HTMLInputElement
113
+ if (template.languageIn?.length) {
114
+ langChooser = document.createElement('select')
115
+ for (const lang of template.languageIn) {
116
+ const option = document.createElement('option')
117
+ option.innerText = lang.value
118
+ langChooser.appendChild(option)
119
+ }
120
+ } else {
121
+ langChooser = document.createElement('input')
122
+ langChooser.maxLength = 5 // e.g. en-US
123
+ langChooser.placeholder = 'lang?'
124
+ }
125
+ langChooser.title = 'Language of the text'
126
+ langChooser.classList.add('lang-chooser')
127
+ // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
128
+ langChooser.addEventListener('change', (ev) => {
129
+ ev.stopPropagation();
130
+ if (editor) {
131
+ editor.dataset.lang = langChooser.value
132
+ editor.dispatchEvent(new Event('change', { bubbles: true }))
133
+ }
134
+ })
135
+ if (value instanceof Literal) {
136
+ langChooser.value = value.language
137
+ }
138
+ editor.dataset.lang = langChooser.value
139
+ editor.after(langChooser)
140
+ return result
141
+ }
142
+
143
+ createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
144
+ const editor = document.createElement('input')
145
+ editor.type = 'checkbox'
146
+ editor.classList.add('ml-0')
147
+
148
+ const result = this.createDefaultTemplate(label, null, required, editor, template)
149
+
150
+ // 'required' on checkboxes forces the user to tick the checkbox, which is not what we want here
151
+ editor.removeAttribute('required')
152
+ result.querySelector(':scope label')?.classList.remove('required')
153
+ if (value instanceof Literal) {
154
+ editor.checked = value.value === 'true'
155
+ }
156
+ return result
157
+ }
158
+
159
+ createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
160
+ const editor = document.createElement('input')
161
+ editor.type = 'file'
162
+ editor.addEventListener('change', (e) => {
163
+ if (editor.files?.length) {
164
+ e.stopPropagation()
165
+ const reader = new FileReader()
166
+ reader.readAsDataURL(editor.files[0])
167
+ reader.onload = () => {
168
+ (editor as Editor)['binaryData'] = btoa(reader.result as string)
169
+ editor.parentElement?.dispatchEvent(new Event('change', { bubbles: true }))
170
+ }
171
+ } else {
172
+ (editor as Editor)['binaryData'] = undefined
173
+ }
174
+ })
175
+ return this.createDefaultTemplate(label, value, required, editor, template)
176
+ }
177
+
178
+ createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
179
+ const editor = document.createElement('input')
180
+ editor.type = 'number'
181
+ editor.classList.add('pr-0')
182
+ const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
183
+ const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
184
+ if (min !== undefined) {
185
+ editor.min = String(min)
186
+ }
187
+ if (max !== undefined) {
188
+ editor.max = String(max)
189
+ }
190
+ if (template.datatype?.value !== PREFIX_XSD + 'integer') {
191
+ editor.step = '0.1'
192
+ }
193
+ return this.createDefaultTemplate(label, value, required, editor, template)
194
+ }
195
+
196
+ createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
197
+ const editor = document.createElement('select')
198
+ const result = this.createDefaultTemplate(label, null, required, editor, template)
199
+ let addEmptyOption = true
200
+
201
+ for (const item of listEntries) {
202
+ const option = document.createElement('option')
203
+ const itemValue = (typeof item.value === 'string') ? item.value : item.value.value
204
+ option.innerHTML = item.label ? item.label : itemValue
205
+ option.value = itemValue
206
+ if (item.indent) {
207
+ for (let i = 0; i < item.indent; i++) {
208
+ option.innerHTML = '&#160;&#160;' + option.innerHTML
209
+ }
210
+ }
211
+ if (value && value.value === itemValue) {
212
+ option.selected = true
213
+ }
214
+ if (itemValue === '') {
215
+ addEmptyOption = false
216
+ }
217
+ editor.appendChild(option)
218
+ }
219
+ if (addEmptyOption) {
220
+ // add an empty element
221
+ const emptyOption = document.createElement('option')
222
+ emptyOption.value = ''
223
+ if (!value) {
224
+ emptyOption.selected = true
225
+ }
226
+ editor.prepend(emptyOption)
227
+ }
228
+ if (value) {
229
+ editor.value = value.value
230
+ }
231
+ return result
232
+ }
233
+
234
+ createButton(label: string, primary: boolean): HTMLElement {
235
+ const button = document.createElement('button')
236
+ button.type = 'button'
237
+ button.innerHTML = label
238
+ return button
239
+ }
240
+ }
@@ -0,0 +1,14 @@
1
+ :host {
2
+ --mdui-color-primary-light: var(--mdui-color-primary-light);
3
+ --mdui-color-primary-dark: var(--mdui-color-primary-dark);
4
+ --mdui-color-background-light: var(--mdui-color-background-light);
5
+ }
6
+ /* this prevents hiding the date picker icon and number controls */
7
+ mdui-text-field::part(input) {
8
+ clip-path: none;
9
+ appearance: inherit;
10
+ }
11
+ form.mode-edit { --label-width: 0em; }
12
+ .property-instance { margin-bottom:14px; }
13
+ .mode-edit .property-instance *[required]::part(icon) { align-self: flex-start; padding-top: 0.7em; }
14
+ .mode-edit .property-instance *[required]::part(icon)::before { color: red; content: '\2736'; font-size: 0.6em; }
@@ -0,0 +1,240 @@
1
+ import { ShaclPropertyTemplate } from '../property-template'
2
+ import { Term } from '@rdfjs/types'
3
+ import { Button, TextField, Select, MenuItem, Checkbox } from 'mdui'
4
+ import { Theme } from '../theme'
5
+ import { InputListEntry, Editor } from '../theme'
6
+ import { Literal } from 'n3'
7
+ import css from './material.css?raw'
8
+ import { PREFIX_XSD } from '../constants'
9
+
10
+ export class MaterialTheme extends Theme {
11
+ constructor() {
12
+ super(css)
13
+ }
14
+
15
+ createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement {
16
+ editor.classList.add('editor')
17
+ if (template?.datatype) {
18
+ // store datatype on editor, this is used for RDF serialization
19
+ editor['shaclDatatype'] = template.datatype
20
+ }
21
+ if (template?.minCount !== undefined) {
22
+ editor.dataset.minCount = String(template.minCount)
23
+ }
24
+ if (template?.class) {
25
+ editor.dataset.class = template.class.value
26
+ }
27
+ if (template?.nodeKind) {
28
+ editor.dataset.nodeKind = template.nodeKind.value
29
+ }
30
+ if (template?.hasValue) {
31
+ editor.disabled = true
32
+ }
33
+ editor.value = value?.value || template?.defaultValue?.value || ''
34
+
35
+ const placeholder = template?.description ? template.description.value : template?.pattern ? template.pattern : null
36
+ if (placeholder) {
37
+ editor.setAttribute('placeholder', placeholder)
38
+ }
39
+ if (required) {
40
+ editor.setAttribute('required', 'true')
41
+ }
42
+
43
+ const result = document.createElement('div')
44
+ if (label) {
45
+ const labelElem = document.createElement('label')
46
+ labelElem.htmlFor = editor.id
47
+ labelElem.innerText = label
48
+ result.appendChild(labelElem)
49
+ }
50
+ result.appendChild(editor)
51
+ return result
52
+ }
53
+
54
+ createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
55
+ const editor = new TextField()
56
+ editor.variant = 'outlined'
57
+ editor.label = label
58
+ editor.type = 'text'
59
+ if (template.description) {
60
+ editor.helper = template.description.value
61
+ }
62
+ if (template.singleLine === false) {
63
+ editor.rows = 5
64
+ }
65
+ if (template.pattern) {
66
+ editor.pattern = template.pattern
67
+ }
68
+ return this.createDefaultTemplate('', value, required, editor, template)
69
+ }
70
+
71
+ createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
72
+ const editor = new TextField()
73
+ editor.variant = 'outlined'
74
+ editor.type = 'number'
75
+ editor.label = label
76
+ editor.helper = template.description ?.value || ''
77
+ const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
78
+ const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
79
+ if (min !== undefined) {
80
+ editor.setAttribute('min', String(min))
81
+ }
82
+ if (max !== undefined) {
83
+ editor.setAttribute('max', String(max))
84
+ }
85
+ if (template.datatype?.value !== PREFIX_XSD + 'integer') {
86
+ editor.setAttribute('step', '0.1')
87
+ }
88
+ return this.createDefaultTemplate('', value, required, editor, template)
89
+ }
90
+
91
+ createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
92
+ const editor = new Select()
93
+ editor.variant = 'outlined'
94
+ editor.label = label
95
+ editor.helper = template?.description?.value
96
+ editor.clearable = true
97
+ // @ts-ignore
98
+ const result = this.createDefaultTemplate('', null, required, editor, template)
99
+ let addEmptyOption = true
100
+
101
+ for (const item of listEntries) {
102
+ const option = new MenuItem()
103
+ const itemValue = (typeof item.value === 'string') ? item.value : item.value.value
104
+ const itemLabel = item.label ? item.label : itemValue
105
+ option.value = itemValue
106
+ option.textContent = itemLabel || itemValue
107
+ // if (value && value.value === itemValue) {
108
+ // option.selected = true
109
+ // }
110
+ if (item.indent) {
111
+ for (let i = 0; i < item.indent; i++) {
112
+ option.innerHTML = '&#160;&#160;' + option.innerHTML
113
+ }
114
+ }
115
+ if (itemValue === '') {
116
+ addEmptyOption = false
117
+ option.ariaLabel = 'blank'
118
+ }
119
+ editor.appendChild(option)
120
+ }
121
+ if (addEmptyOption) {
122
+ // add an empty element
123
+ const empty = new MenuItem()
124
+ empty.ariaLabel = 'blank'
125
+ editor.prepend(empty)
126
+ }
127
+ if (value) {
128
+ editor.value = value.value
129
+ }
130
+ return result
131
+ }
132
+
133
+ createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
134
+ const editor = new Checkbox()
135
+ const result = this.createDefaultTemplate('', value, required, editor, template)
136
+ // 'required' on checkboxes forces the user to tick the checkbox, which is not what we want here
137
+ editor.removeAttribute('required')
138
+ if (value instanceof Literal) {
139
+ editor.checked = value.value === 'true'
140
+ }
141
+ editor.innerText = label
142
+ return result
143
+ }
144
+
145
+ createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
146
+ const editor = new TextField()
147
+ editor.variant = 'outlined'
148
+ editor.helper = template?.description?.value || template?.label || ''
149
+ if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
150
+ editor.type = 'datetime-local'
151
+ // this enables seconds in dateTime input
152
+ editor.setAttribute('step', '1')
153
+ }
154
+ else {
155
+ editor.type = 'date'
156
+ }
157
+ editor.classList.add('pr-0')
158
+ const result = this.createDefaultTemplate('', null, required, editor, template)
159
+ if (value) {
160
+ try {
161
+ let isoDate = new Date(value.value).toISOString()
162
+ if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
163
+ isoDate = isoDate.slice(0, 19)
164
+ } else {
165
+ isoDate = isoDate.slice(0, 10)
166
+ }
167
+ editor.value = isoDate
168
+ } catch(ex) {
169
+ console.error(ex, value)
170
+ }
171
+ }
172
+ return result
173
+ }
174
+
175
+ createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
176
+ const result = this.createTextEditor(label, value, required, template)
177
+ const editor = result.querySelector(':scope .editor') as Editor
178
+ let langChooser: HTMLSelectElement | HTMLInputElement
179
+ if (template.languageIn?.length) {
180
+ langChooser = document.createElement('select')
181
+ for (const lang of template.languageIn) {
182
+ const option = document.createElement('option')
183
+ option.innerText = lang.value
184
+ langChooser.appendChild(option)
185
+ }
186
+ } else {
187
+ langChooser = document.createElement('input')
188
+ langChooser.maxLength = 5 // e.g. en-US
189
+ langChooser.placeholder = 'lang?'
190
+ }
191
+ langChooser.title = 'Language of the text'
192
+ langChooser.classList.add('lang-chooser')
193
+ // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
194
+ langChooser.addEventListener('change', (ev) => {
195
+ ev.stopPropagation();
196
+ if (editor) {
197
+ editor.dataset.lang = langChooser.value
198
+ editor.dispatchEvent(new Event('change', { bubbles: true }))
199
+ }
200
+ })
201
+ if (value instanceof Literal) {
202
+ langChooser.value = value.language
203
+ }
204
+ editor.dataset.lang = langChooser.value
205
+ editor.after(langChooser)
206
+ return result
207
+ }
208
+
209
+ createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
210
+ const editor = document.createElement('input')
211
+ editor.type = 'file'
212
+ editor.addEventListener('change', (e) => {
213
+ if (editor.files?.length) {
214
+ e.stopPropagation()
215
+ const reader = new FileReader()
216
+ reader.readAsDataURL(editor.files[0])
217
+ reader.onload = () => {
218
+ (editor as Editor)['binaryData'] = btoa(reader.result as string)
219
+ editor.parentElement?.dispatchEvent(new Event('change', { bubbles: true }))
220
+ }
221
+ } else {
222
+ (editor as Editor)['binaryData'] = undefined
223
+ }
224
+ })
225
+ return this.createDefaultTemplate(label, value, required, editor, template)
226
+ }
227
+
228
+ createButton(label: string, primary: boolean): HTMLElement {
229
+ let button
230
+ if (primary) {
231
+ button = new Button()
232
+ button.classList.add('primary')
233
+ } else {
234
+ button = new Button()
235
+ button.classList.add('secondary')
236
+ }
237
+ button.innerHTML = label
238
+ return button
239
+ }
240
+ }
package/src/util.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { Literal, NamedNode, Prefixes, Quad, Store } from 'n3'
2
+ import { OWL_OBJECT_NAMED_INDIVIDUAL, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH, SKOS_PREDICATE_BROADER } 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('input,select,textarea') as HTMLElement)?.focus()
48
+ }
49
+
50
+ export function findLabel(quads: Quad[], languages: string[]): string {
51
+ let label = findObjectValueByPredicate(quads, 'prefLabel', PREFIX_SKOS, languages)
52
+ if (label) {
53
+ return label
54
+ }
55
+ return findObjectValueByPredicate(quads, 'label', PREFIX_RDFS, languages)
56
+ }
57
+
58
+ export function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[], indent?: number): InputListEntry[] {
59
+ const entries: InputListEntry[] = []
60
+ for (const subject of subjects) {
61
+ entries.push({ value: subject, label: findLabel(shapesGraph.getQuads(subject, null, null, null), languages), indent: indent })
62
+ }
63
+ return entries
64
+ }
65
+
66
+ export function removePrefixes(id: string, prefixes: Prefixes): string {
67
+ for (const key in prefixes) {
68
+ // need to ignore type check. 'prefix' is a string and not a NamedNode<string> (seems to be a bug in n3 typings)
69
+ // @ts-ignore
70
+ id = id.replace(prefixes[key], '')
71
+ }
72
+ return id
73
+ }
74
+
75
+ function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode | ShaclPropertyTemplate, shapesGraph: Store, instances: Term[], alreadyCheckedImports = new Set<string>()) {
76
+ for (const owlImport of context.owlImports) {
77
+ if (!alreadyCheckedImports.has(owlImport.id)) {
78
+ alreadyCheckedImports.add(owlImport.id)
79
+ instances.push(...shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, owlImport))
80
+ }
81
+ }
82
+ if (context.parent) {
83
+ findClassInstancesFromOwlImports(clazz, context.parent, shapesGraph, instances, alreadyCheckedImports)
84
+ }
85
+ }
86
+
87
+ export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate, indent = 0): InputListEntry[] {
88
+ // find instances in the shapes graph
89
+ const instances: Term[] = template.config.shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
90
+ // find instances in the data graph
91
+ instances.push(...template.config.dataGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, null))
92
+ // find instances in imported taxonomies
93
+ findClassInstancesFromOwlImports(clazz, template, template.config.shapesGraph, instances)
94
+
95
+ const entries = createInputListEntries(instances, template.config.shapesGraph, template.config.languages, indent)
96
+ for (const subClass of template.config.shapesGraph.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
97
+ entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
98
+ }
99
+ if (template.config.shapesGraph.getQuads(clazz, RDF_PREDICATE_TYPE, OWL_OBJECT_NAMED_INDIVIDUAL, null).length > 0) {
100
+ entries.push(...createInputListEntries([ clazz ], template.config.shapesGraph, template.config.languages, indent))
101
+ for (const subClass of template.config.shapesGraph.getSubjects(SKOS_PREDICATE_BROADER, clazz, null)) {
102
+ entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
103
+ }
104
+ }
105
+ return entries
106
+ }
107
+
108
+ export function isURL(input: string): boolean {
109
+ let url: URL
110
+ try {
111
+ url = new URL(input)
112
+ } catch (_) {
113
+ return false
114
+ }
115
+ return url.protocol === 'http:' || url.protocol === 'https:'
116
+ }
117
+
118
+ export function prioritizeByLanguage(languages: string[], text1?: Literal, text2?: Literal): Literal | undefined {
119
+ if (text1 === undefined) {
120
+ return text2
121
+ }
122
+ if (text2 === undefined) {
123
+ return text1
124
+ }
125
+ const index1 = languages.indexOf(text1.language)
126
+ if (index1 < 0) {
127
+ return text2
128
+ }
129
+ const index2 = languages.indexOf(text2.language)
130
+ if (index2 < 0) {
131
+ return text1
132
+ }
133
+ return index2 > index1 ? text1 : text2
134
+ }