diff --git a/README.md b/README.md
index 00cfa3f..7553d85 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,7 @@ data‑ignore‑owl‑imports | By default, `owl:imports` URLs ar
data-view | When set, turns the web component into a viewer that displays the given data graph without editing functionality
data-collapse | When set, `sh:group`s and properties with `sh:node` and `sh:maxCount` != 1 are displayed in a collapsible accordion-like widget to reduce visual complexity of the form. The collapsible element is initially shown closed, except when this attribute's value is `"open"`
data-submit-button | [Ignored when `data-view` attribute is set] Whether to add a submit button to the form. The value of this attribute is used as the button label. `submit` events get emitted only when the form data validates
+data-generate-node-shape-reference | When generating the RDF data graph, <shacl-form> can create a triple that references the root `sh:NodeShape` of the data. Supported values of this attribute are `rdf:type` or `dcterms:conformsTo`. Default is empty, so that no such triple is created
### Element functions
diff --git a/demo/complex-example.ttl b/demo/complex-example.ttl
index 0a8fe40..b5cdfe2 100644
--- a/demo/complex-example.ttl
+++ b/demo/complex-example.ttl
@@ -120,7 +120,7 @@ example:Location
sh:minCount 1 ;
sh:name "Coordinates" ;
sh:path geo:asWKT ;
- sh:pattern "^POINT\\([+\\-]?(?:[0-9]*[.])?[0-9]+ [+\\-]?(?:[0-9]*[.])?[0-9]+\\)|POLYGON\\(\\((?:[+\\-]?(?:[0-9]*[.])?[0-9]+[ ,]?){3,}\\)\\)$"
+ sh:pattern "^POINT\\([+\\-]?(?:[0-9]*[.])?[0-9]+ [+\\-]?(?:[0-9]*[.])?[0-9]+\\)$|^POLYGON\\(\\((?:[+\\-]?(?:[0-9]*[.])?[0-9]+[ ,]?){3,}\\)\\)$"
] ;
sh:property [ dash:singleLine false ;
sh:description "Description of the location" ;
diff --git a/demo/index.html b/demo/index.html
index 6fb83f9..5fa58a1 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -6,12 +6,12 @@
<shacl-form> demo
-
+
diff --git a/package.json b/package.json
index 7295ae8..e1d377e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@ulb-darmstadt/shacl-form",
- "version": "1.4.9",
+ "version": "1.5.0",
"description": "SHACL form generator",
"main": "dist/form-default.js",
"module": "dist/form-default.js",
diff --git a/src/config.ts b/src/config.ts
index 5e70a12..213c4f8 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -23,6 +23,7 @@ export class ElementAttributes {
ignoreOwlImports: string | null = null
collapse: string | null = null
submitButton: string | null = null
+ generateNodeShapeReference: string | null = null
}
export class Config {
diff --git a/src/constants.ts b/src/constants.ts
index e0f6f5e..3697f2d 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -8,11 +8,13 @@ export const PREFIX_RDFS = 'http://www.w3.org/2000/01/rdf-schema#'
export const PREFIX_SKOS = 'http://www.w3.org/2004/02/skos/core#'
export const PREFIX_OWL = 'http://www.w3.org/2002/07/owl#'
export const PREFIX_OA = 'http://www.w3.org/ns/oa#'
+export const PREFIX_DCTERMS = 'http://purl.org/dc/terms/'
export const SHAPES_GRAPH = DataFactory.namedNode('shapes')
export const OWL_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports')
export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
+export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
export const RDFS_PREDICATE_SUBCLASS_OF = DataFactory.namedNode(PREFIX_RDFS + 'subClassOf')
export const SKOS_PREDICATE_BROADER = DataFactory.namedNode(PREFIX_SKOS + 'broader')
export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
diff --git a/src/form.ts b/src/form.ts
index 401f2a6..8ece24b 100644
--- a/src/form.ts
+++ b/src/form.ts
@@ -2,7 +2,7 @@ import { ShaclNode } from './node'
import { Config } from './config'
import { ClassInstanceProvider, Plugin, listPlugins, registerPlugin } from './plugin'
import { Quad, Store, NamedNode, DataFactory } from 'n3'
-import { RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS, SHAPES_GRAPH } from './constants'
+import { DCTERMS_PREDICATE_CONFORMS_TO, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHACL_PREDICATE_TARGET_CLASS, SHAPES_GRAPH } from './constants'
import { Editor, Theme } from './theme'
import { serialize } from './serialize'
import SHACLValidator from 'rdf-validate-shacl'
@@ -240,12 +240,15 @@ export class ShaclForm extends HTMLElement {
// if we have a data graph and data-values-subject is set, use shape of that
if (this.config.attributes.valuesSubject && this.config.dataGraph.size > 0) {
const rootValueSubject = DataFactory.namedNode(this.config.attributes.valuesSubject)
- const rootValueSubjectTypes = this.config.dataGraph.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, null)
+ const rootValueSubjectTypes = [
+ ...this.config.dataGraph.getQuads(rootValueSubject, RDF_PREDICATE_TYPE, null, null),
+ ...this.config.dataGraph.getQuads(rootValueSubject, DCTERMS_PREDICATE_CONFORMS_TO, null, null)
+ ]
if (rootValueSubjectTypes.length === 0) {
- console.warn(`value subject '${this.config.attributes.valuesSubject}' has no ${RDF_PREDICATE_TYPE.id} statement`)
+ console.warn(`value subject '${this.config.attributes.valuesSubject}' has neither ${RDF_PREDICATE_TYPE.id} nor ${DCTERMS_PREDICATE_CONFORMS_TO.id} statement`)
return
}
- // if type refers to a node shape, prioritize that over targetClass resolution
+ // if type/conformsTo refers to a node shape, prioritize that over targetClass resolution
for (const rootValueSubjectType of rootValueSubjectTypes) {
if (this.config.shapesGraph.has(new Quad(rootValueSubjectType.object as NamedNode, RDF_PREDICATE_TYPE, SHACL_OBJECT_NODE_SHAPE, SHAPES_GRAPH))) {
rootShapeShaclSubject = rootValueSubjectType.object as NamedNode
diff --git a/src/loader.ts b/src/loader.ts
index 0ae1da1..38039aa 100644
--- a/src/loader.ts
+++ b/src/loader.ts
@@ -1,6 +1,6 @@
import { Store, Parser, Quad, Prefixes, NamedNode } from 'n3'
import { toRDF } from 'jsonld'
-import { OWL_IMPORTS, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
+import { DCTERMS_PREDICATE_CONFORMS_TO, OWL_IMPORTS, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHAPES_GRAPH } from './constants'
import { Config } from './config'
import { isURL } from './util'
@@ -12,7 +12,7 @@ const loadedClassesCache: Record> = {}
export class Loader {
private config: Config
- private loadedOwlImports: string[] = []
+ private loadedExternalUrls: string[] = []
private loadedClasses: string[] = []
constructor(config: Config) {
@@ -21,19 +21,42 @@ export class Loader {
async loadGraphs() {
// clear local caches
- this.loadedOwlImports = []
+ this.loadedExternalUrls = []
this.loadedClasses = []
- const store = new Store()
+ const shapesStore = new Store()
const valuesStore = new Store()
this.config.prefixes = {}
await Promise.all([
- this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '', store, SHAPES_GRAPH),
+ this.importRDF(this.config.attributes.shapes ? this.config.attributes.shapes : this.config.attributes.shapesUrl ? this.fetchRDF(this.config.attributes.shapesUrl) : '', shapesStore, SHAPES_GRAPH),
this.importRDF(this.config.attributes.values ? this.config.attributes.values : this.config.attributes.valuesUrl ? this.fetchRDF(this.config.attributes.valuesUrl) : '', valuesStore, undefined, new Parser({ blankNodePrefix: '' })),
])
- this.config.shapesGraph = store
+ // if shapes graph is empty, but we have the following triples:
+ // a or dcterms:conformsTo
+ // then try to load the referenced object into the shapes graph
+ if (shapesStore.size == 0 && this.config.attributes.valuesSubject) {
+ const shapeCandidates = [
+ ...valuesStore.getObjects(this.config.attributes.valuesSubject, RDF_PREDICATE_TYPE, null),
+ ...valuesStore.getObjects(this.config.attributes.valuesSubject, DCTERMS_PREDICATE_CONFORMS_TO, null)
+ ]
+ const promises: Promise[] = []
+ for (const uri of shapeCandidates) {
+ const url = this.toURL(uri.value)
+ if (url && this.loadedExternalUrls.indexOf(url) < 0) {
+ this.loadedExternalUrls.push(url)
+ promises.push(this.importRDF(this.fetchRDF(url), shapesStore, SHAPES_GRAPH))
+ }
+ }
+ try {
+ await Promise.all(promises)
+ } catch (e) {
+ console.warn(e)
+ }
+ }
+
+ this.config.shapesGraph = shapesStore
this.config.dataGraph = valuesStore
}
@@ -52,8 +75,8 @@ export class Loader {
if (this.config.attributes.ignoreOwlImports === null && OWL_IMPORTS.equals(quad.predicate)) {
const url = this.toURL(quad.object.value)
// import url only once
- if (url && this.loadedOwlImports.indexOf(url) < 0) {
- this.loadedOwlImports.push(url)
+ if (url && this.loadedExternalUrls.indexOf(url) < 0) {
+ this.loadedExternalUrls.push(url)
dependencies.push(this.importRDF(this.fetchRDF(url), store, graph, parser))
}
}
diff --git a/src/node.ts b/src/node.ts
index 9a4e4dc..3ce1e83 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -1,6 +1,6 @@
import { BlankNode, DataFactory, NamedNode, Store } from 'n3'
import { Term } from '@rdfjs/types'
-import { PREFIX_SHACL, SHAPES_GRAPH, RDF_PREDICATE_TYPE } from './constants'
+import { PREFIX_SHACL, SHAPES_GRAPH, RDF_PREDICATE_TYPE, DCTERMS_PREDICATE_CONFORMS_TO } from './constants'
import { ShaclProperty } from './property'
import { createShaclGroup } from './group'
import { v4 as uuidv4 } from 'uuid'
@@ -134,9 +134,13 @@ export class ShaclNode extends HTMLElement {
if (this.targetClass) {
graph.addQuad(subject, RDF_PREDICATE_TYPE, this.targetClass)
}
- // if this is the root shacl node, add the type predicate
- if (!this.closest('shacl-node shacl-node')) {
- graph.addQuad(subject, RDF_PREDICATE_TYPE, this.shaclSubject)
+ // if this is the root shacl node, check if we should add one of the rdf:type or dcterms:conformsTo predicates
+ if (this.config.attributes.generateNodeShapeReference && !this.closest('shacl-node shacl-node')) {
+ if (this.config.attributes.generateNodeShapeReference === 'rdf:type') {
+ graph.addQuad(subject, RDF_PREDICATE_TYPE, this.shaclSubject)
+ } else if (this.config.attributes.generateNodeShapeReference === 'dcterms:conformsTo') {
+ graph.addQuad(subject, DCTERMS_PREDICATE_CONFORMS_TO, this.shaclSubject)
+ }
}
return subject
}