diff --git a/demo/index.html b/demo/index.html
index f1f3cd6..6fb83f9 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -6,12 +6,12 @@
Same as "example", but with attribute data-view on the <shacl-form> element.
diff --git a/package-lock.json b/package-lock.json
index d2b24d2..201aa1a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,16 @@
{
"name": "@ulb-darmstadt/shacl-form",
- "version": "1.4.4",
+ "version": "1.4.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ulb-darmstadt/shacl-form",
- "version": "1.4.4",
+ "version": "1.4.9",
"license": "MIT",
"dependencies": {
"@mapbox/mapbox-gl-draw": "^1.4.3",
- "@material/web": "^1.4.1",
+ "@material/web": "^1.5.1",
"bootstrap": "^5.3.3",
"jsonld": "^8.3.2",
"leaflet": "^1.9.4",
@@ -18,7 +18,7 @@
"leaflet.fullscreen": "^3.0.1",
"mapbox-gl": "^3.3.0",
"n3": "^1.17.3",
- "rdf-validate-shacl": "^0.5.4",
+ "rdf-validate-shacl": "^0.5.6",
"uuid": "^9.0.1"
},
"devDependencies": {
@@ -39,6 +39,37 @@
"webpack-dev-server": "^5.0.4"
}
},
+ "../shacl-engine": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@comunica/bindings-factory": "^3.1.0",
+ "@comunica/query-sparql-rdfjs-lite": "^3.1.2",
+ "@rdfjs/namespace": "^2.0.0",
+ "@rdfjs/term-map": "^2.0.0",
+ "@rdfjs/term-set": "^2.0.1",
+ "@rdfjs/to-ntriples": "^3.0.1",
+ "grapoi": "^1.1.1",
+ "lodash": "^4.17.21",
+ "rdf-literal": "^1.3.1",
+ "rdf-validation": "^0.1.0",
+ "readable-stream": "^4.5.1"
+ },
+ "devDependencies": {
+ "@rdfjs/data-model": "^2.0.1",
+ "@rdfjs/dataset": "^2.0.1",
+ "@rdfjs/environment": "^1.0.0",
+ "@rdfjs/normalize": "^2.0.0",
+ "@rdfjs/parser-n3": "^2.0.1",
+ "@rdfjs/traverser": "^0.1.1",
+ "c8": "^10.1.2",
+ "mocha": "^10.2.0",
+ "rdf-test": "^0.1.0",
+ "rdf-utils-fs": "^3.0.0",
+ "stream-chunks": "^1.0.0",
+ "stricter-standard": "^0.3.1"
+ }
+ },
"node_modules/@digitalbazaar/http-client": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-3.4.1.tgz",
@@ -320,9 +351,9 @@
}
},
"node_modules/@material/web": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@material/web/-/web-1.5.0.tgz",
- "integrity": "sha512-aM2TVCU8Ozh3QS5cckRalmWkNCKK3Y6uhB3skIozP6jIqYX1PDhme/URrHGcWk44k2qqmR7R+X8GEBAu8N/NCQ==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@material/web/-/web-1.5.1.tgz",
+ "integrity": "sha512-S9iQV1Biq6JhNpAkqXcsFdVrLW0BC1Tez8C36MEQ/VuhT3YLQySbJkUiG+1U+J1jUqlsG8fT5XsEFbhomCY39w==",
"dependencies": {
"lit": "^2.7.4 || ^3.0.0",
"tslib": "^2.4.0"
@@ -4316,9 +4347,9 @@
}
},
"node_modules/rdf-validate-shacl": {
- "version": "0.5.5",
- "resolved": "https://registry.npmjs.org/rdf-validate-shacl/-/rdf-validate-shacl-0.5.5.tgz",
- "integrity": "sha512-oN6LQh7bzdQEkRMPCW332hxkRH9YANJuylcId41mivJar68vjk8Q4yAjAELxwrZsY2FsGWH4r9x5sPGqKMCUPg==",
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/rdf-validate-shacl/-/rdf-validate-shacl-0.5.6.tgz",
+ "integrity": "sha512-B23lccAy1uIYU9XVoXxK2DFGMV+xBbpvzTpfBJXLKoURjdEOfu/MCih1AHiGJh9PInvl667GvkVD9TmAE2b3Sg==",
"dependencies": {
"@rdfjs/data-model": "^2",
"@rdfjs/dataset": "^2",
diff --git a/package.json b/package.json
index c54e45c..7295ae8 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
},
"dependencies": {
"@mapbox/mapbox-gl-draw": "^1.4.3",
- "@material/web": "^1.4.1",
+ "@material/web": "^1.5.1",
"bootstrap": "^5.3.3",
"jsonld": "^8.3.2",
"leaflet": "^1.9.4",
@@ -57,7 +57,7 @@
"leaflet.fullscreen": "^3.0.1",
"mapbox-gl": "^3.3.0",
"n3": "^1.17.3",
- "rdf-validate-shacl": "^0.5.4",
+ "rdf-validate-shacl": "^0.5.6",
"uuid": "^9.0.1"
}
}
diff --git a/src/config.ts b/src/config.ts
index 01fcf83..5e70a12 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -44,8 +44,9 @@ export class Config {
constructor(theme: Theme, form: HTMLElement) {
this.theme = theme
this.form = form
- this.languages = [... new Set(navigator.languages.flatMap(lang => {
+ this.languages = [...new Set(navigator.languages.flatMap(lang => {
if (lang.length > 2) {
+ // for each 5 letter lang code (e.g. de-DE) append its corresponding 2 letter code (e.g. de) directly afterwards
return [lang, lang.substring(0, 2)]
}
return lang
diff --git a/src/form.ts b/src/form.ts
index 1a5977e..401f2a6 100644
--- a/src/form.ts
+++ b/src/form.ts
@@ -26,7 +26,7 @@ export class ShaclForm extends HTMLElement {
if (this.config.editMode) {
this.validate(true).then(valid => {
this.dispatchEvent(new CustomEvent('change', { bubbles: true, cancelable: false, composed: true, detail: { 'valid': valid } }))
- }).catch(e => { console.log(e) })
+ }).catch(e => { console.warn(e) })
}
})
}
@@ -49,6 +49,8 @@ export class ShaclForm extends HTMLElement {
await this.config.loader.loadGraphs()
// remove loading indicator
this.form.replaceChildren()
+ // reset rendered node references
+ this.config.renderedNodes.clear()
// find root shacl shape
const rootShapeShaclSubject = this.findRootShaclShapeSubject()
if (rootShapeShaclSubject) {
@@ -224,7 +226,6 @@ export class ShaclForm extends HTMLElement {
return messageElement
}
-
private findRootShaclShapeSubject(): NamedNode | undefined {
let rootShapeShaclSubject: NamedNode | null = null
// if data-shape-subject is set, use that
diff --git a/src/node.ts b/src/node.ts
index c36431a..9a4e4dc 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -37,72 +37,90 @@ export class ShaclNode extends HTMLElement {
}
}
this.nodeId = nodeId
- this.dataset.nodeId = this.nodeId.id
-
- const quads = config.shapesGraph.getQuads(shaclSubject, null, null, SHAPES_GRAPH)
- let list: Term[] | undefined
- for (const quad of quads) {
- switch (quad.predicate.id) {
- case `${PREFIX_SHACL}property`:
- let parent: HTMLElement = this
- // check if property belongs to a group
- const groupRef = config.shapesGraph.getQuads(quad.object as Term, `${PREFIX_SHACL}group`, null, SHAPES_GRAPH)
- if (groupRef.length > 0) {
- const groupSubject = groupRef[0].object.value
- if (config.groups.indexOf(groupSubject) > -1) {
- // check if group element already exists, otherwise create it
- let group = this.querySelector(`:scope > .shacl-group[data-subject='${groupSubject}']`) as HTMLElement
- if (!group) {
- group = createShaclGroup(groupSubject, config)
- this.appendChild(group)
+ // check if the form already contains the node/value pair to prevent recursion
+ const id = JSON.stringify([shaclSubject, valueSubject])
+ if (valueSubject && config.renderedNodes.has(id)) {
+ // node/value pair is already rendered in the form, so just display a reference
+ if (label && config.attributes.collapse === null) {
+ const labelElem = document.createElement('label')
+ labelElem.innerText = label
+ this.appendChild(labelElem)
+ }
+ const span = document.createElement('span')
+ span.innerText = valueSubject.id
+ this.appendChild(span)
+ this.style.flexDirection = 'row'
+ } else {
+ if (valueSubject) {
+ config.renderedNodes.add(id)
+ }
+ this.dataset.nodeId = this.nodeId.id
+ const quads = config.shapesGraph.getQuads(shaclSubject, null, null, SHAPES_GRAPH)
+ let list: Term[] | undefined
+
+ for (const quad of quads) {
+ switch (quad.predicate.id) {
+ case `${PREFIX_SHACL}property`:
+ let parent: HTMLElement = this
+ // check if property belongs to a group
+ const groupRef = config.shapesGraph.getQuads(quad.object as Term, `${PREFIX_SHACL}group`, null, SHAPES_GRAPH)
+ if (groupRef.length > 0) {
+ const groupSubject = groupRef[0].object.value
+ if (config.groups.indexOf(groupSubject) > -1) {
+ // check if group element already exists, otherwise create it
+ let group = this.querySelector(`:scope > .shacl-group[data-subject='${groupSubject}']`) as HTMLElement
+ if (!group) {
+ group = createShaclGroup(groupSubject, config)
+ this.appendChild(group)
+ }
+ parent = group
+ } else {
+ console.warn('ignoring unknown group reference', groupRef[0])
+ }
+ }
+ const property = new ShaclProperty(quad.object as NamedNode | BlankNode, config, this.nodeId, valueSubject)
+ // 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.
+ if (property.childElementCount > 0) {
+ parent.appendChild(property)
+ }
+ break;
+ case `${PREFIX_SHACL}and`:
+ // inheritance via sh:and
+ list = config.lists[quad.object.value]
+ if (list?.length) {
+ for (const shape of list) {
+ this.prepend(new ShaclNode(shape as NamedNode, config, valueSubject))
}
- parent = group
- } else {
- console.warn('ignoring unknown group reference', groupRef[0])
}
- }
- const property = new ShaclProperty(quad.object as NamedNode | BlankNode, config, this.nodeId, valueSubject)
- // 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.
- if (property.childElementCount > 0) {
- parent.appendChild(property)
- }
- break;
- case `${PREFIX_SHACL}and`:
- // inheritance via sh:and
- list = config.lists[quad.object.value]
- if (list?.length) {
- for (const shape of list) {
- this.prepend(new ShaclNode(shape as NamedNode, config, valueSubject))
+ else {
+ console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
}
- }
- else {
- console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
- }
- break;
- case `${PREFIX_SHACL}node`:
- // inheritance via sh:node
- this.prepend(new ShaclNode(quad.object as NamedNode, config, valueSubject))
- break;
- case `${PREFIX_SHACL}targetClass`:
- this.targetClass = quad.object as NamedNode
- break;
- case `${PREFIX_SHACL}or`:
- list = config.lists[quad.object.value]
- if (list?.length) {
- this.appendChild(createShaclOrConstraint(list, this, config))
- }
- else {
- console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
- }
- break;
+ break;
+ case `${PREFIX_SHACL}node`:
+ // inheritance via sh:node
+ this.prepend(new ShaclNode(quad.object as NamedNode, config, valueSubject))
+ break;
+ case `${PREFIX_SHACL}targetClass`:
+ this.targetClass = quad.object as NamedNode
+ break;
+ case `${PREFIX_SHACL}or`:
+ list = config.lists[quad.object.value]
+ if (list?.length) {
+ this.appendChild(createShaclOrConstraint(list, this, config))
+ }
+ else {
+ console.error('list not found:', quad.object.value, 'existing lists:', config.lists)
+ }
+ break;
+ }
}
- }
- if (label) {
- const header = document.createElement('h1')
- header.innerText = label
- this.prepend(header)
+ if (label) {
+ const header = document.createElement('h1')
+ header.innerText = label
+ this.prepend(header)
+ }
}
}
diff --git a/src/property-template.ts b/src/property-template.ts
index bebb31d..34ce127 100644
--- a/src/property-template.ts
+++ b/src/property-template.ts
@@ -91,8 +91,7 @@ export class ShaclPropertyTemplate {
}
// provide best fitting label for UI
this.label = this.name?.value || findLabel(quads, this.config.languages)
- if (!this.label && !this.node && !this.shaclAnd) {
- // force label value only for non-node properties to avoid nested
in UI
+ if (!this.label && !this.shaclAnd) {
this.label = this.path ? removePrefixes(this.path, this.config.prefixes) : 'unknown'
}
// resolve extended shapes
diff --git a/src/property.ts b/src/property.ts
index bb4845e..83b82ee 100644
--- a/src/property.ts
+++ b/src/property.ts
@@ -127,10 +127,7 @@ export class ShaclProperty extends HTMLElement {
if (instance.firstChild instanceof ShaclNode) {
const quadCount = graph.size
const shapeSubject = instance.firstChild.toRDF(graph)
- // check if shape generated at least one quad. if not, omit path for this property.
- if (graph.size > quadCount) {
- graph.addQuad(subject, pathNode, shapeSubject)
- }
+ graph.addQuad(subject, pathNode, shapeSubject)
} else {
const editor = instance.querySelector('.editor') as Editor
const value = toRDF(editor)