@ulb-darmstadt/shacl-form 1.8.2 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
@@ -71,7 +71,7 @@
71
71
  "uuid": "^11.1.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@ro-kit/ui-widgets": "^0.0.29",
74
+ "@ro-kit/ui-widgets": "^0.0.31",
75
75
  "mdui": "^2.1.4"
76
76
  }
77
77
  }
package/src/constants.ts CHANGED
@@ -18,6 +18,8 @@ export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
18
18
  export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
19
19
  export const RDFS_PREDICATE_SUBCLASS_OF = DataFactory.namedNode(PREFIX_RDFS + 'subClassOf')
20
20
  export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports')
21
+ export const SKOS_PREDICATE_BROADER = DataFactory.namedNode(PREFIX_SKOS + 'broader')
22
+ export const SKOS_PREDICATE_NARROWER = DataFactory.namedNode(PREFIX_SKOS + 'narrower')
21
23
  export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
22
24
  export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
23
25
  export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
package/src/property.ts CHANGED
@@ -294,6 +294,7 @@ function appendRemoveButton(instance: HTMLElement, label: string, forceRemovable
294
294
  removeButton.classList.add('remove-button', 'clear')
295
295
  removeButton.title = 'Remove ' + label
296
296
  removeButton.dense = true
297
+ removeButton.icon = true
297
298
  removeButton.addEventListener('click', _ => {
298
299
  instance.classList.remove('fadeIn')
299
300
  instance.classList.add('fadeOut')
package/src/styles.css CHANGED
@@ -13,7 +13,7 @@ shacl-property:not(.may-remove) > .property-instance > .remove-button:not(.persi
13
13
  shacl-property:not(.may-remove) > .shacl-or-constraint > .remove-button:not(.persistent) { visibility: hidden; }
14
14
  .mode-view .shacl-group:not(:has(shacl-property)) { display: none; }
15
15
  .property-instance, .shacl-or-constraint { display: flex; align-items: flex-start; padding: 4px 0; width: 100%; position: relative; }
16
- .shacl-or-constraint > div { display: flex; flex-grow: 1; }
16
+ .shacl-or-constraint > div { display: flex; flex-grow: 1; align-items: flex-start; }
17
17
  .shacl-or-constraint label { display: inline-block; word-break: break-word; width: var(--label-width); line-height: 1em; padding-top: 0.15em; padding-right: 1em; flex-shrink: 0; position: relative; }
18
18
  .property-instance label[title] { cursor: help; text-decoration: underline dashed #AAA; }
19
19
  .property-instance.linked label:after, label.linked:after { content: '\1F517'; font-size: 0.6em; padding-left: 6px; }
@@ -22,7 +22,7 @@ shacl-property:not(.may-remove) > .shacl-or-constraint > .remove-button:not(.per
22
22
  .editor:not([type='checkbox']) { flex-grow: 1; }
23
23
  textarea.editor { resize: vertical; }
24
24
  .lang-chooser { border: 0; background-color: #e9e9ed; padding: 2px 4px; align-self: flex-start; }
25
- .validation-error { position: absolute; left: calc(var(--label-width) - 1em); top: 0.5em; color: red; cursor: help; }
25
+ .validation-error { position: absolute; left: calc(var(--label-width) - 1em); color: red; cursor: help; }
26
26
  .validation-error::before { content: '\26a0' }
27
27
  .validation-error.node { left: -1em; }
28
28
  .invalid > .editor { border-color: red !important; }
package/src/util.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Literal, NamedNode, Prefixes, Quad, Store } from 'n3'
2
- import { DATA_GRAPH, PREFIX_FOAF, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH } from './constants'
1
+ import { Literal, NamedNode, Prefixes, Quad, Term as N3Term, Store } from 'n3'
2
+ import { DATA_GRAPH, PREFIX_FOAF, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH, SKOS_PREDICATE_BROADER, SKOS_PREDICATE_NARROWER } from './constants'
3
3
  import { Term } from '@rdfjs/types'
4
4
  import { InputListEntry } from './theme'
5
5
  import { ShaclPropertyTemplate } from './property-template'
@@ -83,42 +83,67 @@ function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode |
83
83
  }
84
84
 
85
85
  export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate): InputListEntry[] {
86
- let instances: Term[]
87
86
  // if template has sh:in, then just use that as class instances
88
87
  if (template.shaclIn) {
89
88
  const list = template.config.lists[template.shaclIn]
90
- instances = list?.length ? list : []
89
+ return createInputListEntries(list?.length ? list : [], template.config.store, template.config.languages)
91
90
  } else {
92
- // find instances in the shapes graph
93
- instances = template.config.store.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
91
+ const instances = template.config.store.getSubjects(RDF_PREDICATE_TYPE, clazz, SHAPES_GRAPH)
94
92
  // find instances in the data graph
95
93
  instances.push(...template.config.store.getSubjects(RDF_PREDICATE_TYPE, clazz, DATA_GRAPH))
96
94
  // find instances in imported taxonomies
97
95
  findClassInstancesFromOwlImports(clazz, template, template.config.store, instances)
98
- }
99
96
 
100
- const entries = createInputListEntries(instances, template.config.store, template.config.languages)
101
- // build inheritance tree only if sh:in is not defined
102
- if (template.shaclIn === undefined) {
103
- // find sub classes via rdfs:subClassOf
104
- for (const subClass of template.config.store.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
105
- const subClassIntances = findInstancesOf(subClass as NamedNode, template)
106
- for (const subClassIntance of subClassIntances) {
107
- let isChild = false
108
- // check if found sub class also is an instance of its super class. if yes, add it to the children of the InputListEntry.
109
- for (const entry of entries) {
110
- if (template.config.store.countQuads(subClassIntance.value, RDF_PREDICATE_TYPE, entry.value, null) > 0) {
111
- entry.children!.push(subClassIntance)
112
- isChild = true
113
- }
114
- }
115
- if (!isChild) {
116
- entries.push(subClassIntance)
117
- }
97
+ // structures needed for build a class instance hierarchy
98
+ const nodes = new Map<string, InputListEntry>() // URI -> InputListEntry
99
+ const childToParent = new Map<string, string>() // URI -> parentURI
100
+
101
+ // Step 1: Initialize all instances as InputListEntry's with no children
102
+ for (const instance of instances) {
103
+ nodes.set(instance.id, { value: instance, label: findLabel(template.config.store.getQuads(instance, null, null, null), template.config.languages), children: [] })
104
+ }
105
+
106
+ // Step 2: Record broader/narrower relationships (child -> parent)
107
+ for (const instance of instances) {
108
+ appendSkosBroaderNarrower(instance, nodes, childToParent, template)
109
+ }
110
+
111
+ // Step 3: Build hierarchy by nesting children under parents
112
+ for (const [child, parent] of childToParent.entries()) {
113
+ nodes.get(parent)!.children!.push(nodes.get(child)!)
114
+ }
115
+
116
+ // Step 4: Find root nodes (no broader relationship)
117
+ const roots: InputListEntry[] = []
118
+ for (const [uri, node] of nodes.entries()) {
119
+ if (!childToParent.has(uri)) {
120
+ roots.push(node)
118
121
  }
119
122
  }
123
+
124
+ // Step 5: Add sub class instances
125
+ for (const subClass of template.config.store.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
126
+ roots.push(...findInstancesOf(subClass as NamedNode, template))
127
+ }
128
+ return roots
129
+ }
130
+ }
131
+
132
+ function appendSkosBroaderNarrower(subject: N3Term, nodes: Map<string, InputListEntry>, childToParent: Map<string, string>, template: ShaclPropertyTemplate) {
133
+ for (const parent of template.config.store.getObjects(subject, SKOS_PREDICATE_BROADER, null)) {
134
+ childToParent.set(subject.id, parent.id)
135
+ if (!nodes.has(parent.id)) {
136
+ nodes.set(parent.id, { value: parent, label: findLabel(template.config.store.getQuads(parent, null, null, null), template.config.languages), children: [] })
137
+ }
138
+ appendSkosBroaderNarrower(parent, nodes, childToParent, template)
139
+ }
140
+ for (const child of template.config.store.getObjects(subject, SKOS_PREDICATE_NARROWER, null)) {
141
+ childToParent.set(child.id, subject.id)
142
+ if (!nodes.has(child.id)) {
143
+ nodes.set(child.id, { value: child, label: findLabel(template.config.store.getQuads(child, null, null, null), template.config.languages), children: [] })
144
+ }
145
+ appendSkosBroaderNarrower(child, nodes, childToParent, template)
120
146
  }
121
- return entries
122
147
  }
123
148
 
124
149
  export function isURL(input: string): boolean {