@ulb-darmstadt/shacl-form 1.7.2 → 1.7.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.
- package/dist/constants.d.ts +1 -0
- package/dist/constraints.d.ts +3 -2
- package/dist/form-bootstrap.js +1 -1
- package/dist/form-default.js +1 -1
- package/dist/form-material.js +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/plugins/leaflet.js +1 -1
- package/dist/plugins/mapbox.js +1 -1
- package/dist/property-template.d.ts +2 -0
- package/package.json +4 -4
- package/src/constants.ts +1 -0
- package/src/constraints.ts +76 -29
- package/src/node.ts +59 -36
- package/src/property-template.ts +22 -3
- package/src/property.ts +32 -8
- package/src/util.ts +23 -13
|
@@ -28,9 +28,11 @@ export declare class ShaclPropertyTemplate {
|
|
|
28
28
|
shaclAnd: string | undefined;
|
|
29
29
|
shaclIn: string | undefined;
|
|
30
30
|
shaclOr: Term[] | undefined;
|
|
31
|
+
shaclXone: Term[] | undefined;
|
|
31
32
|
languageIn: Term[] | undefined;
|
|
32
33
|
datatype: NamedNode | undefined;
|
|
33
34
|
hasValue: Term | undefined;
|
|
35
|
+
qualifiedValueShape: Term | undefined;
|
|
34
36
|
owlImports: NamedNode[];
|
|
35
37
|
config: Config;
|
|
36
38
|
extendedShapes: NamedNode[] | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulb-darmstadt/shacl-form",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "SHACL form generator",
|
|
5
5
|
"main": "dist/form-default.js",
|
|
6
6
|
"module": "dist/form-default.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/jsonld": "^1.5.15",
|
|
36
|
-
"@types/leaflet": "^1.9.
|
|
36
|
+
"@types/leaflet": "^1.9.19",
|
|
37
37
|
"@types/leaflet-editable": "^1.2.6",
|
|
38
38
|
"@types/leaflet.fullscreen": "^3.0.2",
|
|
39
39
|
"@types/mapbox__mapbox-gl-draw": "^1.4.9",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@mapbox/mapbox-gl-draw": "^1.5.0",
|
|
52
|
-
"bootstrap": "^5.3.
|
|
52
|
+
"bootstrap": "^5.3.7",
|
|
53
53
|
"jsonld": "^8.3.3",
|
|
54
54
|
"leaflet": "^1.9.4",
|
|
55
55
|
"leaflet-editable": "^1.3.1",
|
|
56
56
|
"leaflet.fullscreen": "^4.0.0",
|
|
57
|
-
"mapbox-gl": "^3.
|
|
57
|
+
"mapbox-gl": "^3.13.0",
|
|
58
58
|
"mdui": "^2.1.4",
|
|
59
59
|
"n3": "^1.26.0",
|
|
60
60
|
"shacl-engine": "^1.0.2",
|
package/src/constants.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports
|
|
|
20
20
|
export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
|
|
21
21
|
export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
|
|
22
22
|
export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
|
|
23
|
+
export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
|
|
23
24
|
export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
|
|
24
25
|
export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
|
|
25
26
|
export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
|
package/src/constraints.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Term } from '@rdfjs/types'
|
|
|
3
3
|
import { ShaclNode } from "./node"
|
|
4
4
|
import { ShaclProperty, createPropertyInstance } from "./property"
|
|
5
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'
|
|
6
|
+
import { PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS, SHACL_PREDICATE_NODE_KIND, SHACL_OBJECT_IRI, SHACL_PREDICATE_PROPERTY } from './constants'
|
|
7
7
|
import { findLabel, removePrefixes } from './util'
|
|
8
8
|
import { ShaclPropertyTemplate } from './property-template'
|
|
9
9
|
import { Editor, InputListEntry } from './theme'
|
|
@@ -17,18 +17,45 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
|
|
|
17
17
|
optionElements.push({ label: '--- please choose ---', value: '' })
|
|
18
18
|
|
|
19
19
|
if (context instanceof ShaclNode) {
|
|
20
|
-
const properties: ShaclProperty[] = []
|
|
21
|
-
//
|
|
20
|
+
const properties: ShaclProperty[][] = []
|
|
21
|
+
// options can be shacl properties or blank nodes referring to (list of) properties
|
|
22
|
+
let optionsAreReferencedProperties = false
|
|
23
|
+
if (options.length) {
|
|
24
|
+
optionsAreReferencedProperties = config.shapesGraph.getObjects(options[0] , SHACL_PREDICATE_PROPERTY, null).length > 0
|
|
25
|
+
}
|
|
22
26
|
for (let i = 0; i < options.length; i++) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
if (optionsAreReferencedProperties) {
|
|
28
|
+
const quads = config.shapesGraph.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null)
|
|
29
|
+
// option can be single property or list of properties
|
|
30
|
+
const list: ShaclProperty[] = []
|
|
31
|
+
let combinedText = ''
|
|
32
|
+
for (const subject of quads) {
|
|
33
|
+
const property = new ShaclProperty(subject as NamedNode | BlankNode, context, config)
|
|
34
|
+
list.push(property)
|
|
35
|
+
combinedText += (combinedText.length > 1 ? ' / ' : '') + property.template.label
|
|
36
|
+
}
|
|
37
|
+
properties.push(list)
|
|
38
|
+
optionElements.push({ label: combinedText, value: i.toString() })
|
|
39
|
+
} else {
|
|
40
|
+
const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
|
|
41
|
+
properties.push([property])
|
|
42
|
+
optionElements.push({ label: property.template.label, value: i.toString() })
|
|
43
|
+
}
|
|
26
44
|
}
|
|
27
45
|
const editor = config.theme.createListEditor('Please choose', null, false, optionElements)
|
|
28
46
|
const select = editor.querySelector('.editor') as Editor
|
|
29
47
|
select.onchange = () => {
|
|
30
48
|
if (select.value) {
|
|
31
|
-
|
|
49
|
+
const selectedOptions = properties[parseInt(select.value)]
|
|
50
|
+
let lastAddedProperty: ShaclProperty
|
|
51
|
+
if (selectedOptions.length) {
|
|
52
|
+
lastAddedProperty = selectedOptions[0]
|
|
53
|
+
constraintElement.replaceWith(selectedOptions[0])
|
|
54
|
+
}
|
|
55
|
+
for (let i = 1; i < selectedOptions.length; i++) {
|
|
56
|
+
lastAddedProperty!.after(selectedOptions[1])
|
|
57
|
+
lastAddedProperty = selectedOptions[1]
|
|
58
|
+
}
|
|
32
59
|
}
|
|
33
60
|
}
|
|
34
61
|
constraintElement.appendChild(editor)
|
|
@@ -54,53 +81,73 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
|
|
|
54
81
|
return constraintElement
|
|
55
82
|
}
|
|
56
83
|
|
|
57
|
-
export function
|
|
58
|
-
if (!template.shaclOr) {
|
|
59
|
-
console.warn('can\'t resolve sh:or because template has no options', template)
|
|
60
|
-
return template
|
|
61
|
-
}
|
|
84
|
+
export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term, config: Config): Quad[] {
|
|
62
85
|
if (value instanceof Literal) {
|
|
63
|
-
// value is a literal, try to resolve sh:or by matching on given value datatype
|
|
86
|
+
// value is a literal, try to resolve sh:or/sh:xone by matching on given value datatype
|
|
64
87
|
const valueType = value.datatype
|
|
65
|
-
for (const subject of
|
|
66
|
-
const options =
|
|
88
|
+
for (const subject of subjects) {
|
|
89
|
+
const options = config.shapesGraph.getQuads(subject, null, null, null)
|
|
67
90
|
for (const quad of options) {
|
|
68
91
|
if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
|
|
69
|
-
return
|
|
92
|
+
return options
|
|
70
93
|
}
|
|
71
94
|
}
|
|
72
95
|
}
|
|
73
96
|
} 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
|
-
|
|
76
|
-
types.push(...
|
|
77
|
-
for (const subject of
|
|
78
|
-
const options =
|
|
97
|
+
// value is a NamedNode or BlankNode, try to resolve sh:or/sh:xone by matching rdf:type of given value with sh:node or sh:class in data graph or shapes graph
|
|
98
|
+
const types = config.dataGraph.getObjects(value, RDF_PREDICATE_TYPE, null)
|
|
99
|
+
types.push(...config.shapesGraph.getObjects(value, RDF_PREDICATE_TYPE, null))
|
|
100
|
+
for (const subject of subjects) {
|
|
101
|
+
const options = config.shapesGraph.getQuads(subject, null, null, null)
|
|
79
102
|
for (const quad of options) {
|
|
80
103
|
if (types.length > 0) {
|
|
81
|
-
// try to find matching sh:node in sh:or values
|
|
104
|
+
// try to find matching sh:node in sh:or/sh:xone values
|
|
82
105
|
if (quad.predicate.value === `${PREFIX_SHACL}node`) {
|
|
83
106
|
for (const type of types) {
|
|
84
|
-
if (
|
|
85
|
-
return
|
|
107
|
+
if (config.shapesGraph.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
|
|
108
|
+
return options
|
|
86
109
|
}
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
|
-
// try to find matching sh:class in sh:or values
|
|
112
|
+
// try to find matching sh:class in sh:or/sh:xone values
|
|
90
113
|
if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) {
|
|
91
114
|
for (const type of types) {
|
|
92
115
|
if (quad.object.equals(type)) {
|
|
93
|
-
return
|
|
116
|
+
return options
|
|
94
117
|
}
|
|
95
118
|
}
|
|
96
119
|
}
|
|
97
120
|
} else if (quad.predicate.equals(SHACL_PREDICATE_NODE_KIND) && quad.object.equals(SHACL_OBJECT_IRI)) {
|
|
98
121
|
// if sh:nodeKind is sh:IRI, just use that
|
|
99
|
-
return
|
|
122
|
+
return options
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.error('couldn\'t resolve sh:or/sh:xone on property for value', value)
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveShaclOrConstraintOnNode(subjects: Term[], value: Term, config: Config): Term[] {
|
|
132
|
+
for (const subject of subjects) {
|
|
133
|
+
let subjectMatches = true
|
|
134
|
+
const propertySubjects = config.shapesGraph.getObjects(subject, SHACL_PREDICATE_PROPERTY, null)
|
|
135
|
+
for (const propertySubject of propertySubjects) {
|
|
136
|
+
const paths = config.shapesGraph.getObjects(propertySubject, `${PREFIX_SHACL}path`, null)
|
|
137
|
+
for (const path of paths) {
|
|
138
|
+
const values = config.dataGraph.getObjects(value, path, null)
|
|
139
|
+
values.push(...config.shapesGraph.getObjects(value, path, null))
|
|
140
|
+
if (!values.length) {
|
|
141
|
+
subjectMatches = false
|
|
142
|
+
break
|
|
100
143
|
}
|
|
101
144
|
}
|
|
102
145
|
}
|
|
146
|
+
if (subjectMatches) {
|
|
147
|
+
return propertySubjects
|
|
148
|
+
}
|
|
103
149
|
}
|
|
104
|
-
|
|
105
|
-
|
|
150
|
+
|
|
151
|
+
console.error('couldn\'t resolve sh:or/sh:xone on node for value', value)
|
|
152
|
+
return []
|
|
106
153
|
}
|
package/src/node.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BlankNode, DataFactory, NamedNode, Store } from 'n3'
|
|
2
2
|
import { Term } from '@rdfjs/types'
|
|
3
|
-
import { PREFIX_SHACL, RDF_PREDICATE_TYPE, OWL_PREDICATE_IMPORTS } from './constants'
|
|
3
|
+
import { PREFIX_SHACL, RDF_PREDICATE_TYPE, OWL_PREDICATE_IMPORTS, SHACL_PREDICATE_PROPERTY } from './constants'
|
|
4
4
|
import { ShaclProperty } from './property'
|
|
5
5
|
import { createShaclGroup } from './group'
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid'
|
|
7
|
-
import { createShaclOrConstraint } from './constraints'
|
|
7
|
+
import { createShaclOrConstraint, resolveShaclOrConstraintOnNode } from './constraints'
|
|
8
8
|
import { Config } from './config'
|
|
9
9
|
|
|
10
10
|
export class ShaclNode extends HTMLElement {
|
|
@@ -76,33 +76,8 @@ export class ShaclNode extends HTMLElement {
|
|
|
76
76
|
|
|
77
77
|
for (const quad of quads) {
|
|
78
78
|
switch (quad.predicate.id) {
|
|
79
|
-
case
|
|
80
|
-
|
|
81
|
-
// check if property belongs to a group
|
|
82
|
-
const groupRef = config.shapesGraph.getQuads(quad.object as Term, `${PREFIX_SHACL}group`, null, null)
|
|
83
|
-
if (groupRef.length > 0) {
|
|
84
|
-
const groupSubject = groupRef[0].object.value
|
|
85
|
-
if (config.groups.indexOf(groupSubject) > -1) {
|
|
86
|
-
// check if group element already exists, otherwise create it
|
|
87
|
-
let group = this.querySelector(`:scope > .shacl-group[data-subject='${groupSubject}']`) as HTMLElement
|
|
88
|
-
if (!group) {
|
|
89
|
-
group = createShaclGroup(groupSubject, config)
|
|
90
|
-
this.appendChild(group)
|
|
91
|
-
}
|
|
92
|
-
parentElement = group
|
|
93
|
-
} else {
|
|
94
|
-
console.warn('ignoring unknown group reference', groupRef[0], 'existing groups:', config.groups)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// delay creating/appending the property until we finished parsing the node.
|
|
98
|
-
// This is needed to have possible owlImports parsed before creating the property.
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
const property = new ShaclProperty(quad.object as NamedNode | BlankNode, this, config, valueSubject)
|
|
101
|
-
// do not add empty properties (i.e. properties with no instances). This can be the case e.g. in viewer mode when there is no data for the respective property.
|
|
102
|
-
if (property.childElementCount > 0) {
|
|
103
|
-
parentElement.appendChild(property)
|
|
104
|
-
}
|
|
105
|
-
})
|
|
79
|
+
case SHACL_PREDICATE_PROPERTY.id:
|
|
80
|
+
this.addPropertyInstance(quad.object, config, valueSubject)
|
|
106
81
|
break;
|
|
107
82
|
case `${PREFIX_SHACL}and`:
|
|
108
83
|
// inheritance via sh:and
|
|
@@ -127,13 +102,10 @@ export class ShaclNode extends HTMLElement {
|
|
|
127
102
|
this.owlImports.push(quad.object as NamedNode)
|
|
128
103
|
break;
|
|
129
104
|
case `${PREFIX_SHACL}or`:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
else {
|
|
135
|
-
console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
|
|
136
|
-
}
|
|
105
|
+
this.tryResolve(quad.object, valueSubject, config)
|
|
106
|
+
break;
|
|
107
|
+
case `${PREFIX_SHACL}xone`:
|
|
108
|
+
this.tryResolve(quad.object, valueSubject, config)
|
|
137
109
|
break;
|
|
138
110
|
}
|
|
139
111
|
}
|
|
@@ -162,6 +134,57 @@ export class ShaclNode extends HTMLElement {
|
|
|
162
134
|
}
|
|
163
135
|
return subject
|
|
164
136
|
}
|
|
137
|
+
|
|
138
|
+
addPropertyInstance(shaclSubject: Term, config: Config, valueSubject: NamedNode | BlankNode | undefined) {
|
|
139
|
+
let parentElement: HTMLElement = this
|
|
140
|
+
// check if property belongs to a group
|
|
141
|
+
const groupRef = config.shapesGraph.getQuads(shaclSubject as Term, `${PREFIX_SHACL}group`, null, null)
|
|
142
|
+
if (groupRef.length > 0) {
|
|
143
|
+
const groupSubject = groupRef[0].object.value
|
|
144
|
+
if (config.groups.indexOf(groupSubject) > -1) {
|
|
145
|
+
// check if group element already exists, otherwise create it
|
|
146
|
+
let group = this.querySelector(`:scope > .shacl-group[data-subject='${groupSubject}']`) as HTMLElement
|
|
147
|
+
if (!group) {
|
|
148
|
+
group = createShaclGroup(groupSubject, config)
|
|
149
|
+
this.appendChild(group)
|
|
150
|
+
}
|
|
151
|
+
parentElement = group
|
|
152
|
+
} else {
|
|
153
|
+
console.warn('ignoring unknown group reference', groupRef[0], 'existing groups:', config.groups)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// delay creating/appending the property until we finished parsing the node.
|
|
157
|
+
// This is needed to have possible owlImports parsed before creating the property.
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
const property = new ShaclProperty(shaclSubject as NamedNode | BlankNode, this, config, valueSubject)
|
|
160
|
+
// do not add empty properties (i.e. properties with no instances). This can be the case e.g. in viewer mode when there is no data for the respective property.
|
|
161
|
+
if (property.childElementCount > 0) {
|
|
162
|
+
parentElement.appendChild(property)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
tryResolve(subject: Term, valueSubject: NamedNode | BlankNode | undefined, config: Config) {
|
|
168
|
+
const list = config.lists[subject.value]
|
|
169
|
+
if (list?.length) {
|
|
170
|
+
let resolved = false
|
|
171
|
+
if (valueSubject) {
|
|
172
|
+
const resolvedPropertySubjects = resolveShaclOrConstraintOnNode(list, valueSubject, config)
|
|
173
|
+
if (resolvedPropertySubjects.length) {
|
|
174
|
+
for (const propertySubject of resolvedPropertySubjects) {
|
|
175
|
+
this.addPropertyInstance(propertySubject, config, valueSubject)
|
|
176
|
+
}
|
|
177
|
+
resolved = true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!resolved) {
|
|
181
|
+
this.appendChild(createShaclOrConstraint(list, this, config))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.error('list for sh:or/sh:xone not found:', subject, 'existing lists:', config.lists)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
window.customElements.define('shacl-node', ShaclNode)
|
package/src/property-template.ts
CHANGED
|
@@ -31,8 +31,11 @@ const mappers: Record<string, (template: ShaclPropertyTemplate, term: Term) => v
|
|
|
31
31
|
[`${PREFIX_SHACL}languageIn`]: (template, term) => { template.languageIn = template.config.lists[term.value]; template.datatype = DataFactory.namedNode(PREFIX_RDF + 'langString') },
|
|
32
32
|
[`${PREFIX_SHACL}defaultValue`]: (template, term) => { template.defaultValue = term },
|
|
33
33
|
[`${PREFIX_SHACL}hasValue`]: (template, term) => { template.hasValue = term },
|
|
34
|
-
[
|
|
35
|
-
[
|
|
34
|
+
[`${PREFIX_SHACL}qualifiedValueShape`]: (template, term) => { template.qualifiedValueShape = term },
|
|
35
|
+
[`${PREFIX_SHACL}qualifiedMinCount`]: (template, term) => { template.minCount = parseInt(term.value) },
|
|
36
|
+
[`${PREFIX_SHACL}qualifiedMaxCount`]: (template, term) => { template.maxCount = parseInt(term.value) },
|
|
37
|
+
[OWL_PREDICATE_IMPORTS.id]: (template, term) => { template.owlImports.push(term as NamedNode) },
|
|
38
|
+
[SHACL_PREDICATE_CLASS.id]: (template, term) => {
|
|
36
39
|
template.class = term as NamedNode
|
|
37
40
|
// try to find node shape that has requested target class
|
|
38
41
|
const nodeShapes = template.config.shapesGraph.getSubjects(SHACL_PREDICATE_TARGET_CLASS, term, null)
|
|
@@ -45,7 +48,15 @@ const mappers: Record<string, (template: ShaclPropertyTemplate, term: Term) => v
|
|
|
45
48
|
if (list?.length) {
|
|
46
49
|
template.shaclOr = list
|
|
47
50
|
} else {
|
|
48
|
-
console.error('list not found:', term.value, 'existing lists:', template.config.lists)
|
|
51
|
+
console.error('list for sh:or not found:', term.value, 'existing lists:', template.config.lists)
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[`${PREFIX_SHACL}xone`]: (template, term) => {
|
|
55
|
+
const list = template.config.lists[term.value]
|
|
56
|
+
if (list?.length) {
|
|
57
|
+
template.shaclXone = list
|
|
58
|
+
} else {
|
|
59
|
+
console.error('list for sh:xone not found:', term.value, 'existing lists:', template.config.lists)
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
62
|
}
|
|
@@ -76,9 +87,11 @@ export class ShaclPropertyTemplate {
|
|
|
76
87
|
shaclAnd: string | undefined
|
|
77
88
|
shaclIn: string | undefined
|
|
78
89
|
shaclOr: Term[] | undefined
|
|
90
|
+
shaclXone: Term[] | undefined
|
|
79
91
|
languageIn: Term[] | undefined
|
|
80
92
|
datatype: NamedNode | undefined
|
|
81
93
|
hasValue: Term | undefined
|
|
94
|
+
qualifiedValueShape: Term | undefined
|
|
82
95
|
owlImports: NamedNode[] = []
|
|
83
96
|
|
|
84
97
|
config: Config
|
|
@@ -88,6 +101,9 @@ export class ShaclPropertyTemplate {
|
|
|
88
101
|
this.parent = parent
|
|
89
102
|
this.config = config
|
|
90
103
|
this.merge(quads)
|
|
104
|
+
if (this.qualifiedValueShape) {
|
|
105
|
+
this.merge(config.shapesGraph.getQuads(this.qualifiedValueShape, null, null, null))
|
|
106
|
+
}
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
merge(quads: Quad[]): ShaclPropertyTemplate {
|
|
@@ -127,6 +143,9 @@ export class ShaclPropertyTemplate {
|
|
|
127
143
|
if (this.shaclOr) {
|
|
128
144
|
copy.shaclOr = [ ...this.shaclOr ]
|
|
129
145
|
}
|
|
146
|
+
if (this.shaclXone) {
|
|
147
|
+
copy.shaclXone = [ ...this.shaclXone ]
|
|
148
|
+
}
|
|
130
149
|
copy.merge = this.merge.bind(copy)
|
|
131
150
|
copy.clone = this.clone.bind(copy)
|
|
132
151
|
return copy
|
package/src/property.ts
CHANGED
|
@@ -2,12 +2,13 @@ import { BlankNode, DataFactory, NamedNode, Store } from 'n3'
|
|
|
2
2
|
import { Term } from '@rdfjs/types'
|
|
3
3
|
import { ShaclNode } from './node'
|
|
4
4
|
import { focusFirstInputElement } from './util'
|
|
5
|
-
import { createShaclOrConstraint,
|
|
5
|
+
import { createShaclOrConstraint, resolveShaclOrConstraintOnProperty } from './constraints'
|
|
6
6
|
import { Config } from './config'
|
|
7
7
|
import { ShaclPropertyTemplate } from './property-template'
|
|
8
8
|
import { Editor, fieldFactory } from './theme'
|
|
9
9
|
import { toRDF } from './serialize'
|
|
10
10
|
import { findPlugin } from './plugin'
|
|
11
|
+
import { RDF_PREDICATE_TYPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
|
|
11
12
|
|
|
12
13
|
export class ShaclProperty extends HTMLElement {
|
|
13
14
|
template: ShaclPropertyTemplate
|
|
@@ -46,6 +47,22 @@ export class ShaclProperty extends HTMLElement {
|
|
|
46
47
|
const values = valueSubject ? config.dataGraph.getQuads(valueSubject, this.template.path, null, null) : []
|
|
47
48
|
let valuesContainHasValue = false
|
|
48
49
|
for (const value of values) {
|
|
50
|
+
// ignore values that do not conform to this property.
|
|
51
|
+
// this might be the case when there are multiple properties with the same sh:path in a NodeShape.
|
|
52
|
+
if (this.template.node) {
|
|
53
|
+
const targetClasses = config.shapesGraph.getObjects(this.template.node, SHACL_PREDICATE_TARGET_CLASS, null)
|
|
54
|
+
if (targetClasses.length > 0) {
|
|
55
|
+
let hasTargetClass = false
|
|
56
|
+
for (let i = 0; i < targetClasses.length && !hasTargetClass; i++) {
|
|
57
|
+
if (config.dataGraph.getQuads(value.object, RDF_PREDICATE_TYPE, targetClasses[i], null).length > 0) {
|
|
58
|
+
hasTargetClass = true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!hasTargetClass) {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
49
66
|
this.addPropertyInstance(value.object)
|
|
50
67
|
if (this.template.hasValue && value.object.equals(this.template.hasValue)) {
|
|
51
68
|
valuesContainHasValue = true
|
|
@@ -83,22 +100,29 @@ export class ShaclProperty extends HTMLElement {
|
|
|
83
100
|
|
|
84
101
|
addPropertyInstance(value?: Term): HTMLElement {
|
|
85
102
|
let instance: HTMLElement
|
|
86
|
-
if (this.template.shaclOr?.length) {
|
|
103
|
+
if (this.template.shaclOr?.length || this.template.shaclXone?.length) {
|
|
104
|
+
const options = this.template.shaclOr?.length ? this.template.shaclOr : this.template.shaclXone as Term[]
|
|
105
|
+
let resolved = false
|
|
87
106
|
if (value) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
const resolvedOptions = resolveShaclOrConstraintOnProperty(options, value, this.template.config)
|
|
108
|
+
if (resolvedOptions.length) {
|
|
109
|
+
instance = createPropertyInstance(this.template.clone().merge(resolvedOptions), value, true)
|
|
110
|
+
resolved = true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!resolved) {
|
|
114
|
+
instance = createShaclOrConstraint(options, this, this.template.config)
|
|
91
115
|
appendRemoveButton(instance, '')
|
|
92
116
|
}
|
|
93
117
|
} else {
|
|
94
118
|
instance = createPropertyInstance(this.template, value)
|
|
95
119
|
}
|
|
96
120
|
if (this.template.config.editMode) {
|
|
97
|
-
this.insertBefore(instance
|
|
121
|
+
this.insertBefore(instance!, this.addButton!)
|
|
98
122
|
} else {
|
|
99
|
-
this.appendChild(instance)
|
|
123
|
+
this.appendChild(instance!)
|
|
100
124
|
}
|
|
101
|
-
return instance
|
|
125
|
+
return instance!
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
updateControls() {
|
package/src/util.ts
CHANGED
|
@@ -85,22 +85,32 @@ function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode |
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate, indent = 0): InputListEntry[] {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
let instances: Term[]
|
|
89
|
+
// if template has sh:in, then just use that as class instances
|
|
90
|
+
if (template.shaclIn) {
|
|
91
|
+
const list = template.config.lists[template.shaclIn]
|
|
92
|
+
instances = list?.length ? list : []
|
|
93
|
+
} else {
|
|
94
|
+
// find instances in the shapes graph
|
|
95
|
+
instances = template.config.shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
|
|
96
|
+
// find instances in the data graph
|
|
97
|
+
instances.push(...template.config.dataGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, null))
|
|
98
|
+
// find instances in imported taxonomies
|
|
99
|
+
findClassInstancesFromOwlImports(clazz, template, template.config.shapesGraph, instances)
|
|
98
100
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
|
|
102
|
+
const entries = createInputListEntries(instances, template.config.shapesGraph, template.config.languages, indent)
|
|
103
|
+
// build inheritance tree only if sh:in is not defined
|
|
104
|
+
if (template.shaclIn === undefined) {
|
|
105
|
+
for (const subClass of template.config.shapesGraph.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
|
|
102
106
|
entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
|
|
103
107
|
}
|
|
108
|
+
if (template.config.shapesGraph.getQuads(clazz, RDF_PREDICATE_TYPE, OWL_OBJECT_NAMED_INDIVIDUAL, null).length > 0) {
|
|
109
|
+
entries.push(...createInputListEntries([ clazz ], template.config.shapesGraph, template.config.languages, indent))
|
|
110
|
+
for (const subClass of template.config.shapesGraph.getSubjects(SKOS_PREDICATE_BROADER, clazz, null)) {
|
|
111
|
+
entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
return entries
|
|
106
116
|
}
|