UNPKG

@ulb-darmstadt/shacl-form

Version:
149 lines (141 loc) 7.34 kB
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, SHACL_PREDICATE_PROPERTY } from './constants' import { findLabel, removePrefixes } from './util' 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[] = [] if (context instanceof ShaclNode) { const properties: ShaclProperty[][] = [] // options can be shacl properties or blank nodes referring to (list of) properties let optionsAreReferencedProperties = false if (options.length) { optionsAreReferencedProperties = config.store.countQuads(options[0], SHACL_PREDICATE_PROPERTY, null, null) > 0 } for (let i = 0; i < options.length; i++) { if (optionsAreReferencedProperties) { const quads = config.store.getObjects(options[i] , SHACL_PREDICATE_PROPERTY, null) // option can be single property or list of properties const list: ShaclProperty[] = [] let combinedText = '' for (const subject of quads) { const property = new ShaclProperty(subject as NamedNode | BlankNode, context, config) list.push(property) combinedText += (combinedText.length > 1 ? ' / ' : '') + property.template.label } properties.push(list) optionElements.push({ label: combinedText, value: i.toString() }) } else { 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) { const selectedOptions = properties[parseInt(select.value)] let lastAddedProperty: ShaclProperty if (selectedOptions.length) { lastAddedProperty = selectedOptions[0] constraintElement.replaceWith(selectedOptions[0]) } for (let i = 1; i < selectedOptions.length; i++) { lastAddedProperty!.after(selectedOptions[i]) lastAddedProperty = selectedOptions[i] } } } constraintElement.appendChild(editor) } else { const values: Quad[][] = [] for (let i = 0; i < options.length; i++) { const quads = config.store.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 resolveShaclOrConstraintOnProperty(subjects: Term[], value: Term, config: Config): Quad[] { if (value instanceof Literal) { // value is a literal, try to resolve sh:or/sh:xone by matching on given value datatype const valueType = value.datatype for (const subject of subjects) { const options = config.store.getQuads(subject, null, null, null) for (const quad of options) { if (quad.predicate.value === `${PREFIX_SHACL}datatype` && quad.object.equals(valueType)) { return options } } } } else { // value is a NamedNode or BlankNode, try to resolve sh:or/sh:xone by matching rdf:type of given value with sh:node or sh:class in data graph or shapes graph const types = config.store.getObjects(value, RDF_PREDICATE_TYPE, null) for (const subject of subjects) { const options = config.store.getQuads(subject, null, null, null) for (const quad of options) { if (types.length > 0) { // try to find matching sh:node in sh:or/sh:xone values if (quad.predicate.value === `${PREFIX_SHACL}node`) { for (const type of types) { if (config.store.getQuads(quad.object, SHACL_PREDICATE_TARGET_CLASS, type, null).length > 0) { return options } } } // try to find matching sh:class in sh:or/sh:xone values if (quad.predicate.equals(SHACL_PREDICATE_CLASS)) { for (const type of types) { if (quad.object.equals(type)) { return 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 options } } } } console.error('couldn\'t resolve sh:or/sh:xone on property for value', value) return [] } export function resolveShaclOrConstraintOnNode(subjects: Term[], value: Term, config: Config): Term[] { for (const subject of subjects) { let subjectMatches = false const propertySubjects = config.store.getObjects(subject, SHACL_PREDICATE_PROPERTY, null) for (const propertySubject of propertySubjects) { const paths = config.store.getObjects(propertySubject, `${PREFIX_SHACL}path`, null) for (const path of paths) { // this allows partial matches in data or shapes graph on properties subjectMatches = config.store.countQuads(value, path, null, null) > 0 if (subjectMatches) { break } } } if (subjectMatches) { return propertySubjects } } console.error('couldn\'t resolve sh:or/sh:xone on node for value', value) return [] }