@ulb-darmstadt/shacl-form 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/config.d.ts +4 -5
- package/dist/constants.d.ts +15 -12
- package/dist/constraints.d.ts +5 -4
- package/dist/exports.d.ts +2 -1
- package/dist/form-bootstrap.d.ts +1 -1
- package/dist/form-bootstrap.js +361 -2
- package/dist/form-default.d.ts +1 -1
- package/dist/form-default.js +350 -2
- package/dist/form-material.d.ts +1 -1
- package/dist/form-material.js +670 -2
- package/dist/form.d.ts +3 -2
- package/dist/node-template.d.ts +17 -0
- package/dist/node.d.ts +5 -1
- package/dist/plugins/leaflet.d.ts +2 -4
- package/dist/plugins/leaflet.js +720 -2
- package/dist/plugins/mapbox.d.ts +2 -2
- package/dist/plugins/mapbox.js +2764 -2
- package/dist/property-template.d.ts +2 -1
- package/dist/property.d.ts +6 -2
- package/dist/theme.d.ts +2 -2
- package/dist/themes/default.d.ts +3 -3
- package/dist/themes/material.d.ts +2 -3
- package/package.json +26 -14
- package/src/config.ts +11 -10
- package/src/constants.ts +4 -1
- package/src/constraints.ts +75 -31
- package/src/exports.ts +2 -1
- package/src/form.ts +32 -17
- package/src/group.ts +1 -1
- package/src/loader.ts +12 -13
- package/src/node-template.ts +82 -0
- package/src/node.ts +88 -63
- package/src/plugins/leaflet.ts +2 -2
- package/src/plugins/mapbox.ts +4 -4
- package/src/property-template.ts +19 -8
- package/src/property.ts +168 -63
- package/src/serialize.ts +14 -1
- package/src/styles.css +8 -10
- package/src/theme.ts +5 -5
- package/src/themes/bootstrap.ts +1 -1
- package/src/themes/default.css +2 -2
- package/src/themes/default.ts +12 -3
- package/src/themes/material.ts +12 -3
- package/src/util.ts +12 -14
- package/dist/form-bootstrap.js.LICENSE.txt +0 -69
- package/dist/form-default.js.LICENSE.txt +0 -69
- package/dist/form-material.js.LICENSE.txt +0 -69
- package/dist/plugins/file-upload.js +0 -1
- package/dist/plugins/fixed-list.js +0 -1
- package/dist/plugins/leaflet.js.LICENSE.txt +0 -4
- package/dist/plugins/mapbox.js.LICENSE.txt +0 -10
|
@@ -28,13 +28,14 @@ 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;
|
|
34
35
|
qualifiedValueShape: Term | undefined;
|
|
35
36
|
owlImports: NamedNode[];
|
|
36
37
|
config: Config;
|
|
37
|
-
extendedShapes: NamedNode[]
|
|
38
|
+
extendedShapes: NamedNode[];
|
|
38
39
|
constructor(quads: Quad[], parent: ShaclNode, config: Config);
|
|
39
40
|
merge(quads: Quad[]): ShaclPropertyTemplate;
|
|
40
41
|
clone(): ShaclPropertyTemplate;
|
package/dist/property.d.ts
CHANGED
|
@@ -3,12 +3,16 @@ import { Term } from '@rdfjs/types';
|
|
|
3
3
|
import { ShaclNode } from './node';
|
|
4
4
|
import { Config } from './config';
|
|
5
5
|
import { ShaclPropertyTemplate } from './property-template';
|
|
6
|
+
import { RokitSelect } from '@ro-kit/ui-widgets';
|
|
6
7
|
export declare class ShaclProperty extends HTMLElement {
|
|
7
8
|
template: ShaclPropertyTemplate;
|
|
8
|
-
addButton:
|
|
9
|
+
addButton: RokitSelect | undefined;
|
|
9
10
|
constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode);
|
|
10
11
|
addPropertyInstance(value?: Term): HTMLElement;
|
|
11
12
|
updateControls(): void;
|
|
12
13
|
toRDF(graph: Store, subject: NamedNode | BlankNode): void;
|
|
14
|
+
getRdfClassToLinkOrCreate(): NamedNode<string> | undefined;
|
|
15
|
+
isValueValid(value: Term): boolean;
|
|
16
|
+
createAddButton(): RokitSelect;
|
|
13
17
|
}
|
|
14
|
-
export declare function createPropertyInstance(template: ShaclPropertyTemplate, value?: Term, forceRemovable?: boolean): HTMLElement;
|
|
18
|
+
export declare function createPropertyInstance(template: ShaclPropertyTemplate, value?: Term, forceRemovable?: boolean, linked?: boolean): HTMLElement;
|
package/dist/theme.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type InputListEntry = {
|
|
|
17
17
|
export declare abstract class Theme {
|
|
18
18
|
stylesheet: CSSStyleSheet;
|
|
19
19
|
constructor(styles?: string);
|
|
20
|
-
apply(
|
|
20
|
+
apply(_: HTMLFormElement): void;
|
|
21
21
|
createViewer(label: string, value: Term, template: ShaclPropertyTemplate): HTMLElement;
|
|
22
22
|
abstract createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
|
|
23
23
|
abstract createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
@@ -28,4 +28,4 @@ export declare abstract class Theme {
|
|
|
28
28
|
abstract createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
29
29
|
abstract createButton(label: string, primary: boolean): HTMLElement;
|
|
30
30
|
}
|
|
31
|
-
export declare function fieldFactory(template: ShaclPropertyTemplate, value: Term | null): HTMLElement;
|
|
31
|
+
export declare function fieldFactory(template: ShaclPropertyTemplate, value: Term | null, editable: boolean): HTMLElement;
|
package/dist/themes/default.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Term } from '@rdfjs/types';
|
|
2
|
-
import { ShaclPropertyTemplate } from
|
|
3
|
-
import { Editor, InputListEntry, Theme } from
|
|
2
|
+
import { ShaclPropertyTemplate } from '../property-template';
|
|
3
|
+
import { Editor, InputListEntry, Theme } from '../theme';
|
|
4
4
|
export declare class DefaultTheme extends Theme {
|
|
5
5
|
idCtr: number;
|
|
6
6
|
constructor(overiddenCss?: string);
|
|
@@ -12,5 +12,5 @@ export declare class DefaultTheme extends Theme {
|
|
|
12
12
|
createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
13
13
|
createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
14
14
|
createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
|
|
15
|
-
createButton(label: string,
|
|
15
|
+
createButton(label: string, _: boolean): HTMLElement;
|
|
16
16
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ShaclPropertyTemplate } from '../property-template';
|
|
2
2
|
import { Term } from '@rdfjs/types';
|
|
3
|
-
import { Theme } from '../theme';
|
|
4
|
-
import { InputListEntry, Editor } from '../theme';
|
|
3
|
+
import { Theme, InputListEntry, Editor } from '../theme';
|
|
5
4
|
export declare class MaterialTheme extends Theme {
|
|
6
5
|
constructor();
|
|
7
6
|
createDefaultTemplate(label: string, value: Term | null, required: boolean, editor: Editor, template?: ShaclPropertyTemplate): HTMLElement;
|
|
@@ -9,7 +8,7 @@ export declare class MaterialTheme extends Theme {
|
|
|
9
8
|
createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
10
9
|
createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement;
|
|
11
10
|
createBooleanEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
12
|
-
createDateEditor(
|
|
11
|
+
createDateEditor(_: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
13
12
|
createLangStringEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
14
13
|
createFileEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement;
|
|
15
14
|
createButton(label: string, primary: boolean): HTMLElement;
|
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulb-darmstadt/shacl-form",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
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
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/form-default.js",
|
|
12
|
+
"types": "./dist/form-default.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"files": [
|
|
9
16
|
"dist",
|
|
10
17
|
"src"
|
|
@@ -26,38 +33,43 @@
|
|
|
26
33
|
"viewer"
|
|
27
34
|
],
|
|
28
35
|
"scripts": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
36
|
+
"dev": "vite serve ./demo --host",
|
|
37
|
+
"build": "tsc && npm run build-default && npm run build-bootstrap && npm run build-material && npm run build-leaflet && npm run build-mapbox",
|
|
38
|
+
"preview": "vite preview",
|
|
31
39
|
"test": "echo \"no tests specified\" && exit 0",
|
|
32
|
-
"
|
|
40
|
+
"build-default": "vite build -c ./vite.form-default.config.ts",
|
|
41
|
+
"build-bootstrap": "vite build -c ./vite.form-bootstrap.config.ts",
|
|
42
|
+
"build-material": "vite build -c ./vite.form-material.config.ts",
|
|
43
|
+
"build-leaflet": "vite build -c ./vite.plugin-leaflet.config.ts",
|
|
44
|
+
"build-mapbox": "vite build -c ./vite.plugin-mapbox.config.ts"
|
|
33
45
|
},
|
|
34
46
|
"devDependencies": {
|
|
35
47
|
"@types/jsonld": "^1.5.15",
|
|
36
|
-
"@types/leaflet": "^1.9.
|
|
48
|
+
"@types/leaflet": "^1.9.19",
|
|
37
49
|
"@types/leaflet-editable": "^1.2.6",
|
|
38
50
|
"@types/leaflet.fullscreen": "^3.0.2",
|
|
39
51
|
"@types/mapbox__mapbox-gl-draw": "^1.4.9",
|
|
40
52
|
"@types/n3": "^1.26.0",
|
|
41
53
|
"@types/uuid": "^10.0.0",
|
|
42
|
-
"
|
|
43
|
-
"rimraf": "^6.0.1",
|
|
44
|
-
"ts-loader": "^9.5.2",
|
|
54
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
45
55
|
"typescript": "^5.8.3",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"webpack-dev-server": "^5.2.2"
|
|
56
|
+
"vite": "^6.2.5",
|
|
57
|
+
"vite-plugin-dts": "^4.5.3"
|
|
49
58
|
},
|
|
50
59
|
"dependencies": {
|
|
51
60
|
"@mapbox/mapbox-gl-draw": "^1.5.0",
|
|
52
|
-
"bootstrap": "^5.3.
|
|
61
|
+
"bootstrap": "^5.3.7",
|
|
53
62
|
"jsonld": "^8.3.3",
|
|
54
63
|
"leaflet": "^1.9.4",
|
|
55
64
|
"leaflet-editable": "^1.3.1",
|
|
56
65
|
"leaflet.fullscreen": "^4.0.0",
|
|
57
|
-
"mapbox-gl": "^3.
|
|
58
|
-
"mdui": "^2.1.4",
|
|
66
|
+
"mapbox-gl": "^3.13.0",
|
|
59
67
|
"n3": "^1.26.0",
|
|
60
68
|
"shacl-engine": "^1.0.2",
|
|
61
69
|
"uuid": "^11.1.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@ro-kit/ui-widgets": "^0.0.27",
|
|
73
|
+
"mdui": "^2.1.4"
|
|
62
74
|
}
|
|
63
75
|
}
|
package/src/config.ts
CHANGED
|
@@ -36,14 +36,13 @@ export class Config {
|
|
|
36
36
|
editMode = true
|
|
37
37
|
languages: string[]
|
|
38
38
|
|
|
39
|
-
dataGraph = new Store()
|
|
40
39
|
lists: Record<string, Term[]> = {}
|
|
41
40
|
groups: Array<string> = []
|
|
42
41
|
theme: Theme
|
|
43
42
|
form: HTMLElement
|
|
44
43
|
renderedNodes = new Set<string>()
|
|
45
|
-
|
|
46
|
-
private
|
|
44
|
+
valuesGraphId: NamedNode | undefined
|
|
45
|
+
private _store = new Store()
|
|
47
46
|
|
|
48
47
|
constructor(theme: Theme, form: HTMLElement) {
|
|
49
48
|
this.theme = theme
|
|
@@ -80,7 +79,9 @@ export class Config {
|
|
|
80
79
|
// now prepend preferred language at start of the list of languages
|
|
81
80
|
this.languages.unshift(atts.language)
|
|
82
81
|
}
|
|
83
|
-
|
|
82
|
+
if (atts.valuesGraph) {
|
|
83
|
+
this.valuesGraphId = DataFactory.namedNode(atts.valuesGraph)
|
|
84
|
+
}
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
static dataAttributes(): Array<string> {
|
|
@@ -92,15 +93,15 @@ export class Config {
|
|
|
92
93
|
})
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
get
|
|
96
|
-
return this.
|
|
96
|
+
get store() {
|
|
97
|
+
return this._store
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
set
|
|
100
|
-
this.
|
|
101
|
-
this.lists =
|
|
100
|
+
set store(store: Store) {
|
|
101
|
+
this._store = store
|
|
102
|
+
this.lists = store.extractLists()
|
|
102
103
|
this.groups = []
|
|
103
|
-
|
|
104
|
+
store.forSubjects(subject => {
|
|
104
105
|
this.groups.push(subject.id)
|
|
105
106
|
}, RDF_PREDICATE_TYPE, `${PREFIX_SHACL}PropertyGroup`, null)
|
|
106
107
|
}
|
package/src/constants.ts
CHANGED
|
@@ -9,8 +9,10 @@ export const PREFIX_SKOS = 'http://www.w3.org/2004/02/skos/core#'
|
|
|
9
9
|
export const PREFIX_OWL = 'http://www.w3.org/2002/07/owl#'
|
|
10
10
|
export const PREFIX_OA = 'http://www.w3.org/ns/oa#'
|
|
11
11
|
export const PREFIX_DCTERMS = 'http://purl.org/dc/terms/'
|
|
12
|
+
export const PREFIX_FOAF = 'http://xmlns.com/foaf/0.1/'
|
|
12
13
|
|
|
13
|
-
export const SHAPES_GRAPH = DataFactory.namedNode('shapes')
|
|
14
|
+
export const SHAPES_GRAPH = DataFactory.namedNode('loaded-shapes')
|
|
15
|
+
export const DATA_GRAPH = DataFactory.namedNode('loaded-data')
|
|
14
16
|
|
|
15
17
|
export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
|
|
16
18
|
export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
|
|
@@ -20,6 +22,7 @@ export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports
|
|
|
20
22
|
export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
|
|
21
23
|
export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
|
|
22
24
|
export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
|
|
25
|
+
export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
|
|
23
26
|
export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
|
|
24
27
|
export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
|
|
25
28
|
export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
|
package/src/constraints.ts
CHANGED
|
@@ -3,9 +3,8 @@ 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
|
-
import { ShaclPropertyTemplate } from './property-template'
|
|
9
8
|
import { Editor, InputListEntry } from './theme'
|
|
10
9
|
|
|
11
10
|
|
|
@@ -17,25 +16,52 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
|
|
|
17
16
|
optionElements.push({ label: '--- please choose ---', value: '' })
|
|
18
17
|
|
|
19
18
|
if (context instanceof ShaclNode) {
|
|
20
|
-
const properties: ShaclProperty[] = []
|
|
21
|
-
//
|
|
19
|
+
const properties: ShaclProperty[][] = []
|
|
20
|
+
// options can be shacl properties or blank nodes referring to (list of) properties
|
|
21
|
+
let optionsAreReferencedProperties = false
|
|
22
|
+
if (options.length) {
|
|
23
|
+
optionsAreReferencedProperties = config.store.countQuads(options[0], SHACL_PREDICATE_PROPERTY, null, null) > 0
|
|
24
|
+
}
|
|
22
25
|
for (let i = 0; i < options.length; i++) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (optionsAreReferencedProperties) {
|
|
27
|
+
const quads = config.store.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null)
|
|
28
|
+
// option can be single property or list of properties
|
|
29
|
+
const list: ShaclProperty[] = []
|
|
30
|
+
let combinedText = ''
|
|
31
|
+
for (const subject of quads) {
|
|
32
|
+
const property = new ShaclProperty(subject as NamedNode | BlankNode, context, config)
|
|
33
|
+
list.push(property)
|
|
34
|
+
combinedText += (combinedText.length > 1 ? ' / ' : '') + property.template.label
|
|
35
|
+
}
|
|
36
|
+
properties.push(list)
|
|
37
|
+
optionElements.push({ label: combinedText, value: i.toString() })
|
|
38
|
+
} else {
|
|
39
|
+
const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
|
|
40
|
+
properties.push([property])
|
|
41
|
+
optionElements.push({ label: property.template.label, value: i.toString() })
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
const editor = config.theme.createListEditor('Please choose', null, false, optionElements)
|
|
28
45
|
const select = editor.querySelector('.editor') as Editor
|
|
29
46
|
select.onchange = () => {
|
|
30
47
|
if (select.value) {
|
|
31
|
-
|
|
48
|
+
const selectedOptions = properties[parseInt(select.value)]
|
|
49
|
+
let lastAddedProperty: ShaclProperty
|
|
50
|
+
if (selectedOptions.length) {
|
|
51
|
+
lastAddedProperty = selectedOptions[0]
|
|
52
|
+
constraintElement.replaceWith(selectedOptions[0])
|
|
53
|
+
}
|
|
54
|
+
for (let i = 1; i < selectedOptions.length; i++) {
|
|
55
|
+
lastAddedProperty!.after(selectedOptions[i])
|
|
56
|
+
lastAddedProperty = selectedOptions[i]
|
|
57
|
+
}
|
|
32
58
|
}
|
|
33
59
|
}
|
|
34
60
|
constraintElement.appendChild(editor)
|
|
35
61
|
} else {
|
|
36
62
|
const values: Quad[][] = []
|
|
37
63
|
for (let i = 0; i < options.length; i++) {
|
|
38
|
-
const quads = config.
|
|
64
|
+
const quads = config.store.getQuads(options[i], null, null, null)
|
|
39
65
|
if (quads.length) {
|
|
40
66
|
values.push(quads)
|
|
41
67
|
optionElements.push({ label: findLabel(quads, config.languages) || (removePrefixes(quads[0].predicate.value, config.prefixes) + ' = ' + removePrefixes(quads[0].object.value, config.prefixes)), value: i.toString() })
|
|
@@ -54,53 +80,71 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
|
|
|
54
80
|
return constraintElement
|
|
55
81
|
}
|
|
56
82
|
|
|
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
|
-
}
|
|
83
|
+
export function resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term, config: Config): Quad[] {
|
|
62
84
|
if (value instanceof Literal) {
|
|
63
|
-
// value is a literal, try to resolve sh:or by matching on given value datatype
|
|
85
|
+
// value is a literal, try to resolve sh:or/sh:xone by matching on given value datatype
|
|
64
86
|
const valueType = value.datatype
|
|
65
|
-
for (const subject of
|
|
66
|
-
const options =
|
|
87
|
+
for (const subject of subjects) {
|
|
88
|
+
const options = config.store.getQuads(subject, null, null, null)
|
|
67
89
|
for (const quad of options) {
|
|
68
90
|
if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
|
|
69
|
-
return
|
|
91
|
+
return options
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
95
|
} 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
|
-
|
|
77
|
-
|
|
78
|
-
const options = template.config.shapesGraph.getQuads(subject, null, null, null)
|
|
96
|
+
// 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
|
|
97
|
+
const types = config.store.getObjects(value, RDF_PREDICATE_TYPE, null)
|
|
98
|
+
for (const subject of subjects) {
|
|
99
|
+
const options = config.store.getQuads(subject, null, null, null)
|
|
79
100
|
for (const quad of options) {
|
|
80
101
|
if (types.length > 0) {
|
|
81
|
-
// try to find matching sh:node in sh:or values
|
|
102
|
+
// try to find matching sh:node in sh:or/sh:xone values
|
|
82
103
|
if (quad.predicate.value === `${PREFIX_SHACL}node`) {
|
|
83
104
|
for (const type of types) {
|
|
84
|
-
if (
|
|
85
|
-
return
|
|
105
|
+
if (config.store.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
|
|
106
|
+
return options
|
|
86
107
|
}
|
|
87
108
|
}
|
|
88
109
|
}
|
|
89
|
-
// try to find matching sh:class in sh:or values
|
|
110
|
+
// try to find matching sh:class in sh:or/sh:xone values
|
|
90
111
|
if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) {
|
|
91
112
|
for (const type of types) {
|
|
92
113
|
if (quad.object.equals(type)) {
|
|
93
|
-
return
|
|
114
|
+
return options
|
|
94
115
|
}
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
} else if (quad.predicate.equals(SHACL_PREDICATE_NODE_KIND) && quad.object.equals(SHACL_OBJECT_IRI)) {
|
|
98
119
|
// if sh:nodeKind is sh:IRI, just use that
|
|
99
|
-
return
|
|
120
|
+
return options
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.error('couldn\'t resolve sh:or/sh:xone on property for value', value)
|
|
126
|
+
return []
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolveShaclOrConstraintOnNode(subjects: Term[], value: Term, config: Config): Term[] {
|
|
130
|
+
for (const subject of subjects) {
|
|
131
|
+
let subjectMatches = false
|
|
132
|
+
const propertySubjects = config.store.getObjects(subject, SHACL_PREDICATE_PROPERTY, null)
|
|
133
|
+
for (const propertySubject of propertySubjects) {
|
|
134
|
+
const paths = config.store.getObjects(propertySubject, `${PREFIX_SHACL}path`, null)
|
|
135
|
+
for (const path of paths) {
|
|
136
|
+
// this allows partial matches in data or shapes graph on properties
|
|
137
|
+
subjectMatches = config.store.countQuads(value, path, null, null) > 0
|
|
138
|
+
if (subjectMatches) {
|
|
139
|
+
break
|
|
100
140
|
}
|
|
101
141
|
}
|
|
102
142
|
}
|
|
143
|
+
if (subjectMatches) {
|
|
144
|
+
return propertySubjects
|
|
145
|
+
}
|
|
103
146
|
}
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
|
|
148
|
+
console.error('couldn\'t resolve sh:or/sh:xone on node for value', value)
|
|
149
|
+
return []
|
|
106
150
|
}
|
package/src/exports.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export type { InputListEntry, Editor } from './theme'
|
|
2
|
+
export { Theme } from './theme'
|
|
2
3
|
export { Loader, setSharedShapesGraph } from './loader'
|
|
3
4
|
export { Config } from './config'
|
|
4
5
|
export { Plugin, registerPlugin } from './plugin'
|
package/src/form.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ShaclNode } from './node'
|
|
2
2
|
import { Config } from './config'
|
|
3
3
|
import { ClassInstanceProvider, Plugin, listPlugins, registerPlugin } from './plugin'
|
|
4
|
-
import { Store, NamedNode, DataFactory, Quad } from 'n3'
|
|
5
|
-
import { DCTERMS_PREDICATE_CONFORMS_TO, PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
|
|
4
|
+
import { Store, NamedNode, DataFactory, Quad, BlankNode } from 'n3'
|
|
5
|
+
import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS, SHAPES_GRAPH } from './constants'
|
|
6
6
|
import { Editor, Theme } from './theme'
|
|
7
7
|
import { serialize } from './serialize'
|
|
8
8
|
import { Validator } from 'shacl-engine'
|
|
@@ -95,9 +95,13 @@ export class ShaclForm extends HTMLElement {
|
|
|
95
95
|
})
|
|
96
96
|
this.form.appendChild(button)
|
|
97
97
|
}
|
|
98
|
+
// delete bound values from data graph, otherwise validation would be confused
|
|
99
|
+
if (this.config.attributes.valuesSubject) {
|
|
100
|
+
this.removeFromDataGraph(DataFactory.namedNode(this.config.attributes.valuesSubject))
|
|
101
|
+
}
|
|
98
102
|
await this.validate(true)
|
|
99
103
|
}
|
|
100
|
-
} else if (this.config.
|
|
104
|
+
} else if (this.config.store.countQuads(null, null, null, SHAPES_GRAPH) > 0) {
|
|
101
105
|
// raise error only when shapes graph is not empty
|
|
102
106
|
throw new Error('shacl root node shape not found')
|
|
103
107
|
}
|
|
@@ -154,14 +158,14 @@ export class ShaclForm extends HTMLElement {
|
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
this.config.
|
|
158
|
-
this.shape?.toRDF(this.config.
|
|
161
|
+
this.config.store.deleteGraph(this.config.valuesGraphId || '')
|
|
162
|
+
this.shape?.toRDF(this.config.store)
|
|
159
163
|
if (this.shape) {
|
|
160
164
|
// add node target for validation. this is required in case of missing sh:targetClass in root shape
|
|
161
|
-
this.config.
|
|
165
|
+
this.config.store.add(new Quad(this.shape.shaclSubject, DataFactory.namedNode(PREFIX_SHACL + 'targetNode'), this.shape.nodeId, this.config.valuesGraphId))
|
|
162
166
|
}
|
|
163
167
|
try {
|
|
164
|
-
const dataset = this.config.
|
|
168
|
+
const dataset = this.config.store
|
|
165
169
|
const report = await new Validator(dataset, { details: true, factory: DataFactory }).validate({ dataset })
|
|
166
170
|
|
|
167
171
|
for (const result of report.results) {
|
|
@@ -172,10 +176,10 @@ export class ShaclForm extends HTMLElement {
|
|
|
172
176
|
if (result.path?.length) {
|
|
173
177
|
const path = result.path[0].predicates[0]
|
|
174
178
|
// try to find most specific editor elements first
|
|
175
|
-
let invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}'] > .editor`)
|
|
179
|
+
let invalidElements = this.form.querySelectorAll(`:scope shacl-node[data-node-id='${focusNode.id}'] > shacl-property > .property-instance[data-path='${path.id}'] > .editor, :scope shacl-node[data-node-id='${focusNode.id}'] > .shacl-group > shacl-property > .property-instance[data-path='${path.id}'] > .editor`)
|
|
176
180
|
if (invalidElements.length === 0) {
|
|
177
181
|
// if no editors found, select respective node. this will be the case for node shape violations.
|
|
178
|
-
invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] [data-path='${path.id}']`)
|
|
182
|
+
invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] > * > [data-path='${path.id}']`)
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
for (const invalidElement of invalidElements) {
|
|
@@ -236,18 +240,18 @@ export class ShaclForm extends HTMLElement {
|
|
|
236
240
|
// if data-shape-subject is set, use that
|
|
237
241
|
if (this.config.attributes.shapeSubject) {
|
|
238
242
|
rootShapeShaclSubject = DataFactory.namedNode(this.config.attributes.shapeSubject)
|
|
239
|
-
if (this.config.
|
|
243
|
+
if (this.config.store.getQuads(rootShapeShaclSubject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
|
|
240
244
|
console.warn(`shapes graph does not contain requested root shape ${this.config.attributes.shapeSubject}`)
|
|
241
245
|
return
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
248
|
else {
|
|
245
249
|
// if we have a data graph and data-values-subject is set, use shape of that
|
|
246
|
-
if (this.config.attributes.valuesSubject && this.config.
|
|
250
|
+
if (this.config.attributes.valuesSubject && this.config.store.countQuads(null, null, null, DATA_GRAPH) > 0) {
|
|
247
251
|
const rootValueSubject = DataFactory.namedNode(this.config.attributes.valuesSubject)
|
|
248
252
|
const rootValueSubjectTypes = [
|
|
249
|
-
...this.config.
|
|
250
|
-
...this.config.
|
|
253
|
+
...this.config.store.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, DATA_GRAPH),
|
|
254
|
+
...this.config.store.getQuads(rootValueSubject, DCTERMS_PREDICATE_CONFORMS_TO, null, DATA_GRAPH)
|
|
251
255
|
]
|
|
252
256
|
if (rootValueSubjectTypes.length === 0) {
|
|
253
257
|
console.warn(`value subject '${this.config.attributes.valuesSubject}' has neither ${RDF_PREDICATE_TYPE.id} nor ${DCTERMS_PREDICATE_CONFORMS_TO.id} statement`)
|
|
@@ -255,13 +259,13 @@ export class ShaclForm extends HTMLElement {
|
|
|
255
259
|
}
|
|
256
260
|
// if type/conformsTo refers to a node shape, prioritize that over targetClass resolution
|
|
257
261
|
for (const rootValueSubjectType of rootValueSubjectTypes) {
|
|
258
|
-
if (this.config.
|
|
262
|
+
if (this.config.store.getQuads(rootValueSubjectType.object as NamedNode, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length > 0) {
|
|
259
263
|
rootShapeShaclSubject = rootValueSubjectType.object as NamedNode
|
|
260
264
|
break
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
267
|
if (!rootShapeShaclSubject) {
|
|
264
|
-
const rootShapes = this.config.
|
|
268
|
+
const rootShapes = this.config.store.getQuads(null, SHACL_PREDICATE_TARGET_CLASS, rootValueSubjectTypes[0].object, null)
|
|
265
269
|
if (rootShapes.length === 0) {
|
|
266
270
|
console.error(`value subject '${this.config.attributes.valuesSubject}' has no shacl shape definition in the shapes graph`)
|
|
267
271
|
return
|
|
@@ -269,7 +273,7 @@ export class ShaclForm extends HTMLElement {
|
|
|
269
273
|
if (rootShapes.length > 1) {
|
|
270
274
|
console.warn(`value subject '${this.config.attributes.valuesSubject}' has multiple shacl shape definitions in the shapes graph, choosing the first found (${rootShapes[0].subject})`)
|
|
271
275
|
}
|
|
272
|
-
if (this.config.
|
|
276
|
+
if (this.config.store.getQuads(rootShapes[0].subject, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null).length === 0) {
|
|
273
277
|
console.error(`value subject '${this.config.attributes.valuesSubject}' references a shape which is not a NodeShape (${rootShapes[0].subject})`)
|
|
274
278
|
return
|
|
275
279
|
}
|
|
@@ -278,7 +282,7 @@ export class ShaclForm extends HTMLElement {
|
|
|
278
282
|
}
|
|
279
283
|
else {
|
|
280
284
|
// choose first of all defined root shapes
|
|
281
|
-
const rootShapes = this.config.
|
|
285
|
+
const rootShapes = this.config.store.getQuads(null, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, null)
|
|
282
286
|
if (rootShapes.length == 0) {
|
|
283
287
|
console.warn('shapes graph does not contain any root shapes')
|
|
284
288
|
return
|
|
@@ -292,4 +296,15 @@ export class ShaclForm extends HTMLElement {
|
|
|
292
296
|
}
|
|
293
297
|
return rootShapeShaclSubject
|
|
294
298
|
}
|
|
299
|
+
|
|
300
|
+
private removeFromDataGraph(subject: NamedNode | BlankNode) {
|
|
301
|
+
this.config.attributes.valuesSubject
|
|
302
|
+
for (const quad of this.config.store.getQuads(subject, null, null, DATA_GRAPH)) {
|
|
303
|
+
this.config.store.delete(quad)
|
|
304
|
+
if (quad.object.termType === 'NamedNode' || quad.object.termType === 'BlankNode') {
|
|
305
|
+
// recurse
|
|
306
|
+
this.removeFromDataGraph(quad.object)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
295
310
|
}
|
package/src/group.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function createShaclGroup(groupSubject: string, config: Config): HTMLElem
|
|
|
7
7
|
group.dataset['subject'] = groupSubject
|
|
8
8
|
group.classList.add('shacl-group')
|
|
9
9
|
let name = groupSubject
|
|
10
|
-
const quads = config.
|
|
10
|
+
const quads = config.store.getQuads(groupSubject, null, null, null)
|
|
11
11
|
const label = findObjectValueByPredicate(quads, "label", PREFIX_RDFS, config.languages)
|
|
12
12
|
if (label) {
|
|
13
13
|
name = label
|
package/src/loader.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Store, Parser, Quad, Prefixes, NamedNode, DataFactory } from 'n3'
|
|
2
2
|
import { toRDF } from 'jsonld'
|
|
3
|
-
import { DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
|
|
3
|
+
import { DATA_GRAPH, DCTERMS_PREDICATE_CONFORMS_TO, OWL_PREDICATE_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
|
|
4
4
|
import { Config } from './config'
|
|
5
5
|
import { isURL } from './util'
|
|
6
6
|
|
|
@@ -25,31 +25,31 @@ export class Loader {
|
|
|
25
25
|
this.loadedExternalUrls = []
|
|
26
26
|
this.loadedClasses = []
|
|
27
27
|
|
|
28
|
-
let
|
|
29
|
-
const valuesStore = new Store()
|
|
28
|
+
let store = sharedShapesGraph
|
|
30
29
|
this.config.prefixes = {}
|
|
31
30
|
|
|
32
|
-
const promises = [
|
|
33
|
-
if (!
|
|
34
|
-
|
|
35
|
-
promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '',
|
|
31
|
+
const promises: Promise<void>[] = []
|
|
32
|
+
if (!store) {
|
|
33
|
+
store = new Store()
|
|
34
|
+
promises.push(this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '', store, SHAPES_GRAPH))
|
|
36
35
|
}
|
|
36
|
+
promises.push(this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ? this.fetchRDF(this.config.attributes.valuesUrl) : '', store, DATA_GRAPH, new Parser({ blankNodePrefix: '' })))
|
|
37
37
|
await Promise.all(promises)
|
|
38
38
|
|
|
39
39
|
// if shapes graph is empty, but we have the following triples:
|
|
40
40
|
// <valueSubject> a <uri> or <valueSubject> dcterms:conformsTo <uri>
|
|
41
41
|
// then try to load the referenced object into the shapes graph
|
|
42
|
-
if (!sharedShapesGraph &&
|
|
42
|
+
if (!sharedShapesGraph && store?.size == 0 && this.config.attributes.valuesSubject) {
|
|
43
43
|
const shapeCandidates = [
|
|
44
|
-
...
|
|
45
|
-
...
|
|
44
|
+
...store.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, DATA_GRAPH),
|
|
45
|
+
...store.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, DATA_GRAPH)
|
|
46
46
|
]
|
|
47
47
|
const promises: Promise<void>[] = []
|
|
48
48
|
for (const uri of shapeCandidates) {
|
|
49
49
|
const url = this.toURL(uri.value)
|
|
50
50
|
if (url && this.loadedExternalUrls.indexOf(url) < 0) {
|
|
51
51
|
this.loadedExternalUrls.push(url)
|
|
52
|
-
promises.push(this.importRDF(this.fetchRDF(url),
|
|
52
|
+
promises.push(this.importRDF(this.fetchRDF(url), store, SHAPES_GRAPH))
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
try {
|
|
@@ -59,8 +59,7 @@ export class Loader {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
this.config.
|
|
63
|
-
this.config.dataGraph = valuesStore
|
|
62
|
+
this.config.store = store
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
async importRDF(input: string | Promise<string>, store: Store, graph?: NamedNode, parser?: Parser) {
|