Skip to content

Commit

Permalink
prevent recursion in case of circular references
Browse files Browse the repository at this point in the history
  • Loading branch information
s-tittel committed Jul 24, 2024
1 parent 4d5c835 commit 8e7650a
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 84 deletions.
6 changes: 3 additions & 3 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>&lt;shacl-form&gt; demo</title>
<link rel="stylesheet" href="./style.css">
<!-- "@ulb-darmstadt/shacl-form": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/[email protected].6/dist/" -->
<!-- "@ulb-darmstadt/shacl-form": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/[email protected].9/dist/" -->
<!-- "@ulb-darmstadt/shacl-form": "./" -->
<script type="importmap">
{
"imports": {
"@ulb-darmstadt/shacl-form/": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/[email protected].8/dist/"
"@ulb-darmstadt/shacl-form/": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/[email protected].9/dist/"
}
}
</script>
Expand Down Expand Up @@ -225,7 +225,7 @@ <h1>&lt;shacl-form&gt; demo</h1>
})
</script>
</template>

<template id="template-viewer-mode">
<p class="mb-1">Same as <a href="#example">"example"</a>, but with attribute <span class="code">data-view</span> on the &lt;shacl-form&gt; element.</p>
<div class="wrapper">
Expand Down
51 changes: 41 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@
},
"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",
"leaflet-editable": "^1.2.0",
"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"
}
}
3 changes: 2 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) })
}
})
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
138 changes: 78 additions & 60 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/property-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <h1> in UI
if (!this.label && !this.shaclAnd) {
this.label = this.path ? removePrefixes(this.path, this.config.prefixes) : 'unknown'
}
// resolve extended shapes
Expand Down
5 changes: 1 addition & 4 deletions src/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 8e7650a

Please sign in to comment.