@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.
- package/cli/index.mjs +32 -0
- package/env.d.ts +15 -0
- package/package.json +12 -2
- package/scripts/__tests__/doctor.test.mjs +147 -0
- package/scripts/doctor.mjs +327 -0
- package/scripts/generate-data.mjs +136 -0
- package/scripts/generate-ontology-data.mjs +3 -3
- package/scripts/generate-ontology-schema.mjs +3 -3
- package/scripts/lib/agents-turtle.mjs +64 -0
- package/scripts/lib/bibliography-turtle.mjs +54 -0
- package/scripts/lib/build-activity-turtle.mjs +92 -0
- package/scripts/lib/build-cache.mjs +70 -0
- package/scripts/lib/dataset-turtle.mjs +79 -0
- package/scripts/lib/turtle-escape.mjs +0 -0
- package/scripts/lib/version-turtle.mjs +56 -0
- package/scripts/lib/vocab-turtle.mjs +64 -0
- package/scripts/normalize-yaml.mjs +99 -0
- package/scripts/sync-concept-model.mjs +86 -0
- package/scripts/validate-shacl.mjs +194 -0
- package/src/__fixtures__/concept-shape.ttl +20 -0
- package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
- package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
- package/src/__fixtures__/shacl/good/concept.ttl +8 -0
- package/src/__tests__/__fixtures__/concepts.ts +221 -0
- package/src/__tests__/adapters/concept-identity.test.ts +76 -0
- package/src/__tests__/components/error-boundary.test.ts +109 -0
- package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
- package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
- package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
- package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
- package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
- package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
- package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
- package/src/__tests__/concept-rdf/differential.test.ts +96 -0
- package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
- package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
- package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
- package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
- package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
- package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
- package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
- package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
- package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
- package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
- package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
- package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
- package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
- package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
- package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
- package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
- package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
- package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
- package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
- package/src/__tests__/concept-rdf-view.test.ts +136 -0
- package/src/__tests__/dataset-style.test.ts +12 -7
- package/src/__tests__/errors/errors.test.ts +142 -0
- package/src/__tests__/format-downloads.test.ts +47 -65
- package/src/__tests__/markdown-lite.test.ts +19 -0
- package/src/__tests__/perf/bundle-layout.test.ts +50 -0
- package/src/__tests__/perf/serialization-perf.test.ts +121 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
- package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
- package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
- package/src/__tests__/scripts/build-cache.test.ts +78 -0
- package/src/__tests__/scripts/build-integration.test.ts +134 -0
- package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
- package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
- package/src/__tests__/scripts/stryker-config.test.ts +33 -0
- package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
- package/src/__tests__/scripts/version-turtle.test.ts +72 -0
- package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
- package/src/__tests__/use-format-registry.test.ts +125 -0
- package/src/__tests__/utils/bcp47.test.ts +166 -0
- package/src/__tests__/utils/color-theme.test.ts +143 -0
- package/src/__tests__/utils/url-safety.test.ts +65 -0
- package/src/__tests__/validate-shacl.test.ts +100 -0
- package/src/adapters/DatasetAdapter.ts +11 -5
- package/src/adapters/GraphDataSource.ts +2 -1
- package/src/adapters/UriRouter.ts +2 -1
- package/src/adapters/concept-identity.ts +69 -0
- package/src/adapters/factory.ts +3 -2
- package/src/adapters/model-bridge.ts +2 -1
- package/src/adapters/non-verbal/figure-bridge.ts +22 -23
- package/src/adapters/non-verbal/formula-bridge.ts +11 -9
- package/src/adapters/non-verbal/glossarist-augment.d.ts +133 -0
- package/src/adapters/non-verbal/index.ts +12 -9
- package/src/adapters/non-verbal/kind.ts +2 -1
- package/src/adapters/non-verbal/table-bridge.ts +12 -10
- package/src/adapters/non-verbal/types.ts +36 -54
- package/src/adapters/non-verbal-resolver.ts +6 -3
- package/src/components/AppSidebar.vue +189 -93
- package/src/components/ConceptDetail.vue +8 -0
- package/src/components/ConceptEditionRail.vue +222 -0
- package/src/components/ConceptRdfView.vue +37 -377
- package/src/components/DatasetSeriesCard.vue +270 -0
- package/src/components/ErrorBoundary.vue +95 -0
- package/src/components/FormatDownloads.vue +17 -13
- package/src/components/HomeSeriesSection.vue +277 -0
- package/src/components/NonVerbalRepDisplay.vue +2 -2
- package/src/components/RelationSphere.vue +1672 -0
- package/src/components/SidebarSeriesSection.vue +239 -0
- package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
- package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
- package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
- package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
- package/src/components/concept-rdf/agents-emitter.ts +82 -0
- package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
- package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
- package/src/components/concept-rdf/concept-emitter.ts +443 -0
- package/src/components/concept-rdf/dataset-emitter.ts +95 -0
- package/src/components/concept-rdf/group-emitter.ts +69 -0
- package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
- package/src/components/concept-rdf/jsonld-writer.ts +82 -0
- package/src/components/concept-rdf/predicates.ts +261 -0
- package/src/components/concept-rdf/provenance.ts +80 -0
- package/src/components/concept-rdf/rdf-graph.ts +211 -0
- package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
- package/src/components/concept-rdf/sections-builder.ts +62 -0
- package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
- package/src/components/concept-rdf/turtle-writer.ts +116 -0
- package/src/components/concept-rdf/use-rdf-document.ts +72 -0
- package/src/components/concept-rdf/version-emitter.ts +65 -0
- package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
- package/src/components/figure/FigureDisplay.vue +16 -15
- package/src/components/figure/FigureImages.vue +38 -16
- package/src/components/figure/figure-image-pick.ts +1 -1
- package/src/components/figure/figure-layout.ts +1 -1
- package/src/components/formula/FormulaDisplay.vue +11 -9
- package/src/components/formula/FormulaExpression.vue +4 -4
- package/src/components/non-verbal/NonVerbalCaption.vue +5 -5
- package/src/components/non-verbal/NonVerbalSources.vue +3 -11
- package/src/components/table/TableDisplay.vue +6 -4
- package/src/components/table/TableMarkup.vue +1 -1
- package/src/composables/use-color-theme.ts +82 -0
- package/src/composables/use-format-registry.ts +42 -0
- package/src/composables/use-non-verbal-entity.ts +2 -1
- package/src/composables/useDatasetSeries.ts +258 -0
- package/src/composables/useSphereProjection.ts +125 -0
- package/src/config/group-types.ts +92 -0
- package/src/config/types.ts +81 -2
- package/src/config/use-site-config.ts +2 -1
- package/src/errors.ts +136 -0
- package/src/i18n/locales/eng.yml +24 -0
- package/src/i18n/locales/fra.yml +24 -0
- package/src/stores/vocabulary.ts +3 -1
- package/src/style.css +17 -2
- package/src/types/agents-version-turtle.d.ts +27 -0
- package/src/types/bibliography-turtle.d.ts +12 -0
- package/src/types/build-activity-turtle.d.ts +16 -0
- package/src/types/build-cache.d.ts +20 -0
- package/src/types/dataset-turtle.d.ts +32 -0
- package/src/types/normalize-yaml.d.ts +16 -0
- package/src/types/turtle-escape.d.ts +6 -0
- package/src/types/vocab-turtle.d.ts +13 -0
- package/src/utils/asciidoc-lite.ts +11 -6
- package/src/utils/bcp47.ts +141 -0
- package/src/utils/color-theme-integration.ts +11 -0
- package/src/utils/color-theme.ts +129 -0
- package/src/utils/dataset-style.ts +31 -6
- package/src/utils/locale.ts +6 -14
- package/src/utils/markdown-lite.ts +6 -1
- package/src/utils/relation-sphere-styling.ts +63 -0
- package/src/utils/relationship-categories.ts +30 -0
- package/src/utils/url-safety.ts +30 -0
- package/src/views/ConceptView.vue +183 -9
- package/src/views/DatasetView.vue +6 -0
- package/src/views/HomeView.vue +5 -0
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { RDF_PREFIXES } from './rdf-prefixes';
|
|
2
|
+
import { RDF } from './predicates';
|
|
3
|
+
import type { RdfGraph, RdfResource, RdfTerm, RdfTriple } from './rdf-graph';
|
|
4
|
+
|
|
5
|
+
export function writeJsonLd(graph: RdfGraph): string {
|
|
6
|
+
const context: Record<string, string> = {};
|
|
7
|
+
for (const p of RDF_PREFIXES) {
|
|
8
|
+
context[p.prefix] = p.iri;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nodes: JsonLdNode[] = [];
|
|
12
|
+
for (const r of graph.resources()) {
|
|
13
|
+
nodes.push(resourceToNode(r));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const doc = {
|
|
17
|
+
'@context': context,
|
|
18
|
+
'@graph': nodes,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return JSON.stringify(doc, null, 2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface JsonLdNode {
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resourceToNode(r: RdfResource): JsonLdNode {
|
|
29
|
+
const node: JsonLdNode = {
|
|
30
|
+
'@id': r.subject,
|
|
31
|
+
'@type': [...r.types],
|
|
32
|
+
};
|
|
33
|
+
mergeTriplesInto(node, r.triples);
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function blankNodeToNode(triples: readonly RdfTriple[]): JsonLdNode {
|
|
38
|
+
const node: JsonLdNode = {};
|
|
39
|
+
mergeTriplesInto(node, triples);
|
|
40
|
+
return node;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mergeTriplesInto(node: JsonLdNode, triples: readonly RdfTriple[]): void {
|
|
44
|
+
const grouped = new Map<string, RdfTerm[]>();
|
|
45
|
+
for (const t of triples) {
|
|
46
|
+
const arr = grouped.get(t.predicate) ?? [];
|
|
47
|
+
arr.push(t.object);
|
|
48
|
+
grouped.set(t.predicate, arr);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const typeTerms = grouped.get(RDF.type);
|
|
52
|
+
if (typeTerms && typeTerms.length > 0) {
|
|
53
|
+
const typeIris = typeTerms
|
|
54
|
+
.filter((t): t is Extract<RdfTerm, { kind: 'iri' }> => t.kind === 'iri')
|
|
55
|
+
.map(t => t.value);
|
|
56
|
+
if (typeIris.length === 1) {
|
|
57
|
+
node['@type'] = typeIris[0];
|
|
58
|
+
} else if (typeIris.length > 1) {
|
|
59
|
+
node['@type'] = typeIris;
|
|
60
|
+
}
|
|
61
|
+
grouped.delete(RDF.type);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [pred, terms] of grouped) {
|
|
65
|
+
node[pred] = terms.length === 1
|
|
66
|
+
? termToJson(terms[0])
|
|
67
|
+
: terms.map(termToJson);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function termToJson(term: RdfTerm): unknown {
|
|
72
|
+
switch (term.kind) {
|
|
73
|
+
case 'iri':
|
|
74
|
+
return { '@id': term.value };
|
|
75
|
+
case 'literal':
|
|
76
|
+
if (term.lang) return { '@value': term.value, '@language': term.lang };
|
|
77
|
+
if (term.datatype) return { '@value': term.value, '@type': term.datatype };
|
|
78
|
+
return term.value;
|
|
79
|
+
case 'blank':
|
|
80
|
+
return blankNodeToNode(term.triples);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export const GLOSS = {
|
|
2
|
+
// Classes
|
|
3
|
+
Concept: 'gloss:Concept',
|
|
4
|
+
LocalizedConcept: 'gloss:LocalizedConcept',
|
|
5
|
+
Designation: 'gloss:Designation',
|
|
6
|
+
Expression: 'gloss:Expression',
|
|
7
|
+
Abbreviation: 'gloss:Abbreviation',
|
|
8
|
+
Symbol: 'gloss:Symbol',
|
|
9
|
+
LetterSymbol: 'gloss:LetterSymbol',
|
|
10
|
+
GraphicalSymbol: 'gloss:GraphicalSymbol',
|
|
11
|
+
|
|
12
|
+
DetailedDefinition: 'gloss:DetailedDefinition',
|
|
13
|
+
ConceptDate: 'gloss:ConceptDate',
|
|
14
|
+
ConceptSource: 'gloss:ConceptSource',
|
|
15
|
+
ConceptRef: 'gloss:ConceptRef',
|
|
16
|
+
NonVerbalEntity: 'gloss:NonVerbalEntity',
|
|
17
|
+
NonVerbalRep: 'gloss:NonVerbalRep',
|
|
18
|
+
NonVerbalRepresentation: 'gloss:NonVerbalRepresentation',
|
|
19
|
+
Figure: 'gloss:Figure',
|
|
20
|
+
Table: 'gloss:Table',
|
|
21
|
+
Formula: 'gloss:Formula',
|
|
22
|
+
RelatedConcept: 'gloss:RelatedConcept',
|
|
23
|
+
Citation: 'gloss:Citation',
|
|
24
|
+
CitationRef: 'gloss:CitationRef',
|
|
25
|
+
Locality: 'gloss:Locality',
|
|
26
|
+
CustomLocality: 'gloss:CustomLocality',
|
|
27
|
+
Reference: 'gloss:Reference',
|
|
28
|
+
ConceptCollection: 'gloss:ConceptCollection',
|
|
29
|
+
|
|
30
|
+
// Concept predicates
|
|
31
|
+
identifier: 'gloss:identifier',
|
|
32
|
+
uri: 'gloss:uri',
|
|
33
|
+
hasStatus: 'gloss:hasStatus',
|
|
34
|
+
hasEntryStatus: 'gloss:hasEntryStatus',
|
|
35
|
+
hasLocalization: 'gloss:hasLocalization',
|
|
36
|
+
isLocalizationOf: 'gloss:isLocalizationOf',
|
|
37
|
+
hasDomain: 'gloss:hasDomain',
|
|
38
|
+
domain: 'gloss:domain',
|
|
39
|
+
hasSource: 'gloss:hasSource',
|
|
40
|
+
hasDate: 'gloss:hasDate',
|
|
41
|
+
hasRelatedConcept: 'gloss:hasRelatedConcept',
|
|
42
|
+
hasDesignation: 'gloss:hasDesignation',
|
|
43
|
+
hasDefinition: 'gloss:hasDefinition',
|
|
44
|
+
hasNote: 'gloss:hasNote',
|
|
45
|
+
hasExample: 'gloss:hasExample',
|
|
46
|
+
hasAnnotation: 'gloss:hasAnnotation',
|
|
47
|
+
hasNonVerbalRep: 'gloss:hasNonVerbalRep',
|
|
48
|
+
hasReference: 'gloss:hasReference',
|
|
49
|
+
hasPronunciation: 'gloss:hasPronunciation',
|
|
50
|
+
hasGrammarInfo: 'gloss:hasGrammarInfo',
|
|
51
|
+
hasTermType: 'gloss:hasTermType',
|
|
52
|
+
tag: 'gloss:tag',
|
|
53
|
+
register: 'gloss:register',
|
|
54
|
+
|
|
55
|
+
// Lifecycle / review
|
|
56
|
+
reviewDate: 'gloss:reviewDate',
|
|
57
|
+
reviewDecisionDate: 'gloss:reviewDecisionDate',
|
|
58
|
+
reviewDecisionEvent: 'gloss:reviewDecisionEvent',
|
|
59
|
+
reviewStatus: 'gloss:reviewStatus',
|
|
60
|
+
reviewDecision: 'gloss:reviewDecision',
|
|
61
|
+
reviewDecisionNotes: 'gloss:reviewDecisionNotes',
|
|
62
|
+
reviewType: 'gloss:reviewType',
|
|
63
|
+
lineageSourceSimilarity: 'gloss:lineageSimilarity',
|
|
64
|
+
lineageSimilarity: 'gloss:lineageSimilarity',
|
|
65
|
+
release: 'gloss:release',
|
|
66
|
+
classification: 'gloss:classification',
|
|
67
|
+
script: 'gloss:script',
|
|
68
|
+
system: 'gloss:conversionSystem',
|
|
69
|
+
conversionSystem: 'gloss:conversionSystem',
|
|
70
|
+
language: 'gloss:language',
|
|
71
|
+
|
|
72
|
+
// Non-verbal
|
|
73
|
+
nonVerbalType: 'gloss:representationType',
|
|
74
|
+
representationType: 'gloss:representationType',
|
|
75
|
+
representationRef: 'gloss:representationRef',
|
|
76
|
+
hasNonVerbalRepresentation: 'gloss:hasNonVerbalRepresentation',
|
|
77
|
+
expression: 'gloss:expression',
|
|
78
|
+
latexForm: 'gloss:latexForm',
|
|
79
|
+
content: 'gloss:content',
|
|
80
|
+
hasHeader: 'gloss:hasHeader',
|
|
81
|
+
hasRow: 'gloss:hasRow',
|
|
82
|
+
image: 'gloss:image',
|
|
83
|
+
caption: 'gloss:caption',
|
|
84
|
+
altText: 'gloss:altText',
|
|
85
|
+
|
|
86
|
+
// Designation
|
|
87
|
+
normativeStatus: 'gloss:normativeStatus',
|
|
88
|
+
geographicalArea: 'gloss:geographicalArea',
|
|
89
|
+
fieldOfApplication: 'gloss:fieldOfApplication',
|
|
90
|
+
prefix: 'gloss:prefix',
|
|
91
|
+
usageInfo: 'gloss:usageInfo',
|
|
92
|
+
text: 'gloss:text',
|
|
93
|
+
isInternational: 'gloss:isInternational',
|
|
94
|
+
isAbsent: 'gloss:isAbsent',
|
|
95
|
+
isAcronym: 'gloss:isAcronym',
|
|
96
|
+
isInitialism: 'gloss:isInitialism',
|
|
97
|
+
isTruncation: 'gloss:isTruncation',
|
|
98
|
+
|
|
99
|
+
// Related concept / ConceptRef
|
|
100
|
+
relationshipType: 'gloss:relationshipType',
|
|
101
|
+
relationshipContent: 'gloss:relationshipContent',
|
|
102
|
+
relationshipRef: 'gloss:relationshipRef',
|
|
103
|
+
conceptRefSource: 'gloss:conceptRefSource',
|
|
104
|
+
conceptRefId: 'gloss:conceptRefId',
|
|
105
|
+
|
|
106
|
+
// ConceptDate
|
|
107
|
+
dateType: 'gloss:dateType',
|
|
108
|
+
dateValue: 'gloss:dateValue',
|
|
109
|
+
eventDescription: 'gloss:eventDescription',
|
|
110
|
+
|
|
111
|
+
// ConceptSource
|
|
112
|
+
sourceStatus: 'gloss:sourceStatus',
|
|
113
|
+
sourceType: 'gloss:sourceType',
|
|
114
|
+
sourceOrigin: 'gloss:sourceOrigin',
|
|
115
|
+
modification: 'gloss:modification',
|
|
116
|
+
|
|
117
|
+
// Citation
|
|
118
|
+
hasCitationRef: 'gloss:hasCitationRef',
|
|
119
|
+
hasCitationLocality: 'gloss:hasCitationLocality',
|
|
120
|
+
citationLink: 'gloss:citationLink',
|
|
121
|
+
citationOriginal: 'gloss:citationOriginal',
|
|
122
|
+
hasCustomLocality: 'gloss:hasCustomLocality',
|
|
123
|
+
bibliographicCitation: 'gloss:bibliographicCitation',
|
|
124
|
+
|
|
125
|
+
// CitationRef
|
|
126
|
+
citationRefSource: 'gloss:citationRefSource',
|
|
127
|
+
citationRefId: 'gloss:citationRefId',
|
|
128
|
+
citationRefVersion: 'gloss:citationRefVersion',
|
|
129
|
+
|
|
130
|
+
// Reference
|
|
131
|
+
refSource: 'gloss:source',
|
|
132
|
+
refId: 'gloss:refId',
|
|
133
|
+
refVersion: 'gloss:refVersion',
|
|
134
|
+
refLink: 'gloss:refLink',
|
|
135
|
+
refType: 'gloss:refType',
|
|
136
|
+
urn: 'gloss:urn',
|
|
137
|
+
term: 'gloss:term',
|
|
138
|
+
hasLocality: 'gloss:hasLocality',
|
|
139
|
+
|
|
140
|
+
// Locality
|
|
141
|
+
localityType: 'gloss:localityType',
|
|
142
|
+
referenceFrom: 'gloss:referenceFrom',
|
|
143
|
+
referenceTo: 'gloss:referenceTo',
|
|
144
|
+
customLocalityName: 'gloss:customLocalityName',
|
|
145
|
+
customLocalityValue: 'gloss:customLocalityValue',
|
|
146
|
+
} as const;
|
|
147
|
+
|
|
148
|
+
export const SKOS = {
|
|
149
|
+
Concept: 'skos:Concept',
|
|
150
|
+
Collection: 'skos:Collection',
|
|
151
|
+
ConceptScheme: 'skos:ConceptScheme',
|
|
152
|
+
prefLabel: 'skos:prefLabel',
|
|
153
|
+
altLabel: 'skos:altLabel',
|
|
154
|
+
definition: 'skos:definition',
|
|
155
|
+
scopeNote: 'skos:scopeNote',
|
|
156
|
+
notation: 'skos:notation',
|
|
157
|
+
member: 'skos:member',
|
|
158
|
+
hasTopConcept: 'skos:hasTopConcept',
|
|
159
|
+
inScheme: 'skos:inScheme',
|
|
160
|
+
} as const;
|
|
161
|
+
|
|
162
|
+
export const SKOSXL = {
|
|
163
|
+
Label: 'skosxl:Label',
|
|
164
|
+
prefLabel: 'skosxl:prefLabel',
|
|
165
|
+
altLabel: 'skosxl:altLabel',
|
|
166
|
+
literalForm: 'skosxl:literalForm',
|
|
167
|
+
} as const;
|
|
168
|
+
|
|
169
|
+
export const DCTERMS = {
|
|
170
|
+
language: 'dcterms:language',
|
|
171
|
+
source: 'dcterms:source',
|
|
172
|
+
title: 'dcterms:title',
|
|
173
|
+
description: 'dcterms:description',
|
|
174
|
+
created: 'dcterms:created',
|
|
175
|
+
modified: 'dcterms:modified',
|
|
176
|
+
identifier: 'dcterms:identifier',
|
|
177
|
+
date: 'dcterms:date',
|
|
178
|
+
type: 'dcterms:type',
|
|
179
|
+
isVersionOf: 'dcterms:isVersionOf',
|
|
180
|
+
isPartOf: 'dcterms:isPartOf',
|
|
181
|
+
replaces: 'dcterms:replaces',
|
|
182
|
+
isReplacedBy: 'dcterms:isReplacedBy',
|
|
183
|
+
bibliographicCitation: 'dcterms:bibliographicCitation',
|
|
184
|
+
subject: 'dcterms:subject',
|
|
185
|
+
format: 'dcterms:format',
|
|
186
|
+
publisher: 'dcterms:publisher',
|
|
187
|
+
contactPoint: 'dcterms:contactPoint',
|
|
188
|
+
BibliographicResource: 'dcterms:BibliographicResource',
|
|
189
|
+
} as const;
|
|
190
|
+
|
|
191
|
+
export const RDF = {
|
|
192
|
+
type: 'rdf:type',
|
|
193
|
+
value: 'rdf:value',
|
|
194
|
+
} as const;
|
|
195
|
+
|
|
196
|
+
export const OWL = {
|
|
197
|
+
Thing: 'owl:Thing',
|
|
198
|
+
sameAs: 'owl:sameAs',
|
|
199
|
+
deprecated: 'owl:deprecated',
|
|
200
|
+
} as const;
|
|
201
|
+
|
|
202
|
+
export const RDFS = {
|
|
203
|
+
seeAlso: 'rdfs:seeAlso',
|
|
204
|
+
label: 'rdfs:label',
|
|
205
|
+
comment: 'rdfs:comment',
|
|
206
|
+
} as const;
|
|
207
|
+
|
|
208
|
+
export const PROV = {
|
|
209
|
+
wasGeneratedBy: 'prov:wasGeneratedBy',
|
|
210
|
+
generatedAtTime: 'prov:generatedAtTime',
|
|
211
|
+
wasDerivedFrom: 'prov:wasDerivedFrom',
|
|
212
|
+
wasAttributedTo: 'prov:wasAttributedTo',
|
|
213
|
+
invalidatedAtTime: 'prov:invalidatedAtTime',
|
|
214
|
+
wasInvalidatedBy: 'prov:wasInvalidatedBy',
|
|
215
|
+
wasAssociatedWith: 'prov:wasAssociatedWith',
|
|
216
|
+
wasRevisionOf: 'prov:wasRevisionOf',
|
|
217
|
+
used: 'prov:used',
|
|
218
|
+
actedOnBehalfOf: 'prov:actedOnBehalfOf',
|
|
219
|
+
Activity: 'prov:Activity',
|
|
220
|
+
Entity: 'prov:Entity',
|
|
221
|
+
Agent: 'prov:Agent',
|
|
222
|
+
Organization: 'prov:Organization',
|
|
223
|
+
Person: 'prov:Person',
|
|
224
|
+
} as const;
|
|
225
|
+
|
|
226
|
+
export const DCAT = {
|
|
227
|
+
Dataset: 'dcat:Dataset',
|
|
228
|
+
DatasetSeries: 'dcat:DatasetSeries',
|
|
229
|
+
Catalog: 'dcat:Catalog',
|
|
230
|
+
Distribution: 'dcat:Distribution',
|
|
231
|
+
distribution: 'dcat:distribution',
|
|
232
|
+
dataset: 'dcat:dataset',
|
|
233
|
+
hasVersion: 'dcat:hasVersion',
|
|
234
|
+
isVersionOf: 'dcat:isVersionOf',
|
|
235
|
+
hasCurrentVersion: 'gloss:hasCurrentVersion',
|
|
236
|
+
isCurrentVersionOf: 'gloss:isCurrentVersionOf',
|
|
237
|
+
downloadURL: 'dcat:downloadURL',
|
|
238
|
+
mediaType: 'dcat:mediaType',
|
|
239
|
+
byteSize: 'dcat:byteSize',
|
|
240
|
+
contactPoint: 'dcat:contactPoint',
|
|
241
|
+
theme: 'dcat:theme',
|
|
242
|
+
keyword: 'dcat:keyword',
|
|
243
|
+
} as const;
|
|
244
|
+
|
|
245
|
+
export const FOAF = {
|
|
246
|
+
Person: 'foaf:Person',
|
|
247
|
+
Organization: 'foaf:Organization',
|
|
248
|
+
name: 'foaf:name',
|
|
249
|
+
mbox: 'foaf:mbox',
|
|
250
|
+
page: 'foaf:page',
|
|
251
|
+
Image: 'foaf:Image',
|
|
252
|
+
} as const;
|
|
253
|
+
|
|
254
|
+
export const XSD = {
|
|
255
|
+
dateTime: 'xsd:dateTime',
|
|
256
|
+
date: 'xsd:date',
|
|
257
|
+
boolean: 'xsd:boolean',
|
|
258
|
+
integer: 'xsd:integer',
|
|
259
|
+
string: 'xsd:string',
|
|
260
|
+
anyURI: 'xsd:anyURI',
|
|
261
|
+
} as const;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { RdfGraph } from './rdf-graph';
|
|
2
|
+
import { PROV, DCTERMS, XSD } from './predicates';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provenance information attached to every emitted concept graph.
|
|
6
|
+
*
|
|
7
|
+
* The `toolId` and `toolVersion` identify the serializer that produced
|
|
8
|
+
* the document; together they form the PROV-O activity IRI
|
|
9
|
+
* (`<activity/serializers/{toolId}/{version}>`). The `generatedAt`
|
|
10
|
+
* timestamp is when this document came into existence — at build
|
|
11
|
+
* time for static dumps, at view time for client-side emission.
|
|
12
|
+
*
|
|
13
|
+
* `canonicalUri` is the unversioned concept URI; `dcterms:isVersionOf`
|
|
14
|
+
* points there from the versioned concept resource.
|
|
15
|
+
*/
|
|
16
|
+
export interface ProvenanceOptions {
|
|
17
|
+
readonly toolId: string;
|
|
18
|
+
readonly toolVersion: string;
|
|
19
|
+
readonly generatedAt: string;
|
|
20
|
+
readonly canonicalUri?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const SERIALIZER_TOOL_ID = 'concept-browser';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compose the PROV-O activity IRI for a given tool + version. Format:
|
|
27
|
+
* `activity/serializers/{toolId}/{version}`. This is a relative IRI;
|
|
28
|
+
* under the document base it resolves to a per-tool activity resource.
|
|
29
|
+
*/
|
|
30
|
+
export function activityUri(opts: ProvenanceOptions): string {
|
|
31
|
+
return `activity/serializers/${opts.toolId}/${opts.toolVersion}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attach provenance triples to the concept resource identified by
|
|
36
|
+
* `subjectUri` in `graph`. Idempotent — calling twice with the same
|
|
37
|
+
* arguments produces the same single set of triples (the underlying
|
|
38
|
+
* RdfGraph deduplicates).
|
|
39
|
+
*
|
|
40
|
+
* The decorator is invoked AFTER the base emitter has populated the
|
|
41
|
+
* graph, keeping domain emission (concept-emitter.ts) decoupled from
|
|
42
|
+
* build/render context (timestamp, tool version). See ADR 0001 for
|
|
43
|
+
* the rationale.
|
|
44
|
+
*/
|
|
45
|
+
export function decorateWithProvenance(
|
|
46
|
+
graph: RdfGraph,
|
|
47
|
+
subjectUri: string,
|
|
48
|
+
opts: ProvenanceOptions,
|
|
49
|
+
): void {
|
|
50
|
+
const w = graph.declare(subjectUri, {});
|
|
51
|
+
w.iri(PROV.wasGeneratedBy, activityUri(opts));
|
|
52
|
+
w.literal(PROV.generatedAtTime, opts.generatedAt, { datatype: XSD.dateTime });
|
|
53
|
+
if (opts.canonicalUri && opts.canonicalUri !== subjectUri) {
|
|
54
|
+
w.iri(DCTERMS.isVersionOf, opts.canonicalUri);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
graph.declare(activityUri(opts), {
|
|
58
|
+
types: [PROV.Activity],
|
|
59
|
+
label: `${opts.toolId} ${opts.toolVersion}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build ProvenanceOptions for runtime emission in the browser. Uses
|
|
65
|
+
* the current wall-clock time as `generatedAt`. Callers that want a
|
|
66
|
+
* deterministic timestamp (e.g., build scripts, snapshot tests)
|
|
67
|
+
* should construct the options directly.
|
|
68
|
+
*/
|
|
69
|
+
export function runtimeProvenance(
|
|
70
|
+
toolVersion: string,
|
|
71
|
+
canonicalUri?: string,
|
|
72
|
+
now: () => Date = () => new Date(),
|
|
73
|
+
): ProvenanceOptions {
|
|
74
|
+
return {
|
|
75
|
+
toolId: SERIALIZER_TOOL_ID,
|
|
76
|
+
toolVersion,
|
|
77
|
+
generatedAt: now().toISOString(),
|
|
78
|
+
canonicalUri,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RDF intermediate representation — a subject-grouped triple graph.
|
|
3
|
+
*
|
|
4
|
+
* The graph is the single source of truth for all RDF emission. The
|
|
5
|
+
* ConceptEmitter walks the Concept model and populates an RdfGraph; the
|
|
6
|
+
* Turtle/JSON-LD writers and the UI sections builder all consume the
|
|
7
|
+
* same graph. Adding a new field touches exactly one site (the emitter),
|
|
8
|
+
* not three.
|
|
9
|
+
*
|
|
10
|
+
* Terms use prefixed names (`gloss:Concept`) or absolute IRIs
|
|
11
|
+
* (`https://glossarist.org/...`). Writers decide how to render each.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type RdfTerm = RdfIri | RdfLiteral | RdfBlankNode;
|
|
15
|
+
|
|
16
|
+
export interface RdfIri {
|
|
17
|
+
readonly kind: 'iri';
|
|
18
|
+
readonly value: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RdfLiteral {
|
|
22
|
+
readonly kind: 'literal';
|
|
23
|
+
readonly value: string;
|
|
24
|
+
readonly lang?: string;
|
|
25
|
+
readonly datatype?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RdfBlankNode {
|
|
29
|
+
readonly kind: 'blank';
|
|
30
|
+
readonly triples: RdfTriple[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RdfTriple {
|
|
34
|
+
readonly predicate: string;
|
|
35
|
+
readonly object: RdfTerm;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RdfResource {
|
|
39
|
+
readonly subject: string;
|
|
40
|
+
readonly types: readonly string[];
|
|
41
|
+
readonly triples: readonly RdfTriple[];
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly classLabel: string;
|
|
44
|
+
readonly classId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface MutableResource {
|
|
48
|
+
readonly subject: string;
|
|
49
|
+
readonly types: string[];
|
|
50
|
+
readonly triples: RdfTriple[];
|
|
51
|
+
label: string;
|
|
52
|
+
classLabel: string;
|
|
53
|
+
classId: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ResourceInit {
|
|
57
|
+
readonly types?: readonly string[];
|
|
58
|
+
readonly label?: string;
|
|
59
|
+
readonly classLabel?: string;
|
|
60
|
+
readonly classId?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function lit(value: string, opts: { lang?: string; datatype?: string } = {}): RdfLiteral {
|
|
64
|
+
return { kind: 'literal', value, lang: opts.lang, datatype: opts.datatype };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function iri(value: string): RdfIri {
|
|
68
|
+
return { kind: 'iri', value };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function blank(...triples: RdfTriple[]): RdfBlankNode {
|
|
72
|
+
return { kind: 'blank', triples };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function triple(predicate: string, object: RdfTerm): RdfTriple {
|
|
76
|
+
return { predicate, object };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function termEquals(a: RdfTerm, b: RdfTerm): boolean {
|
|
80
|
+
if (a.kind !== b.kind) return false;
|
|
81
|
+
switch (a.kind) {
|
|
82
|
+
case 'iri':
|
|
83
|
+
return a.value === (b as RdfIri).value;
|
|
84
|
+
case 'literal': {
|
|
85
|
+
const o = b as RdfLiteral;
|
|
86
|
+
return a.value === o.value && a.lang === o.lang && a.datatype === o.datatype;
|
|
87
|
+
}
|
|
88
|
+
case 'blank': {
|
|
89
|
+
const o = b as RdfBlankNode;
|
|
90
|
+
if (a.triples.length !== o.triples.length) return false;
|
|
91
|
+
return a.triples.every((t, i) => {
|
|
92
|
+
const u = o.triples[i];
|
|
93
|
+
return t.predicate === u.predicate && termEquals(t.object, u.object);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class RdfGraph {
|
|
100
|
+
private readonly _resources = new Map<string, MutableResource>();
|
|
101
|
+
private readonly _order: string[] = [];
|
|
102
|
+
|
|
103
|
+
declare(subject: string, init: ResourceInit = {}): ResourceWriter {
|
|
104
|
+
let r = this._resources.get(subject);
|
|
105
|
+
if (!r) {
|
|
106
|
+
r = {
|
|
107
|
+
subject,
|
|
108
|
+
types: [...(init.types ?? [])],
|
|
109
|
+
triples: [],
|
|
110
|
+
label: init.label ?? subject,
|
|
111
|
+
classLabel: init.classLabel ?? '',
|
|
112
|
+
classId: init.classId ?? '',
|
|
113
|
+
};
|
|
114
|
+
this._resources.set(subject, r);
|
|
115
|
+
this._order.push(subject);
|
|
116
|
+
} else {
|
|
117
|
+
if (init.types) {
|
|
118
|
+
for (const t of init.types) {
|
|
119
|
+
if (!r.types.includes(t)) r.types.push(t);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (init.label !== undefined && r.label === subject) r.label = init.label;
|
|
123
|
+
if (init.classLabel && !r.classLabel) r.classLabel = init.classLabel;
|
|
124
|
+
if (init.classId && !r.classId) r.classId = init.classId;
|
|
125
|
+
}
|
|
126
|
+
return new ResourceWriter(r.triples);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
has(subject: string): boolean {
|
|
130
|
+
return this._resources.has(subject);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get(subject: string): RdfResource | undefined {
|
|
134
|
+
return this._resources.get(subject);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get size(): number {
|
|
138
|
+
return this._order.length;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
*resources(): Iterable<RdfResource> {
|
|
142
|
+
for (const s of this._order) {
|
|
143
|
+
yield this._resources.get(s)!;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
toArray(): RdfResource[] {
|
|
148
|
+
return Array.from(this.resources());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
merge(other: RdfGraph): this {
|
|
152
|
+
for (const r of other.resources()) {
|
|
153
|
+
const w = this.declare(r.subject, {
|
|
154
|
+
types: [...r.types],
|
|
155
|
+
label: r.label,
|
|
156
|
+
classLabel: r.classLabel,
|
|
157
|
+
classId: r.classId,
|
|
158
|
+
});
|
|
159
|
+
for (const t of r.triples) {
|
|
160
|
+
if (t.object.kind === 'iri') w.iri(t.predicate, t.object.value);
|
|
161
|
+
else if (t.object.kind === 'literal') {
|
|
162
|
+
w.literal(t.predicate, t.object.value, { lang: t.object.lang, datatype: t.object.datatype });
|
|
163
|
+
} else {
|
|
164
|
+
w.blank(t.predicate, t.object.triples);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class ResourceWriter {
|
|
173
|
+
constructor(private readonly triples: RdfTriple[]) {}
|
|
174
|
+
|
|
175
|
+
add(predicate: string, object: RdfTerm): this {
|
|
176
|
+
if (object.kind === 'literal' && object.value === '') return this;
|
|
177
|
+
if (object.kind === 'iri' && object.value === '') return this;
|
|
178
|
+
if (!this.triples.some(t => t.predicate === predicate && termEquals(t.object, object))) {
|
|
179
|
+
this.triples.push({ predicate, object });
|
|
180
|
+
}
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
literal(predicate: string, value: string, opts: { lang?: string; datatype?: string } = {}): this {
|
|
185
|
+
if (!value) return this;
|
|
186
|
+
return this.add(predicate, { kind: 'literal', value, lang: opts.lang, datatype: opts.datatype });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
iri(predicate: string, value: string): this {
|
|
190
|
+
if (!value) return this;
|
|
191
|
+
return this.add(predicate, { kind: 'iri', value });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
blank(predicate: string, triples: readonly RdfTriple[]): this {
|
|
195
|
+
if (triples.length === 0) return this;
|
|
196
|
+
return this.add(predicate, { kind: 'blank', triples: [...triples] });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
addTriple(t: RdfTriple): this {
|
|
200
|
+
return this.add(t.predicate, t.object);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
addTriples(triples: readonly RdfTriple[]): this {
|
|
204
|
+
for (const t of triples) this.addTriple(t);
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function isAbsoluteIri(s: string): boolean {
|
|
210
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(s) || s.startsWith('urn:');
|
|
211
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PrefixEntry {
|
|
2
|
+
prefix: string;
|
|
3
|
+
iri: string;
|
|
4
|
+
description: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const RDF_PREFIXES: readonly PrefixEntry[] = [
|
|
8
|
+
{ prefix: 'gloss', iri: 'https://www.glossarist.org/ontologies/', description: 'Glossarist ontology' },
|
|
9
|
+
{ prefix: 'skos', iri: 'http://www.w3.org/2004/02/skos/core#', description: 'Simple Knowledge Organization System' },
|
|
10
|
+
{ prefix: 'skosxl', iri: 'http://www.w3.org/2008/05/skos-xl#', description: 'SKOS eXtension for Labels' },
|
|
11
|
+
{ prefix: 'rdf', iri: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', description: 'RDF core vocabulary' },
|
|
12
|
+
{ prefix: 'rdfs', iri: 'http://www.w3.org/2000/01/rdf-schema#', description: 'RDF Schema' },
|
|
13
|
+
{ prefix: 'owl', iri: 'http://www.w3.org/2002/07/owl#', description: 'Web Ontology Language' },
|
|
14
|
+
{ prefix: 'dcterms', iri: 'http://purl.org/dc/terms/', description: 'Dublin Core terms' },
|
|
15
|
+
{ prefix: 'prov', iri: 'http://www.w3.org/ns/prov#', description: 'PROV-O provenance' },
|
|
16
|
+
{ prefix: 'dcat', iri: 'http://www.w3.org/ns/dcat#', description: 'Data Catalog vocabulary' },
|
|
17
|
+
{ prefix: 'foaf', iri: 'http://xmlns.com/foaf/0.1/', description: 'Friend-of-a-Friend agents' },
|
|
18
|
+
{ prefix: 'xsd', iri: 'http://www.w3.org/2001/XMLSchema#', description: 'XML Schema datatypes' },
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export function findPrefix(prefix: string): PrefixEntry | undefined {
|
|
22
|
+
return RDF_PREFIXES.find(p => p.prefix === prefix);
|
|
23
|
+
}
|