@ulb-darmstadt/shacl-form
Version:
SHACL form generator
106 lines (100 loc) • 5.4 kB
text/typescript
import { BlankNode, Literal, NamedNode, Quad } from 'n3'
import { Term } from '@rdfjs/types'
import { ShaclNode } from "./node"
import { ShaclProperty, createPropertyInstance } from "./property"
import { Config } from './config'
import { PREFIX_SHACL, RDF_PREDICATE_TYPE, SHACL_PREDICATE_CLASS, SHACL_PREDICATE_TARGET_CLASS, SHACL_PREDICATE_NODE_KIND, SHACL_OBJECT_IRI } from './constants'
import { findLabel, removePrefixes } from './util'
import { ShaclPropertyTemplate } from './property-template'
import { Editor, InputListEntry } from './theme'
export function createShaclOrConstraint(options: Term[], context: ShaclNode | ShaclProperty, config: Config): HTMLElement {
const constraintElement = document.createElement('div')
constraintElement.classList.add('shacl-or-constraint')
const optionElements: InputListEntry[] = []
optionElements.push({ label: '--- please choose ---', value: '' })
if (context instanceof ShaclNode) {
const properties: ShaclProperty[] = []
// expect options to be shacl properties
for (let i = 0; i < options.length; i++) {
const property = new ShaclProperty(options[i] as NamedNode | BlankNode, context, config)
properties.push(property)
optionElements.push({ label: property.template.label, value: i.toString() })
}
const editor = config.theme.createListEditor('Please choose', null, false, optionElements)
const select = editor.querySelector('.editor') as Editor
select.onchange = () => {
if (select.value) {
constraintElement.replaceWith(properties[parseInt(select.value)])
}
}
constraintElement.appendChild(editor)
} else {
const values: Quad[][] = []
for (let i = 0; i < options.length; i++) {
const quads = config.shapesGraph.getQuads(options[i], null, null, null)
if (quads.length) {
values.push(quads)
optionElements.push({ label: findLabel(quads, config.languages) || (removePrefixes(quads[0].predicate.value, config.prefixes) + ' = ' + removePrefixes(quads[0].object.value, config.prefixes)), value: i.toString() })
}
}
const editor = config.theme.createListEditor(context.template.label + '?', null, false, optionElements, context.template)
const select = editor.querySelector('.editor') as Editor
select.onchange = () => {
if (select.value) {
constraintElement.replaceWith(createPropertyInstance(context.template.clone().merge(values[parseInt(select.value)]), undefined, true))
}
}
constraintElement.appendChild(editor)
}
return constraintElement
}
export function resolveShaclOrConstraint(template: ShaclPropertyTemplate, value: Term): ShaclPropertyTemplate {
if (!template.shaclOr) {
console.warn('can\'t resolve sh:or because template has no options', template)
return template
}
if (value instanceof Literal) {
// value is a literal, try to resolve sh:or by matching on given value datatype
const valueType = value.datatype
for (const subject of template.shaclOr) {
const options = template.config.shapesGraph.getQuads(subject, null, null, null)
for (const quad of options) {
if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) {
return template.clone().merge(options)
}
}
}
} else {
// 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
let types = template.config.dataGraph.getObjects(value, RDF_PREDICATE_TYPE, null)
types.push(...template.config.shapesGraph.getObjects(value, RDF_PREDICATE_TYPE, null))
for (const subject of template.shaclOr) {
const options = template.config.shapesGraph.getQuads(subject, null, null, null)
for (const quad of options) {
if (types.length > 0) {
// try to find matching sh:node in sh:or values
if (quad.predicate.value === `${PREFIX_SHACL}node`) {
for (const type of types) {
if (template.config.shapesGraph.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) {
return template.clone().merge(options)
}
}
}
// try to find matching sh:class in sh:or values
if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) {
for (const type of types) {
if (quad.object.equals(type)) {
return template.clone().merge(options)
}
}
}
} else if (quad.predicate.equals(SHACL_PREDICATE_NODE_KIND) && quad.object.equals(SHACL_OBJECT_IRI)) {
// if sh:nodeKind is sh:IRI, just use that
return template.clone().merge(options)
}
}
}
}
console.error('couldn\'t resolve sh:or for value', value)
return template
}