@ulb-darmstadt/shacl-form 1.6.2 → 1.6.3

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.
@@ -19,36 +19,6 @@
19
19
 
20
20
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
21
21
 
22
- /**
23
- * @license
24
- * Copyright 2014 Travis Webb
25
- * SPDX-License-Identifier: MIT
26
- */
27
-
28
- /**
29
- * @license
30
- * Copyright 2017 Google LLC
31
- * SPDX-License-Identifier: BSD-3-Clause
32
- */
33
-
34
- /**
35
- * @license
36
- * Copyright 2018 Google LLC
37
- * SPDX-License-Identifier: BSD-3-Clause
38
- */
39
-
40
- /**
41
- * @license
42
- * Copyright 2020 Google LLC
43
- * SPDX-License-Identifier: BSD-3-Clause
44
- */
45
-
46
- /**
47
- * @license
48
- * Copyright 2021 Google LLC
49
- * SPDX-License-Identifier: BSD-3-Clause
50
- */
51
-
52
22
  /**
53
23
  * A JavaScript implementation of the JSON-LD API.
54
24
  *
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
7
7
  "types": "dist/form-default.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "src"
10
11
  ],
11
12
  "repository": {
12
13
  "type": "git",
package/src/config.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { Prefixes, Store } from 'n3'
2
+ import { Term } from '@rdfjs/types'
3
+ import { PREFIX_SHACL, RDF_PREDICATE_TYPE } from './constants'
4
+ import { ClassInstanceProvider } from './plugin'
5
+ import { Loader } from './loader'
6
+ import { Theme } from './theme'
7
+
8
+ export class ElementAttributes {
9
+ shapes: string | null = null
10
+ shapesUrl: string | null = null
11
+ shapeSubject: string | null = null
12
+ values: string | null = null
13
+ valuesUrl: string | null = null
14
+ /**
15
+ * @deprecated Use valuesSubject instead
16
+ */
17
+ valueSubject: string | null = null // for backward compatibility
18
+ valuesSubject: string | null = null
19
+ valuesNamespace = ''
20
+ view: string | null = null
21
+ language: string | null = null
22
+ loading: string = 'Loading\u2026'
23
+ ignoreOwlImports: string | null = null
24
+ collapse: string | null = null
25
+ submitButton: string | null = null
26
+ generateNodeShapeReference: string | null = null
27
+ showNodeIds: string | null = null
28
+ }
29
+
30
+ export class Config {
31
+ attributes = new ElementAttributes()
32
+ loader = new Loader(this)
33
+ classInstanceProvider: ClassInstanceProvider | undefined
34
+ prefixes: Prefixes = {}
35
+ editMode = true
36
+ languages: string[]
37
+
38
+ dataGraph = new Store()
39
+ lists: Record<string, Term[]> = {}
40
+ groups: Array<string> = []
41
+ theme: Theme
42
+ form: HTMLElement
43
+ renderedNodes = new Set<string>()
44
+ private _shapesGraph = new Store()
45
+
46
+ constructor(theme: Theme, form: HTMLElement) {
47
+ this.theme = theme
48
+ this.form = form
49
+ this.languages = [...new Set(navigator.languages.flatMap(lang => {
50
+ if (lang.length > 2) {
51
+ // for each 5 letter lang code (e.g. de-DE) append its corresponding 2 letter code (e.g. de) directly afterwards
52
+ return [lang, lang.substring(0, 2)]
53
+ }
54
+ return lang
55
+ }))]
56
+ }
57
+
58
+ updateAttributes(elem: HTMLElement) {
59
+ const atts = new ElementAttributes();
60
+ (Object.keys(atts) as Array<keyof ElementAttributes>).forEach(key => {
61
+ const value = elem.dataset[key]
62
+ if (value !== undefined) {
63
+ atts[key] = value
64
+ }
65
+ })
66
+ this.editMode = atts.view === null
67
+ this.attributes = atts
68
+ // for backward compatibility
69
+ if (this.attributes.valueSubject && !this.attributes.valuesSubject) {
70
+ this.attributes.valuesSubject = this.attributes.valueSubject
71
+ }
72
+ if (atts.language) {
73
+ const index = this.languages.indexOf(atts.language)
74
+ if (index > -1) {
75
+ // remove preferred language from the list of languages
76
+ this.languages.splice(index, 1)
77
+ }
78
+ // now prepend preferred language at start of the list of languages
79
+ this.languages.unshift(atts.language)
80
+ }
81
+ }
82
+
83
+ static dataAttributes(): Array<string> {
84
+ const atts = new ElementAttributes()
85
+ return Object.keys(atts).map(key => {
86
+ // convert camelcase key to kebap case
87
+ key = key.replace(/[A-Z]/g, m => "-" + m.toLowerCase());
88
+ return 'data-' + key
89
+ })
90
+ }
91
+
92
+ get shapesGraph() {
93
+ return this._shapesGraph
94
+ }
95
+
96
+ set shapesGraph(graph: Store) {
97
+ this._shapesGraph = graph
98
+ this.lists = graph.extractLists()
99
+ this.groups = []
100
+ graph.forSubjects(subject => {
101
+ this.groups.push(subject.id)
102
+ }, RDF_PREDICATE_TYPE, `${PREFIX_SHACL}PropertyGroup`, null)
103
+ }
104
+
105
+ registerPrefixes(prefixes: Prefixes) {
106
+ for (const key in prefixes) {
107
+ // ignore empty (default) namespace
108
+ if (key) {
109
+ this.prefixes[key] = prefixes[key]
110
+ }
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,25 @@
1
+ import { DataFactory } from "n3"
2
+
3
+ export const PREFIX_SHACL = 'http://www.w3.org/ns/shacl#'
4
+ export const PREFIX_DASH = 'http://datashapes.org/dash#'
5
+ export const PREFIX_XSD = 'http://www.w3.org/2001/XMLSchema#'
6
+ export const PREFIX_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
7
+ export const PREFIX_RDFS = 'http://www.w3.org/2000/01/rdf-schema#'
8
+ export const PREFIX_SKOS = 'http://www.w3.org/2004/02/skos/core#'
9
+ export const PREFIX_OWL = 'http://www.w3.org/2002/07/owl#'
10
+ export const PREFIX_OA = 'http://www.w3.org/ns/oa#'
11
+ export const PREFIX_DCTERMS = 'http://purl.org/dc/terms/'
12
+
13
+ export const SHAPES_GRAPH = DataFactory.namedNode('shapes')
14
+
15
+ export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
16
+ export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
17
+ export const RDFS_PREDICATE_SUBCLASS_OF = DataFactory.namedNode(PREFIX_RDFS + 'subClassOf')
18
+ export const SKOS_PREDICATE_BROADER = DataFactory.namedNode(PREFIX_SKOS + 'broader')
19
+ export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports')
20
+ export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
21
+ export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
22
+ export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
23
+ export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
24
+ export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
25
+ export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
@@ -0,0 +1,106 @@
1
+ import { BlankNode, Literal, NamedNode, Quad } from 'n3'
2
+ import { Term } from '@rdfjs/types'
3
+ import { ShaclNode } from "./node"
4
+ import { ShaclProperty, createPropertyInstance } from "./property"
5
+ import { Config } from './config'
6
+ import { PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS, SHACL_PREDICATE_NODE_KIND, SHACL_OBJECT_IRI } from './constants'
7
+ import { findLabel, removePrefixes } from './util'
8
+ import { ShaclPropertyTemplate } from './property-template'
9
+ import { Editor, InputListEntry } from './theme'
10
+
11
+
12
+ export function createShaclOrConstraint(options: Term[], context: ShaclNode | ShaclProperty, config: Config): HTMLElement {
13
+ const constraintElement = document.createElement('div')
14
+ constraintElement.classList.add('shacl-or-constraint')
15
+
16
+ const optionElements: InputListEntry[] = []
17
+ optionElements.push({ label: '--- please choose ---', value: '' })
18
+
19
+ if (context instanceof ShaclNode) {
20
+ const properties: ShaclProperty[] = []
21
+ // expect options to be shacl properties
22
+ for (let i = 0; i < options.length; i++) {
23
+ const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
24
+ properties.push(property)
25
+ optionElements.push({ label: property.template.label, value: i.toString() })
26
+ }
27
+ const editor = config.theme.createListEditor('Please choose', null, false, optionElements)
28
+ const select = editor.querySelector('.editor') as Editor
29
+ select.onchange = () => {
30
+ if (select.value) {
31
+ constraintElement.replaceWith(properties[parseInt(select.value)])
32
+ }
33
+ }
34
+ constraintElement.appendChild(editor)
35
+ } else {
36
+ const values: Quad[][] = []
37
+ for (let i = 0; i < options.length; i++) {
38
+ const quads = config.shapesGraph.getQuads(options[i], null, null, null)
39
+ if (quads.length) {
40
+ values.push(quads)
41
+ optionElements.push({ label: findLabel(quads, config.languages) || (removePrefixes(quads[0].predicate.value, config.prefixes) + ' = ' + removePrefixes(quads[0].object.value, config.prefixes)), value: i.toString() })
42
+ }
43
+ }
44
+ const editor = config.theme.createListEditor(context.template.label + '?', null, false, optionElements, context.template)
45
+ const select = editor.querySelector('.editor') as Editor
46
+ select.onchange = () => {
47
+ if (select.value) {
48
+ constraintElement.replaceWith(createPropertyInstance(context.template.clone().merge(values[parseInt(select.value)]), undefined, true))
49
+ }
50
+ }
51
+ constraintElement.appendChild(editor)
52
+ }
53
+
54
+ return constraintElement
55
+ }
56
+
57
+ export function resolveShaclOrConstraint(template: ShaclPropertyTemplate, value: Term): ShaclPropertyTemplate {
58
+ if (!template.shaclOr) {
59
+ console.warn('can\'t resolve sh:or because template has no options', template)
60
+ return template
61
+ }
62
+ if (value instanceof Literal) {
63
+ // value is a literal, try to resolve sh:or by matching on given value datatype
64
+ const valueType = value.datatype
65
+ for (const subject of template.shaclOr) {
66
+ const options = template.config.shapesGraph.getQuads(subject, null, null, null)
67
+ for (const quad of options) {
68
+ if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
69
+ return template.clone().merge(options)
70
+ }
71
+ }
72
+ }
73
+ } else {
74
+ // value is a NamedNode or BlankNode, try to resolve sh:or by matching rdf:type of given value with sh:node or sh:class in data graph or shapes graph
75
+ let types = template.config.dataGraph.getObjects(value, RDF_PREDICATE_TYPE, null)
76
+ types.push(...template.config.shapesGraph.getObjects(value, RDF_PREDICATE_TYPE, null))
77
+ for (const subject of template.shaclOr) {
78
+ const options = template.config.shapesGraph.getQuads(subject, null, null, null)
79
+ for (const quad of options) {
80
+ if (types.length > 0) {
81
+ // try to find matching sh:node in sh:or values
82
+ if (quad.predicate.value === `${PREFIX_SHACL}node`) {
83
+ for (const type of types) {
84
+ if (template.config.shapesGraph.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
85
+ return template.clone().merge(options)
86
+ }
87
+ }
88
+ }
89
+ // try to find matching sh:class in sh:or values
90
+ if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) {
91
+ for (const type of types) {
92
+ if (quad.object.equals(type)) {
93
+ return template.clone().merge(options)
94
+ }
95
+ }
96
+ }
97
+ } else if (quad.predicate.equals(SHACL_PREDICATE_NODE_KIND) && quad.object.equals(SHACL_OBJECT_IRI)) {
98
+ // if sh:nodeKind is sh:IRI, just use that
99
+ return template.clone().merge(options)
100
+ }
101
+ }
102
+ }
103
+ }
104
+ console.error('couldn\'t resolve sh:or for value', value)
105
+ return template
106
+ }
package/src/exports.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { Theme, InputListEntry, Editor } from './theme'
2
+ export { Loader, setSharedShapesGraph } from './loader'
3
+ export { Config } from './config'
4
+ export { Plugin, registerPlugin } from './plugin'
5
+ export { ShaclPropertyTemplate } from './property-template'
6
+ export { findLabel } from './util'
@@ -0,0 +1,12 @@
1
+ import { ShaclForm as FormBase } from "./form"
2
+ import { BootstrapTheme } from "./themes/bootstrap"
3
+
4
+ export * from './exports'
5
+
6
+ export class ShaclForm extends FormBase {
7
+ constructor() {
8
+ super(new BootstrapTheme())
9
+ }
10
+ }
11
+
12
+ window.customElements.define('shacl-form', ShaclForm)
@@ -0,0 +1,12 @@
1
+ import { ShaclForm as FormBase } from "./form"
2
+ import { DefaultTheme } from "./themes/default"
3
+
4
+ export * from './exports'
5
+
6
+ export class ShaclForm extends FormBase {
7
+ constructor() {
8
+ super(new DefaultTheme())
9
+ }
10
+ }
11
+
12
+ window.customElements.define('shacl-form', ShaclForm)
@@ -0,0 +1,12 @@
1
+ import { ShaclForm as FormBase } from "./form"
2
+ import { MaterialTheme } from "./themes/material"
3
+
4
+ export * from './exports'
5
+
6
+ export class ShaclForm extends FormBase {
7
+ constructor() {
8
+ super(new MaterialTheme())
9
+ }
10
+ }
11
+
12
+ window.customElements.define('shacl-form', ShaclForm)
package/src/form.ts ADDED
@@ -0,0 +1,290 @@
1
+ import { ShaclNode } from './node'
2
+ import { Config } from './config'
3
+ import { ClassInstanceProvider, Plugin, listPlugins, registerPlugin } from './plugin'
4
+ import { Store, NamedNode, DataFactory } from 'n3'
5
+ import { DCTERMS_PREDICATE_CONFORMS_TO, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
6
+ import { Editor, Theme } from './theme'
7
+ import { serialize } from './serialize'
8
+ import { Validator } from 'shacl-engine'
9
+ import { setSharedShapesGraph } from './loader'
10
+
11
+ export class ShaclForm extends HTMLElement {
12
+ static get observedAttributes() { return Config.dataAttributes() }
13
+
14
+ config: Config
15
+ shape: ShaclNode | null = null
16
+ form: HTMLFormElement
17
+ initDebounceTimeout: ReturnType<typeof setTimeout> | undefined
18
+
19
+ constructor(theme: Theme) {
20
+ super()
21
+
22
+ this.attachShadow({ mode: 'open' })
23
+ this.form = document.createElement('form')
24
+ this.config = new Config(theme, this.form)
25
+ this.form.addEventListener('change', ev => {
26
+ ev.stopPropagation()
27
+ if (this.config.editMode) {
28
+ this.validate(true).then(valid => {
29
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, cancelable: false, composed: true, detail: { 'valid': valid } }))
30
+ }).catch(e => { console.warn(e) })
31
+ }
32
+ })
33
+ }
34
+
35
+ connectedCallback() {
36
+ this.shadowRoot!.prepend(this.form)
37
+ }
38
+
39
+ attributeChangedCallback() {
40
+ this.config.updateAttributes(this)
41
+ this.initialize()
42
+ }
43
+
44
+ private initialize() {
45
+ clearTimeout(this.initDebounceTimeout)
46
+ this.initDebounceTimeout = setTimeout(async () => {
47
+ // remove all child elements from form and show loading indicator
48
+ this.form.replaceChildren(document.createTextNode(this.config.attributes.loading))
49
+ try {
50
+ await this.config.loader.loadGraphs()
51
+ // remove loading indicator
52
+ this.form.replaceChildren()
53
+ // reset rendered node references
54
+ this.config.renderedNodes.clear()
55
+ // find root shacl shape
56
+ const rootShapeShaclSubject = this.findRootShaclShapeSubject()
57
+ if (rootShapeShaclSubject) {
58
+ // remove all previous css classes to have a defined state
59
+ this.form.classList.forEach(value => { this.form.classList.remove(value) })
60
+ this.form.classList.toggle('mode-edit', this.config.editMode)
61
+ this.form.classList.toggle('mode-view', !this.config.editMode)
62
+ // let theme add classes to form element
63
+ this.config.theme.apply(this.form)
64
+ // adopt stylesheets from theme and plugins
65
+ const styles: CSSStyleSheet[] = [ this.config.theme.stylesheet ]
66
+ for (const plugin of listPlugins()) {
67
+ if (plugin.stylesheet) {
68
+ styles.push(plugin.stylesheet)
69
+ }
70
+ }
71
+ this.shadowRoot!.adoptedStyleSheets = styles
72
+
73
+ this.shape = new ShaclNode(rootShapeShaclSubject, this.config, this.config.attributes.valuesSubject ? DataFactory.namedNode(this.config.attributes.valuesSubject) : undefined)
74
+ this.form.appendChild(this.shape)
75
+
76
+ if (this.config.editMode) {
77
+ // add submit button
78
+ if (this.config.attributes.submitButton !== null) {
79
+ const button = this.config.theme.createButton(this.config.attributes.submitButton || 'Submit', true)
80
+ button.addEventListener('click', (event) => {
81
+ event.preventDefault()
82
+ // let browser check form validity first
83
+ if (this.form.reportValidity()) {
84
+ // now validate data graph
85
+ this.validate().then(valid => {
86
+ if (valid) {
87
+ // form and data graph are valid, so fire submit event
88
+ this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
89
+ } else {
90
+ // focus first invalid element
91
+ (this.form.querySelector(':scope .invalid > .editor') as HTMLElement)?.focus()
92
+ }
93
+ })
94
+ }
95
+ })
96
+ this.form.appendChild(button)
97
+ }
98
+ await this.validate(true)
99
+ }
100
+ } else if (this.config.shapesGraph.size > 0) {
101
+ // raise error only when shapes graph is not empty
102
+ throw new Error('shacl root node shape not found')
103
+ }
104
+ } catch (e) {
105
+ console.error(e)
106
+ const errorDisplay = document.createElement('div')
107
+ errorDisplay.innerText = String(e)
108
+ this.form.replaceChildren(errorDisplay)
109
+ }
110
+ }, 200)
111
+ }
112
+
113
+ public serialize(format = 'text/turtle', graph = this.toRDF()): string {
114
+ const quads = graph.getQuads(null, null, null, null)
115
+ return serialize(quads, format, this.config.prefixes)
116
+ }
117
+
118
+ public toRDF(graph = new Store()): Store {
119
+ this.shape?.toRDF(graph)
120
+ return graph
121
+ }
122
+
123
+ public registerPlugin(plugin: Plugin) {
124
+ registerPlugin(plugin)
125
+ this.initialize()
126
+ }
127
+
128
+ public setTheme(theme: Theme) {
129
+ this.config.theme = theme
130
+ this.initialize()
131
+ }
132
+
133
+ public setSharedShapesGraph(graph: Store) {
134
+ setSharedShapesGraph(graph)
135
+ this.initialize()
136
+ }
137
+
138
+ public setClassInstanceProvider(provider: ClassInstanceProvider) {
139
+ this.config.classInstanceProvider = provider
140
+ this.initialize()
141
+ }
142
+
143
+ public async validate(ignoreEmptyValues = false): Promise<boolean> {
144
+ for (const elem of this.form.querySelectorAll(':scope .validation-error')) {
145
+ elem.remove()
146
+ }
147
+ for (const elem of this.form.querySelectorAll(':scope .property-instance')) {
148
+ elem.classList.remove('invalid')
149
+ if (((elem.querySelector(':scope > .editor')) as Editor)?.value) {
150
+ elem.classList.add('valid')
151
+ } else {
152
+ elem.classList.remove('valid')
153
+ }
154
+ }
155
+
156
+ this.config.shapesGraph.deleteGraph('')
157
+ this.shape?.toRDF(this.config.shapesGraph)
158
+ try {
159
+ const dataset = this.config.shapesGraph
160
+ const report = await new Validator(dataset, { details: true, factory: DataFactory }).validate({ dataset })
161
+
162
+ for (const result of report.results) {
163
+ if (result.focusNode?.ptrs?.length) {
164
+ for (const ptr of result.focusNode.ptrs) {
165
+ const focusNode = ptr._term
166
+ // result.path can be empty, e.g. if a focus node does not contain a required property node
167
+ if (result.path?.length) {
168
+ const path = result.path[0].predicates[0]
169
+ // try to find most specific editor elements first
170
+ let invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}'] > .editor`)
171
+ if (invalidElements.length === 0) {
172
+ // if no editors found, select respective node. this will be the case for node shape violations.
173
+ invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}']`)
174
+ }
175
+
176
+ for (const invalidElement of invalidElements) {
177
+ if (invalidElement.classList.contains('editor')) {
178
+ // this is a property shape violation
179
+ if (!ignoreEmptyValues || (invalidElement as Editor).value) {
180
+ let parent: HTMLElement | null = invalidElement.parentElement!
181
+ parent.classList.add('invalid')
182
+ parent.classList.remove('valid')
183
+ parent.appendChild(this.createValidationErrorDisplay(result))
184
+ do {
185
+ if (parent.classList.contains('collapsible')) {
186
+ parent.classList.add('open')
187
+ }
188
+ parent = parent.parentElement
189
+ } while (parent)
190
+ }
191
+ } else if (!ignoreEmptyValues) {
192
+ // this is a node shape violation
193
+ invalidElement.classList.add('invalid')
194
+ invalidElement.classList.remove('valid')
195
+ invalidElement.appendChild(this.createValidationErrorDisplay(result, 'node'))
196
+ }
197
+ }
198
+ } else if (!ignoreEmptyValues) {
199
+ this.form.querySelector(`:scope [data-node-id='${focusNode.id}']`)?.prepend(this.createValidationErrorDisplay(result, 'node'))
200
+ }
201
+ }
202
+ }
203
+ }
204
+ return report.conforms
205
+ } catch(e) {
206
+ console.error(e)
207
+ return false
208
+ }
209
+ }
210
+
211
+ private createValidationErrorDisplay(validatonResult?: any, clazz?: string): HTMLElement {
212
+ const messageElement = document.createElement('span')
213
+ messageElement.classList.add('validation-error')
214
+ if (clazz) {
215
+ messageElement.classList.add(clazz)
216
+ }
217
+ if (validatonResult) {
218
+ if (validatonResult.message?.length > 0) {
219
+ for (const message of validatonResult.message) {
220
+ messageElement.title += message.value + '\n'
221
+ }
222
+ } else {
223
+ messageElement.title = validatonResult.sourceConstraintComponent?.value
224
+ }
225
+ }
226
+ return messageElement
227
+ }
228
+
229
+ private findRootShaclShapeSubject(): NamedNode | undefined {
230
+ let rootShapeShaclSubject: NamedNode | null = null
231
+ // if data-shape-subject is set, use that
232
+ if (this.config.attributes.shapeSubject) {
233
+ rootShapeShaclSubject = DataFactory.namedNode(this.config.attributes.shapeSubject)
234
+ if (this.config.shapesGraph.getQuads(rootShapeShaclSubject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
235
+ console.warn(`shapes graph does not contain requested root shape ${this.config.attributes.shapeSubject}`)
236
+ return
237
+ }
238
+ }
239
+ else {
240
+ // if we have a data graph and data-values-subject is set, use shape of that
241
+ if (this.config.attributes.valuesSubject && this.config.dataGraph.size > 0) {
242
+ const rootValueSubject = DataFactory.namedNode(this.config.attributes.valuesSubject)
243
+ const rootValueSubjectTypes = [
244
+ ...this.config.dataGraph.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, null),
245
+ ...this.config.dataGraph.getQuads(rootValueSubject, DCTERMS_PREDICATE_CONFORMS_TO, null, null)
246
+ ]
247
+ if (rootValueSubjectTypes.length === 0) {
248
+ console.warn(`value subject '${this.config.attributes.valuesSubject}' has neither ${RDF_PREDICATE_TYPE.id} nor ${DCTERMS_PREDICATE_CONFORMS_TO.id} statement`)
249
+ return
250
+ }
251
+ // if type/conformsTo refers to a node shape, prioritize that over targetClass resolution
252
+ for (const rootValueSubjectType of rootValueSubjectTypes) {
253
+ if (this.config.shapesGraph.getQuads(rootValueSubjectType.object as NamedNode, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length > 0) {
254
+ rootShapeShaclSubject = rootValueSubjectType.object as NamedNode
255
+ break
256
+ }
257
+ }
258
+ if (!rootShapeShaclSubject) {
259
+ const rootShapes = this.config.shapesGraph.getQuads(null, SHACL_PREDICATE_TARGET_CLASS, rootValueSubjectTypes[0].object, null)
260
+ if (rootShapes.length === 0) {
261
+ console.error(`value subject '${this.config.attributes.valuesSubject}' has no shacl shape definition in the shapes graph`)
262
+ return
263
+ }
264
+ if (rootShapes.length > 1) {
265
+ console.warn(`value subject '${this.config.attributes.valuesSubject}' has multiple shacl shape definitions in the shapes graph, choosing the first found (${rootShapes[0].subject})`)
266
+ }
267
+ if (this.config.shapesGraph.getQuads(rootShapes[0].subject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
268
+ console.error(`value subject '${this.config.attributes.valuesSubject}' references a shape which is not a NodeShape (${rootShapes[0].subject})`)
269
+ return
270
+ }
271
+ rootShapeShaclSubject = rootShapes[0].subject as NamedNode
272
+ }
273
+ }
274
+ else {
275
+ // choose first of all defined root shapes
276
+ const rootShapes = this.config.shapesGraph.getQuads(null, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null)
277
+ if (rootShapes.length == 0) {
278
+ console.warn('shapes graph does not contain any root shapes')
279
+ return
280
+ }
281
+ if (rootShapes.length > 1) {
282
+ console.warn('shapes graph contains', rootShapes.length, 'root shapes. choosing first found which is', rootShapes[0].subject.value)
283
+ console.info('hint: set the shape to use with attribute "data-shape-subject"')
284
+ }
285
+ rootShapeShaclSubject = rootShapes[0].subject as NamedNode
286
+ }
287
+ }
288
+ return rootShapeShaclSubject
289
+ }
290
+ }
@@ -0,0 +1,2 @@
1
+ declare module "*.css";
2
+ declare module "*.css?raw";