@ulb-darmstadt/shacl-form
Version:
SHACL form generator
134 lines (122 loc) • 5.51 kB
text/typescript
import { Literal, NamedNode, Prefixes, Quad, Store } from 'n3'
import { OWL_OBJECT_NAMED_INDIVIDUAL, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH, SKOS_PREDICATE_BROADER } from './constants'
import { Term } from '@rdfjs/types'
import { InputListEntry } from './theme'
import { ShaclPropertyTemplate } from './property-template'
import { ShaclNode } from './node'
export function findObjectValueByPredicate(quads: Quad[], predicate: string, prefix: string = PREFIX_SHACL, languages?: string[]): string {
let result = ''
const object = findObjectByPredicate(quads, predicate, prefix, languages)
if (object) {
result = object.value
}
return result
}
export function findObjectByPredicate(quads: Quad[], predicate: string, prefix: string = PREFIX_SHACL, languages?: string[]): Term | undefined {
let candidate: Term | undefined
const prefixedPredicate = prefix + predicate
if (languages?.length) {
for (const language of languages) {
for (const quad of quads) {
if (quad.predicate.value === prefixedPredicate) {
if (quad.object.id.endsWith(`@${language}`)) {
return quad.object
}
else if (quad.object.id.indexOf('@') < 0) {
candidate = quad.object
} else if (!candidate) {
candidate = quad.object
}
}
}
}
} else {
for (const quad of quads) {
if (quad.predicate.value === prefixedPredicate) {
return quad.object
}
}
}
return candidate
}
export function focusFirstInputElement(context: HTMLElement) {
(context.querySelector('input,select,textarea') as HTMLElement)?.focus()
}
export function findLabel(quads: Quad[], languages: string[]): string {
let label = findObjectValueByPredicate(quads, 'prefLabel', PREFIX_SKOS, languages)
if (label) {
return label
}
return findObjectValueByPredicate(quads, 'label', PREFIX_RDFS, languages)
}
export function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[], indent?: number): InputListEntry[] {
const entries: InputListEntry[] = []
for (const subject of subjects) {
entries.push({ value: subject, label: findLabel(shapesGraph.getQuads(subject, null, null, null), languages), indent: indent })
}
return entries
}
export function removePrefixes(id: string, prefixes: Prefixes): string {
for (const key in prefixes) {
// need to ignore type check. 'prefix' is a string and not a NamedNode<string> (seems to be a bug in n3 typings)
// @ts-ignore
id = id.replace(prefixes[key], '')
}
return id
}
function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode | ShaclPropertyTemplate, shapesGraph: Store, instances: Term[], alreadyCheckedImports = new Set<string>()) {
for (const owlImport of context.owlImports) {
if (!alreadyCheckedImports.has(owlImport.id)) {
alreadyCheckedImports.add(owlImport.id)
instances.push(...shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, owlImport))
}
}
if (context.parent) {
findClassInstancesFromOwlImports(clazz, context.parent, shapesGraph, instances, alreadyCheckedImports)
}
}
export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate, indent = 0): InputListEntry[] {
// find instances in the shapes graph
const instances: Term[] = template.config.shapesGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
// find instances in the data graph
instances.push(...template.config.dataGraph.getSubjects(RDF_PREDICATE_TYPE, clazz, null))
// find instances in imported taxonomies
findClassInstancesFromOwlImports(clazz, template, template.config.shapesGraph, instances)
const entries = createInputListEntries(instances, template.config.shapesGraph, template.config.languages, indent)
for (const subClass of template.config.shapesGraph.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
}
if (template.config.shapesGraph.getQuads(clazz, RDF_PREDICATE_TYPE, OWL_OBJECT_NAMED_INDIVIDUAL, null).length > 0) {
entries.push(...createInputListEntries([ clazz ], template.config.shapesGraph, template.config.languages, indent))
for (const subClass of template.config.shapesGraph.getSubjects(SKOS_PREDICATE_BROADER, clazz, null)) {
entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
}
}
return entries
}
export function isURL(input: string): boolean {
let url: URL
try {
url = new URL(input)
} catch (_) {
return false
}
return url.protocol === 'http:' || url.protocol === 'https:'
}
export function prioritizeByLanguage(languages: string[], text1?: Literal, text2?: Literal): Literal | undefined {
if (text1 === undefined) {
return text2
}
if (text2 === undefined) {
return text1
}
const index1 = languages.indexOf(text1.language)
if (index1 < 0) {
return text2
}
const index2 = languages.indexOf(text2.language)
if (index2 < 0) {
return text1
}
return index2 > index1 ? text1 : text2
}