Skip to content

Commit

Permalink
new data attribute "data-generate-node-shape-reference"
Browse files Browse the repository at this point in the history
  • Loading branch information
s-tittel committed Aug 1, 2024
1 parent 8e7650a commit 159c8a1
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 20 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion demo/complex-example.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -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" ;
Expand Down
4 changes: 2 additions & 2 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].9/dist/" -->
<!-- "@ulb-darmstadt/shacl-form": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/[email protected].8/dist/" -->
<!-- "@ulb-darmstadt/shacl-form": "./" -->
<script type="importmap">
{
"imports": {
"@ulb-darmstadt/shacl-form/": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form@1.4.9/dist/"
"@ulb-darmstadt/shacl-form/": "https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form@1.5.0/dist/"
}
}
</script>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
11 changes: 7 additions & 4 deletions src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
39 changes: 31 additions & 8 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -12,7 +12,7 @@ const loadedClassesCache: Record<string, Promise<string>> = {}

export class Loader {
private config: Config
private loadedOwlImports: string[] = []
private loadedExternalUrls: string[] = []
private loadedClasses: string[] = []

constructor(config: Config) {
Expand All @@ -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:
// <valueSubject> a <uri> or <valueSubject> dcterms:conformsTo <uri>
// 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<void>[] = []
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
}

Expand All @@ -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))
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/node.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 159c8a1

Please sign in to comment.