@glossarist/concept-browser 0.7.50 → 0.7.52

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.
Files changed (170) hide show
  1. package/cli/index.mjs +32 -0
  2. package/env.d.ts +15 -0
  3. package/package.json +12 -2
  4. package/scripts/__tests__/doctor.test.mjs +147 -0
  5. package/scripts/doctor.mjs +327 -0
  6. package/scripts/generate-data.mjs +136 -0
  7. package/scripts/generate-ontology-data.mjs +3 -3
  8. package/scripts/generate-ontology-schema.mjs +3 -3
  9. package/scripts/lib/agents-turtle.mjs +64 -0
  10. package/scripts/lib/bibliography-turtle.mjs +54 -0
  11. package/scripts/lib/build-activity-turtle.mjs +92 -0
  12. package/scripts/lib/build-cache.mjs +70 -0
  13. package/scripts/lib/dataset-turtle.mjs +79 -0
  14. package/scripts/lib/turtle-escape.mjs +0 -0
  15. package/scripts/lib/version-turtle.mjs +56 -0
  16. package/scripts/lib/vocab-turtle.mjs +64 -0
  17. package/scripts/normalize-yaml.mjs +99 -0
  18. package/scripts/sync-concept-model.mjs +86 -0
  19. package/scripts/validate-shacl.mjs +194 -0
  20. package/src/__fixtures__/concept-shape.ttl +20 -0
  21. package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
  22. package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
  23. package/src/__fixtures__/shacl/good/concept.ttl +8 -0
  24. package/src/__tests__/__fixtures__/concepts.ts +221 -0
  25. package/src/__tests__/adapters/concept-identity.test.ts +76 -0
  26. package/src/__tests__/components/error-boundary.test.ts +109 -0
  27. package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
  28. package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
  29. package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
  30. package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
  31. package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
  32. package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
  33. package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
  34. package/src/__tests__/concept-rdf/differential.test.ts +96 -0
  35. package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
  36. package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
  37. package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
  38. package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
  39. package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
  40. package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
  41. package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
  42. package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
  43. package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
  44. package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
  45. package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
  46. package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
  47. package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
  48. package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
  49. package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
  50. package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
  51. package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
  52. package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
  53. package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
  54. package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
  55. package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
  56. package/src/__tests__/concept-rdf-view.test.ts +136 -0
  57. package/src/__tests__/dataset-style.test.ts +12 -7
  58. package/src/__tests__/errors/errors.test.ts +142 -0
  59. package/src/__tests__/format-downloads.test.ts +47 -65
  60. package/src/__tests__/markdown-lite.test.ts +19 -0
  61. package/src/__tests__/perf/bundle-layout.test.ts +50 -0
  62. package/src/__tests__/perf/serialization-perf.test.ts +121 -0
  63. package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
  64. package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
  65. package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
  66. package/src/__tests__/scripts/build-cache.test.ts +78 -0
  67. package/src/__tests__/scripts/build-integration.test.ts +134 -0
  68. package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
  69. package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
  70. package/src/__tests__/scripts/stryker-config.test.ts +33 -0
  71. package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
  72. package/src/__tests__/scripts/version-turtle.test.ts +72 -0
  73. package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
  74. package/src/__tests__/use-format-registry.test.ts +125 -0
  75. package/src/__tests__/utils/bcp47.test.ts +166 -0
  76. package/src/__tests__/utils/color-theme.test.ts +143 -0
  77. package/src/__tests__/utils/url-safety.test.ts +65 -0
  78. package/src/__tests__/validate-shacl.test.ts +100 -0
  79. package/src/adapters/DatasetAdapter.ts +11 -5
  80. package/src/adapters/GraphDataSource.ts +2 -1
  81. package/src/adapters/UriRouter.ts +2 -1
  82. package/src/adapters/concept-identity.ts +69 -0
  83. package/src/adapters/factory.ts +3 -2
  84. package/src/adapters/model-bridge.ts +2 -1
  85. package/src/adapters/non-verbal/figure-bridge.ts +22 -23
  86. package/src/adapters/non-verbal/formula-bridge.ts +11 -9
  87. package/src/adapters/non-verbal/glossarist-augment.d.ts +133 -0
  88. package/src/adapters/non-verbal/index.ts +12 -9
  89. package/src/adapters/non-verbal/kind.ts +2 -1
  90. package/src/adapters/non-verbal/table-bridge.ts +12 -10
  91. package/src/adapters/non-verbal/types.ts +36 -54
  92. package/src/adapters/non-verbal-resolver.ts +6 -3
  93. package/src/components/AppSidebar.vue +189 -93
  94. package/src/components/ConceptDetail.vue +8 -0
  95. package/src/components/ConceptEditionRail.vue +222 -0
  96. package/src/components/ConceptRdfView.vue +37 -377
  97. package/src/components/DatasetSeriesCard.vue +270 -0
  98. package/src/components/ErrorBoundary.vue +95 -0
  99. package/src/components/FormatDownloads.vue +17 -13
  100. package/src/components/HomeSeriesSection.vue +277 -0
  101. package/src/components/NonVerbalRepDisplay.vue +2 -2
  102. package/src/components/RelationSphere.vue +1672 -0
  103. package/src/components/SidebarSeriesSection.vue +239 -0
  104. package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
  105. package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
  106. package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
  107. package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
  108. package/src/components/concept-rdf/agents-emitter.ts +82 -0
  109. package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
  110. package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
  111. package/src/components/concept-rdf/concept-emitter.ts +443 -0
  112. package/src/components/concept-rdf/dataset-emitter.ts +95 -0
  113. package/src/components/concept-rdf/group-emitter.ts +69 -0
  114. package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
  115. package/src/components/concept-rdf/jsonld-writer.ts +82 -0
  116. package/src/components/concept-rdf/predicates.ts +261 -0
  117. package/src/components/concept-rdf/provenance.ts +80 -0
  118. package/src/components/concept-rdf/rdf-graph.ts +211 -0
  119. package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
  120. package/src/components/concept-rdf/sections-builder.ts +62 -0
  121. package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
  122. package/src/components/concept-rdf/turtle-writer.ts +116 -0
  123. package/src/components/concept-rdf/use-rdf-document.ts +72 -0
  124. package/src/components/concept-rdf/version-emitter.ts +65 -0
  125. package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
  126. package/src/components/figure/FigureDisplay.vue +16 -15
  127. package/src/components/figure/FigureImages.vue +38 -16
  128. package/src/components/figure/figure-image-pick.ts +1 -1
  129. package/src/components/figure/figure-layout.ts +1 -1
  130. package/src/components/formula/FormulaDisplay.vue +11 -9
  131. package/src/components/formula/FormulaExpression.vue +4 -4
  132. package/src/components/non-verbal/NonVerbalCaption.vue +5 -5
  133. package/src/components/non-verbal/NonVerbalSources.vue +3 -11
  134. package/src/components/table/TableDisplay.vue +6 -4
  135. package/src/components/table/TableMarkup.vue +1 -1
  136. package/src/composables/use-color-theme.ts +82 -0
  137. package/src/composables/use-format-registry.ts +42 -0
  138. package/src/composables/use-non-verbal-entity.ts +2 -1
  139. package/src/composables/useDatasetSeries.ts +258 -0
  140. package/src/composables/useSphereProjection.ts +125 -0
  141. package/src/config/group-types.ts +92 -0
  142. package/src/config/types.ts +81 -2
  143. package/src/config/use-site-config.ts +2 -1
  144. package/src/errors.ts +136 -0
  145. package/src/i18n/locales/eng.yml +24 -0
  146. package/src/i18n/locales/fra.yml +24 -0
  147. package/src/stores/vocabulary.ts +3 -1
  148. package/src/style.css +17 -2
  149. package/src/types/agents-version-turtle.d.ts +27 -0
  150. package/src/types/bibliography-turtle.d.ts +12 -0
  151. package/src/types/build-activity-turtle.d.ts +16 -0
  152. package/src/types/build-cache.d.ts +20 -0
  153. package/src/types/dataset-turtle.d.ts +32 -0
  154. package/src/types/normalize-yaml.d.ts +16 -0
  155. package/src/types/turtle-escape.d.ts +6 -0
  156. package/src/types/vocab-turtle.d.ts +13 -0
  157. package/src/utils/asciidoc-lite.ts +11 -6
  158. package/src/utils/bcp47.ts +141 -0
  159. package/src/utils/color-theme-integration.ts +11 -0
  160. package/src/utils/color-theme.ts +129 -0
  161. package/src/utils/dataset-style.ts +31 -6
  162. package/src/utils/locale.ts +6 -14
  163. package/src/utils/markdown-lite.ts +6 -1
  164. package/src/utils/relation-sphere-styling.ts +63 -0
  165. package/src/utils/relationship-categories.ts +30 -0
  166. package/src/utils/url-safety.ts +30 -0
  167. package/src/views/ConceptView.vue +183 -9
  168. package/src/views/DatasetView.vue +6 -0
  169. package/src/views/HomeView.vue +5 -0
  170. package/vite.config.ts +7 -0
@@ -0,0 +1,62 @@
1
+ import type { RdfGraph, RdfResource, RdfTerm } from './rdf-graph';
2
+
3
+ export interface PropValue {
4
+ predicate: string;
5
+ values: string[];
6
+ nested?: boolean;
7
+ }
8
+
9
+ export interface ClassInstance {
10
+ classId: string;
11
+ classLabel: string;
12
+ label: string;
13
+ props: PropValue[];
14
+ }
15
+
16
+ export function buildSections(graph: RdfGraph): ClassInstance[] {
17
+ const out: ClassInstance[] = [];
18
+ for (const r of graph.resources()) {
19
+ out.push(resourceToSection(r));
20
+ }
21
+ return out;
22
+ }
23
+
24
+ function resourceToSection(r: RdfResource): ClassInstance {
25
+ const props: PropValue[] = [];
26
+ const seen = new Set<string>();
27
+
28
+ for (const t of r.triples) {
29
+ const value = formatTerm(t.object);
30
+ if (!value) continue;
31
+ const isNested = t.object.kind === 'blank';
32
+ const key = `${t.predicate}#${isNested ? 'n' : 'f'}#${value}`;
33
+ if (seen.has(key)) continue;
34
+ seen.add(key);
35
+ const prop: PropValue = {
36
+ predicate: t.predicate,
37
+ values: [value],
38
+ };
39
+ if (isNested) prop.nested = true;
40
+ props.push(prop);
41
+ }
42
+
43
+ return {
44
+ classId: r.classId,
45
+ classLabel: r.classLabel,
46
+ label: r.label,
47
+ props,
48
+ };
49
+ }
50
+
51
+ export function formatTerm(term: RdfTerm): string {
52
+ switch (term.kind) {
53
+ case 'iri':
54
+ return term.value;
55
+ case 'literal':
56
+ return term.value;
57
+ case 'blank': {
58
+ const parts = term.triples.map(t => `${t.predicate}: ${formatTerm(t.object)}`);
59
+ return parts.join('; ');
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,101 @@
1
+ import { GLOSS, DCTERMS, RDF, XSD } from './predicates';
2
+ import { RdfGraph, lit, iri, blank, triple } from './rdf-graph';
3
+ import type { RdfTriple } from './rdf-graph';
4
+
5
+ export interface FormulaInput {
6
+ readonly registerId: string;
7
+ readonly formulaId: string;
8
+ readonly expression: string;
9
+ readonly latexForm?: string;
10
+ readonly description?: string;
11
+ readonly lang?: string;
12
+ readonly sourceIri?: string;
13
+ readonly baseUri?: string;
14
+ }
15
+
16
+ export interface TableHeaderCell {
17
+ readonly value: string;
18
+ readonly lang?: string;
19
+ }
20
+
21
+ export interface TableRow {
22
+ readonly cells: readonly string[];
23
+ }
24
+
25
+ export interface TableInput {
26
+ readonly registerId: string;
27
+ readonly tableId: string;
28
+ readonly title?: string;
29
+ readonly caption?: string;
30
+ readonly headers?: readonly TableHeaderCell[];
31
+ readonly rows?: readonly TableRow[];
32
+ readonly markup?: string;
33
+ readonly markupFormat?: string;
34
+ readonly lang?: string;
35
+ readonly baseUri?: string;
36
+ }
37
+
38
+ function base(input: { baseUri?: string }): string {
39
+ return input.baseUri ?? 'https://glossarist.org';
40
+ }
41
+
42
+ export function formulaIri(input: FormulaInput): string {
43
+ return `${base(input)}/${input.registerId}/formula/${input.formulaId}`;
44
+ }
45
+
46
+ export function tableIri(input: TableInput): string {
47
+ return `${base(input)}/${input.registerId}/table/${input.tableId}`;
48
+ }
49
+
50
+ export function emitFormulaGraph(input: FormulaInput): RdfGraph {
51
+ const graph = new RdfGraph();
52
+ const iriStr = formulaIri(input);
53
+ const w = graph.declare(iriStr, {
54
+ types: [GLOSS.Formula],
55
+ label: input.expression,
56
+ classLabel: 'Formula',
57
+ classId: GLOSS.Formula,
58
+ });
59
+ w.literal(GLOSS.expression, input.expression);
60
+ if (input.latexForm) w.literal(GLOSS.latexForm, input.latexForm);
61
+ if (input.description) w.literal(DCTERMS.description, input.description, { lang: input.lang });
62
+ if (input.sourceIri) w.iri('prov:wasDerivedFrom', input.sourceIri);
63
+ return graph;
64
+ }
65
+
66
+ export function emitTableGraph(input: TableInput): RdfGraph {
67
+ const graph = new RdfGraph();
68
+ const iriStr = tableIri(input);
69
+ const w = graph.declare(iriStr, {
70
+ types: [GLOSS.Table],
71
+ label: input.title ?? input.caption ?? input.tableId,
72
+ classLabel: 'Table',
73
+ classId: GLOSS.Table,
74
+ });
75
+
76
+ if (input.title) w.literal(DCTERMS.title, input.title, { lang: input.lang });
77
+ if (input.caption) w.literal(GLOSS.caption, input.caption, { lang: input.lang });
78
+
79
+ if (input.markup != null) {
80
+ const contentTriples: RdfTriple[] = [
81
+ triple(RDF.type, iri('gloss:MarkupTable')),
82
+ triple(GLOSS.content, lit(input.markup)),
83
+ ];
84
+ if (input.markupFormat) contentTriples.push(triple(DCTERMS.format, lit(input.markupFormat)));
85
+ w.blank(GLOSS.content, contentTriples);
86
+ } else if (input.headers && input.headers.length > 0) {
87
+ const contentTriples: RdfTriple[] = [
88
+ triple(RDF.type, iri('gloss:StructuredTable')),
89
+ ];
90
+ for (const h of input.headers) {
91
+ contentTriples.push(triple(GLOSS.hasHeader, lit(h.value, { lang: h.lang ?? input.lang })));
92
+ }
93
+ for (const row of input.rows ?? []) {
94
+ const rowTriples = row.cells.map(c => triple(GLOSS.content, lit(c)));
95
+ contentTriples.push(triple(GLOSS.hasRow, blank(...rowTriples)));
96
+ }
97
+ w.blank(GLOSS.content, contentTriples);
98
+ }
99
+
100
+ return graph;
101
+ }
@@ -0,0 +1,116 @@
1
+ import { RDF_PREFIXES } from './rdf-prefixes';
2
+ import type { RdfGraph, RdfResource, RdfTerm, RdfTriple } from './rdf-graph';
3
+
4
+ const INDENT = ' ';
5
+
6
+ const ABSOLUTE_SCHEMES = new Set(['http', 'https', 'urn', 'file', 'mailto', 'ftp']);
7
+
8
+ function isPrefixedName(s: string): boolean {
9
+ const colonIdx = s.indexOf(':');
10
+ if (colonIdx < 1) return false;
11
+ const prefix = s.slice(0, colonIdx);
12
+ if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(prefix)) return false;
13
+ if (ABSOLUTE_SCHEMES.has(prefix)) return false;
14
+ const local = s.slice(colonIdx + 1);
15
+ return !local.startsWith('//');
16
+ }
17
+
18
+ function formatIri(value: string): string {
19
+ if (!isPrefixedName(value)) return `<${value}>`;
20
+ const colonIdx = value.indexOf(':');
21
+ const prefix = value.slice(0, colonIdx + 1);
22
+ const local = value.slice(colonIdx + 1);
23
+ const escaped = local.replace(/([/])/g, '\\$1');
24
+ return prefix + escaped;
25
+ }
26
+
27
+ export function writeTurtle(graph: RdfGraph): string {
28
+ const lines: string[] = [];
29
+
30
+ for (const p of RDF_PREFIXES) {
31
+ lines.push(`@prefix ${p.prefix}: <${p.iri}> .`);
32
+ }
33
+
34
+ let first = true;
35
+ for (const r of graph.resources()) {
36
+ if (first) {
37
+ lines.push('');
38
+ first = false;
39
+ } else {
40
+ lines.push('');
41
+ }
42
+ writeResource(lines, r);
43
+ }
44
+
45
+ return lines.join('\n');
46
+ }
47
+
48
+ function writeResource(lines: string[], r: RdfResource): void {
49
+ const subjectForm = formatSubject(r.subject);
50
+
51
+ if (r.types.length === 0 && r.triples.length === 0) {
52
+ lines.push(`${subjectForm} .`);
53
+ return;
54
+ }
55
+
56
+ if (r.types.length === 0) {
57
+ const [first, ...rest] = r.triples;
58
+ lines.push(`${subjectForm} ${first.predicate} ${formatObject(first.object)} ;`);
59
+ for (const t of rest) {
60
+ lines.push(`${INDENT}${t.predicate} ${formatObject(t.object)} ;`);
61
+ }
62
+ const last = lines.length - 1;
63
+ lines[last] = lines[last].replace(/ ;$/, ' .');
64
+ return;
65
+ }
66
+
67
+ const head = `${subjectForm} a ${r.types.join(', ')}`;
68
+ if (r.triples.length === 0) {
69
+ lines.push(`${head} .`);
70
+ return;
71
+ }
72
+ lines.push(`${head} ;`);
73
+ for (const t of r.triples) {
74
+ lines.push(`${INDENT}${t.predicate} ${formatObject(t.object)} ;`);
75
+ }
76
+ const last = lines.length - 1;
77
+ lines[last] = lines[last].replace(/ ;$/, ' .');
78
+ }
79
+
80
+ function formatSubject(subject: string): string {
81
+ return formatIri(subject);
82
+ }
83
+
84
+ function formatObject(term: RdfTerm): string {
85
+ switch (term.kind) {
86
+ case 'iri':
87
+ return formatIri(term.value);
88
+ case 'literal':
89
+ return formatLiteral(term.value, term.lang, term.datatype);
90
+ case 'blank':
91
+ return formatBlankNode(term.triples);
92
+ }
93
+ }
94
+
95
+ function formatLiteral(value: string, lang?: string, datatype?: string): string {
96
+ const escaped = escapeLiteral(value);
97
+ let s = `"${escaped}"`;
98
+ if (lang) s += `@${lang}`;
99
+ else if (datatype) s += `^^${datatype}`;
100
+ return s;
101
+ }
102
+
103
+ function formatBlankNode(triples: readonly RdfTriple[]): string {
104
+ if (triples.length === 0) return '[]';
105
+ const parts = triples.map(t => `${t.predicate} ${formatObject(t.object)}`);
106
+ return `[ ${parts.join(' ; ')} ]`;
107
+ }
108
+
109
+ function escapeLiteral(s: string): string {
110
+ return s
111
+ .replace(/\\/g, '\\\\')
112
+ .replace(/"/g, '\\"')
113
+ .replace(/\n/g, '\\n')
114
+ .replace(/\r/g, '\\r')
115
+ .replace(/\t/g, '\\t');
116
+ }
@@ -0,0 +1,72 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import { computed } from 'vue';
3
+ import type { Concept } from 'glossarist';
4
+ import { getClass } from '../../adapters/ontology-schema';
5
+ import { ConceptIdentity } from '../../adapters/concept-identity';
6
+ import { emitConceptGraph } from './concept-emitter';
7
+ import { writeTurtle } from './turtle-writer';
8
+ import { writeJsonLd } from './jsonld-writer';
9
+ import { buildSections } from './sections-builder';
10
+ import type { ClassInstance, PropValue } from './sections-builder';
11
+ import { decorateWithProvenance, runtimeProvenance } from './provenance';
12
+
13
+ export type { ClassInstance, PropValue } from './sections-builder';
14
+
15
+ export interface RdfDocument {
16
+ sections: ComputedRef<ClassInstance[]>;
17
+ turtle: ComputedRef<string>;
18
+ jsonld: ComputedRef<string>;
19
+ typeChain: ComputedRef<string[]>;
20
+ }
21
+
22
+ export interface UseRdfDocumentOptions {
23
+ readonly lazy?: boolean;
24
+ }
25
+
26
+ const SERIALIZER_VERSION =
27
+ typeof __CONCEPT_BROWSER_VERSION__ !== 'undefined'
28
+ ? __CONCEPT_BROWSER_VERSION__
29
+ : 'dev';
30
+
31
+ export function useRdfDocument(
32
+ getConcept: () => Concept,
33
+ getConceptUri: () => string,
34
+ _options: UseRdfDocumentOptions = {},
35
+ ): RdfDocument {
36
+ const identity = computed(() => {
37
+ const concept = getConcept();
38
+ const uri = getConceptUri();
39
+ if (ConceptIdentity.isConceptUri(uri)) {
40
+ const parsed = ConceptIdentity.fromUri(uri);
41
+ if (parsed.localId === concept.id) return parsed;
42
+ }
43
+ return new ConceptIdentity(concept.id, '', '');
44
+ });
45
+
46
+ const safeUri = computed(() => {
47
+ const uri = getConceptUri();
48
+ return uri || identity.value.uri;
49
+ });
50
+
51
+ const emission = computed(() => {
52
+ const result = emitConceptGraph(getConcept(), safeUri.value);
53
+ decorateWithProvenance(
54
+ result.graph,
55
+ safeUri.value,
56
+ runtimeProvenance(SERIALIZER_VERSION, safeUri.value),
57
+ );
58
+ return result;
59
+ });
60
+
61
+ const sections = computed(() => buildSections(emission.value.graph));
62
+ const turtle = computed(() => writeTurtle(emission.value.graph));
63
+ const jsonld = computed(() => writeJsonLd(emission.value.graph));
64
+
65
+ const typeChain = computed(() => {
66
+ const conceptCls = getClass('gloss:Concept');
67
+ if (!conceptCls) return ['owl:Thing', 'skos:Concept', 'gloss:Concept'];
68
+ return ['owl:Thing', ...conceptCls.ancestors, 'gloss:Concept'];
69
+ });
70
+
71
+ return { sections, turtle, jsonld, typeChain };
72
+ }
@@ -0,0 +1,65 @@
1
+ import { PROV, DCTERMS, XSD } from './predicates';
2
+ import { RdfGraph, lit, iri, triple } from './rdf-graph';
3
+
4
+ export interface DatasetVersionInput {
5
+ readonly registerId: string;
6
+ readonly version: string;
7
+ readonly versionIri: string;
8
+ readonly datasetIri: string;
9
+ readonly generatedAt: string;
10
+ readonly previousVersionIri?: string;
11
+ readonly changeSummary?: string;
12
+ readonly associatedAgentIri?: string;
13
+ }
14
+
15
+ export function emitVersionGraph(input: DatasetVersionInput): RdfGraph {
16
+ const graph = new RdfGraph();
17
+ const w = graph.declare(input.versionIri, {
18
+ types: [PROV.Entity],
19
+ label: `${input.registerId} version ${input.version}`,
20
+ classLabel: 'Version',
21
+ classId: PROV.Entity,
22
+ });
23
+ w.literal(DCTERMS.isVersionOf, input.datasetIri);
24
+ if (input.previousVersionIri) {
25
+ w.iri(PROV.wasRevisionOf, input.previousVersionIri);
26
+ }
27
+ w.literal(PROV.generatedAtTime, input.generatedAt, { datatype: XSD.dateTime });
28
+ if (input.changeSummary) w.literal(DCTERMS.description, input.changeSummary);
29
+ if (input.associatedAgentIri) w.iri(PROV.wasAssociatedWith, input.associatedAgentIri);
30
+ return graph;
31
+ }
32
+
33
+ export interface VersionHistoryEntry {
34
+ readonly version: string;
35
+ readonly generatedAt: string;
36
+ readonly changeSummary?: string;
37
+ }
38
+
39
+ export interface VersionEmitAllInput {
40
+ readonly registerId: string;
41
+ readonly datasetIri: string;
42
+ readonly versions: readonly VersionHistoryEntry[];
43
+ readonly associatedAgentIri?: string;
44
+ }
45
+
46
+ export function emitVersionHistory(input: VersionEmitAllInput): RdfGraph {
47
+ const graph = new RdfGraph();
48
+ let previousIri: string | undefined;
49
+ for (const v of input.versions) {
50
+ const versionIri = `${input.datasetIri}versions/${v.version}`;
51
+ const single = emitVersionGraph({
52
+ registerId: input.registerId,
53
+ version: v.version,
54
+ versionIri,
55
+ datasetIri: input.datasetIri,
56
+ generatedAt: v.generatedAt,
57
+ previousVersionIri: previousIri,
58
+ changeSummary: v.changeSummary,
59
+ associatedAgentIri: input.associatedAgentIri,
60
+ });
61
+ graph.merge(single);
62
+ previousIri = versionIri;
63
+ }
64
+ return graph;
65
+ }
@@ -0,0 +1,62 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { SKOS } from './predicates';
5
+ import { RdfGraph } from './rdf-graph';
6
+ import type { RdfGraph as RdfGraphType } from './rdf-graph';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const VOCAB_PATH = join(__dirname, '..', '..', '..', 'data', 'glossarist-vocab.json');
10
+
11
+ export interface VocabTerm {
12
+ readonly iri: string;
13
+ readonly label: string;
14
+ readonly group?: string;
15
+ }
16
+
17
+ export interface VocabScheme {
18
+ readonly schemeIri: string;
19
+ readonly label: string;
20
+ readonly terms: readonly VocabTerm[];
21
+ }
22
+
23
+ interface VocabFile {
24
+ readonly schemes: readonly VocabScheme[];
25
+ }
26
+
27
+ let cached: VocabFile | undefined;
28
+
29
+ export function loadVocabulary(path: string = VOCAB_PATH): VocabFile {
30
+ if (cached && path === VOCAB_PATH) return cached;
31
+ const text = readFileSync(path, 'utf8');
32
+ cached = JSON.parse(text) as VocabFile;
33
+ return cached;
34
+ }
35
+
36
+ export const VOCAB_SCHEMES: readonly VocabScheme[] = loadVocabulary().schemes;
37
+
38
+ export function emitVocabularyGraph(graph: RdfGraphType = new RdfGraph()): RdfGraphType {
39
+ for (const scheme of VOCAB_SCHEMES) {
40
+ const schemeW = graph.declare(scheme.schemeIri, {
41
+ types: [SKOS.ConceptScheme],
42
+ label: scheme.label,
43
+ classLabel: 'ConceptScheme',
44
+ classId: SKOS.ConceptScheme,
45
+ });
46
+ for (const term of scheme.terms) {
47
+ const termW = graph.declare(term.iri, {
48
+ types: [SKOS.Concept],
49
+ label: term.label,
50
+ classLabel: 'Concept',
51
+ classId: SKOS.Concept,
52
+ });
53
+ termW.iri(SKOS.inScheme, scheme.schemeIri);
54
+ schemeW.iri(SKOS.hasTopConcept, term.iri);
55
+ }
56
+ }
57
+ return graph;
58
+ }
59
+
60
+ export function listVocabSchemes(): readonly VocabScheme[] {
61
+ return VOCAB_SCHEMES;
62
+ }
@@ -10,7 +10,7 @@
10
10
  * so `{{fig:id}}` mentions can scroll to it via the cross-ref composable.
11
11
  */
12
12
  import { computed, ref } from 'vue';
13
- import type { Figure } from '../../adapters/non-verbal/types';
13
+ import type { Figure } from 'glossarist';
14
14
  import { useNonVerbalEntity } from '../../composables/use-non-verbal-entity';
15
15
  import { resolveFallbackChain } from '../../utils/locale';
16
16
  import { anchorId } from '../../utils/non-verbal-anchor';
@@ -34,39 +34,40 @@ const id = () => props.entityId;
34
34
  const { entity, state, error } = useNonVerbalEntity(k, ds, id);
35
35
 
36
36
  const fallbackChain = computed(() => resolveFallbackChain(props.datasetLocales));
37
- const layout = computed(() => entity.value ? deriveLayout(entity.value as Figure) : 'single');
37
+ const fig = computed(() => entity.value as Figure | null);
38
+ const layout = computed(() => fig.value ? deriveLayout(fig.value) : 'single');
38
39
  const anchor = computed(() => anchorId('figure', props.datasetId, props.entityId));
39
40
  const descriptionId = computed(() => `${anchor.value}-desc`);
40
41
 
41
- const topLevelImages = computed(() => (entity.value as Figure | null)?.images ?? []);
42
+ const topLevelImages = computed(() => fig.value?.images ?? []);
42
43
  </script>
43
44
 
44
45
  <template>
45
46
  <figure
46
- v-if="entity && state === 'loaded'"
47
+ v-if="fig && state === 'loaded'"
47
48
  :id="anchor"
48
49
  :class="`figure figure--${layout}`"
49
50
  >
50
51
  <div v-if="topLevelImages.length" :class="`figure__media figure__media--${layout}`">
51
52
  <FigureImages
52
53
  :images="topLevelImages"
53
- :alt="(entity as Figure).alt"
54
+ :alt="fig.alt"
54
55
  :dataset-id="datasetId"
55
56
  :locale="locale"
56
57
  :fallback-chain="fallbackChain"
57
- :hasDescription="!!(entity as Figure).description && Object.keys((entity as Figure).description ?? {}).length > 0"
58
+ :hasDescription="!!fig.description && Object.keys(fig.description ?? {}).length > 0"
58
59
  :description-id="descriptionId"
59
60
  entity-label="Figure"
60
61
  />
61
62
  </div>
62
63
 
63
- <template v-if="(entity as Figure).subfigures?.length">
64
+ <template v-if="fig.subfigures?.length">
64
65
  <div :class="`figure__subfigures figure__subfigures--${layout}`">
65
66
  <FigureDisplay
66
- v-for="sub in (entity as Figure).subfigures"
67
- :key="sub.id"
67
+ v-for="sub in fig.subfigures"
68
+ :key="sub.id ?? ''"
68
69
  :dataset-id="datasetId"
69
- :entity-id="sub.id"
70
+ :entity-id="sub.id ?? ''"
70
71
  :locale="locale"
71
72
  :dataset-locales="datasetLocales"
72
73
  />
@@ -74,17 +75,17 @@ const topLevelImages = computed(() => (entity.value as Figure | null)?.images ??
74
75
  </template>
75
76
 
76
77
  <NonVerbalCaption
77
- :identifier="(entity as Figure).identifier"
78
- :caption="(entity as Figure).caption"
79
- :description="(entity as Figure).description"
78
+ :identifier="fig.identifier"
79
+ :caption="fig.caption"
80
+ :description="fig.description"
80
81
  :locale="locale"
81
82
  :fallback-chain="fallbackChain"
82
83
  :description-id="descriptionId"
83
84
  />
84
85
 
85
86
  <NonVerbalSources
86
- v-if="(entity as Figure).sources?.length"
87
- :sources="(entity as Figure).sources!"
87
+ v-if="fig.sources?.length"
88
+ :sources="fig.sources"
88
89
  />
89
90
  </figure>
90
91
 
@@ -1,12 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
- import type { FigureImage, LocalizedString } from '../../adapters/non-verbal/types';
3
+ import type { FigureImage } from 'glossarist';
4
+ import type { LocalizedString, NonVerbRepImage } from '../../adapters/non-verbal/types';
4
5
  import { pickLocaleText, hasLocale } from '../../utils/locale';
5
6
  import { getFactory } from '../../adapters/factory';
6
7
 
8
+ type ImageLike = FigureImage | NonVerbRepImage;
9
+
7
10
  const props = withDefaults(defineProps<{
8
- images: FigureImage[];
9
- alt?: LocalizedString;
11
+ images: ImageLike[];
12
+ alt?: LocalizedString | null;
10
13
  datasetId: string;
11
14
  locale: string;
12
15
  fallbackChain?: readonly string[];
@@ -19,7 +22,19 @@ const props = withDefaults(defineProps<{
19
22
 
20
23
  const resolver = getFactory().nonVerbalResolver;
21
24
 
22
- const altText = computed(() => pickLocaleText(props.alt, props.locale, props.fallbackChain));
25
+ function imgSrc(img: ImageLike): string {
26
+ return typeof img.src === 'string' ? img.src : '';
27
+ }
28
+ function imgFormat(img: ImageLike): string {
29
+ const f = img.format;
30
+ return typeof f === 'string' ? f : 'svg';
31
+ }
32
+ function imgRole(img: ImageLike): string | null {
33
+ const r = img.role;
34
+ return typeof r === 'string' ? r : null;
35
+ }
36
+
37
+ const altText = computed(() => pickLocaleText(props.alt ?? undefined, props.locale, props.fallbackChain));
23
38
  const altMissing = computed(() => !props.alt || Object.keys(props.alt).length === 0);
24
39
  const altEmpty = computed(() =>
25
40
  !!props.alt && hasLocale(props.alt, props.locale) && props.alt[props.locale] === '',
@@ -30,26 +45,33 @@ interface SourceVariant { src: string; type: string; media?: string; }
30
45
  const sourceVariants = computed<SourceVariant[]>(() => {
31
46
  const out: SourceVariant[] = [];
32
47
  for (const img of props.images) {
33
- if (!img.role || img.role === 'vector' || img.role === 'raster') continue;
34
- const src = resolver.resolveImageUrl(props.datasetId, img.src);
35
- const type = img.format === 'svg' ? 'image/svg+xml' : `image/${img.format === 'jpg' ? 'jpeg' : img.format}`;
48
+ const role = imgRole(img);
49
+ if (!role || role === 'vector' || role === 'raster') continue;
50
+ const src = imgSrc(img);
51
+ const format = imgFormat(img);
52
+ if (!src) continue;
53
+ const type = format === 'svg' ? 'image/svg+xml' : `image/${format === 'jpg' ? 'jpeg' : format}`;
36
54
  let media: string | undefined;
37
- if (img.role === 'dark') media = '(prefers-color-scheme: dark)';
38
- else if (img.role === 'light') media = '(prefers-color-scheme: light)';
39
- else if (img.role === 'print') media = 'print';
40
- out.push({ src, type, media });
55
+ if (role === 'dark') media = '(prefers-color-scheme: dark)';
56
+ else if (role === 'light') media = '(prefers-color-scheme: light)';
57
+ else if (role === 'print') media = 'print';
58
+ out.push({ src: resolver.resolveImageUrl(props.datasetId, src), type, media });
41
59
  }
42
60
  return out;
43
61
  });
44
62
 
45
63
  const defaultImg = computed(() => {
46
- const nonRole = props.images.find(i => !i.role || i.role === 'vector' || i.role === 'raster');
64
+ const nonRole = props.images.find(i => {
65
+ const r = imgRole(i);
66
+ return !r || r === 'vector' || r === 'raster';
67
+ });
47
68
  const chosen = nonRole ?? props.images[0];
48
- if (!chosen) return null;
69
+ const src = chosen ? imgSrc(chosen) : '';
70
+ if (!src) return null;
49
71
  return {
50
- src: resolver.resolveImageUrl(props.datasetId, chosen.src),
51
- width: chosen.width,
52
- height: chosen.height,
72
+ src: resolver.resolveImageUrl(props.datasetId, src),
73
+ width: chosen?.width ?? undefined,
74
+ height: chosen?.height ?? undefined,
53
75
  };
54
76
  });
55
77
  </script>
@@ -11,7 +11,7 @@
11
11
  * The `print` role is reserved for print stylesheets (selected via CSS
12
12
  * @media print in FigureImages.vue), not picked here.
13
13
  */
14
- import type { FigureImage } from '../../adapters/non-verbal/types';
14
+ import type { FigureImage } from 'glossarist';
15
15
 
16
16
  export interface PickOptions {
17
17
  prefersDark?: boolean;
@@ -12,7 +12,7 @@
12
12
  * Authors who want different behavior can split a composite figure into
13
13
  * multiple top-level figures. V1 does not expose a `layout` field.
14
14
  */
15
- import type { Figure } from '../../adapters/non-verbal/types';
15
+ import type { Figure } from 'glossarist';
16
16
 
17
17
  export type FigureLayout = 'single' | 'row' | 'column' | 'grid';
18
18