@glossarist/concept-browser 0.7.65 → 0.7.67

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 (71) hide show
  1. package/cli/index.mjs +38 -33
  2. package/package.json +2 -2
  3. package/scripts/__tests__/cli-script-contract.test.mjs +39 -0
  4. package/scripts/__tests__/first-non-empty.test.mjs +51 -0
  5. package/scripts/__tests__/process-about-pages.test.mjs +106 -0
  6. package/scripts/build-edges.js +10 -2
  7. package/scripts/fetch-datasets.mjs +61 -54
  8. package/scripts/generate-data.mjs +39 -27
  9. package/scripts/lib/agents-turtle.mjs +8 -2
  10. package/scripts/lib/first-non-empty.mjs +19 -0
  11. package/scripts/process-about-pages.mjs +63 -15
  12. package/scripts/validate-shacl.mjs +1 -1
  13. package/src/__tests__/concept-rdf-view.test.ts +21 -13
  14. package/src/__tests__/perf/serialization-perf.test.ts +13 -30
  15. package/src/__tests__/scripts/agents-turtle.test.ts +18 -6
  16. package/src/__tests__/scripts/build-activity-turtle.test.ts +6 -0
  17. package/src/__tests__/scripts/build-integration.test.ts +4 -3
  18. package/src/__tests__/use-rdf-document.test.ts +125 -0
  19. package/src/components/ConceptRdfView.vue +4 -4
  20. package/src/components/use-rdf-document.ts +137 -0
  21. package/src/__tests__/concept-rdf/agents-emitter.test.ts +0 -110
  22. package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +0 -159
  23. package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +0 -119
  24. package/src/__tests__/concept-rdf/coerce-date.test.ts +0 -97
  25. package/src/__tests__/concept-rdf/concept-emitter.test.ts +0 -258
  26. package/src/__tests__/concept-rdf/dataset-emitter.test.ts +0 -224
  27. package/src/__tests__/concept-rdf/differential.test.ts +0 -96
  28. package/src/__tests__/concept-rdf/group-emitter.test.ts +0 -109
  29. package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +0 -135
  30. package/src/__tests__/concept-rdf/jsonld-writer.test.ts +0 -109
  31. package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +0 -177
  32. package/src/__tests__/concept-rdf/property-based.test.ts +0 -179
  33. package/src/__tests__/concept-rdf/provenance.test.ts +0 -110
  34. package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +0 -43
  35. package/src/__tests__/concept-rdf/quad-isomorphism.ts +0 -47
  36. package/src/__tests__/concept-rdf/rdf-components.test.ts +0 -145
  37. package/src/__tests__/concept-rdf/rdf-graph.test.ts +0 -115
  38. package/src/__tests__/concept-rdf/round-trip.test.ts +0 -243
  39. package/src/__tests__/concept-rdf/scoped-examples.test.ts +0 -126
  40. package/src/__tests__/concept-rdf/sections-builder.test.ts +0 -94
  41. package/src/__tests__/concept-rdf/shacl-conformance.test.ts +0 -110
  42. package/src/__tests__/concept-rdf/shape-consistency.test.ts +0 -138
  43. package/src/__tests__/concept-rdf/snapshot-generation.test.ts +0 -75
  44. package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +0 -142
  45. package/src/__tests__/concept-rdf/turtle-writer.test.ts +0 -114
  46. package/src/__tests__/concept-rdf/use-rdf-document.test.ts +0 -246
  47. package/src/__tests__/concept-rdf/version-emitter.test.ts +0 -120
  48. package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +0 -100
  49. package/src/__tests__/perf/bundle-layout.test.ts +0 -50
  50. package/src/components/concept-rdf/agents-emitter.ts +0 -82
  51. package/src/components/concept-rdf/bibliography-emitter.ts +0 -83
  52. package/src/components/concept-rdf/build-activity-emitter.ts +0 -89
  53. package/src/components/concept-rdf/concept-emitter.ts +0 -443
  54. package/src/components/concept-rdf/dataset-emitter.ts +0 -95
  55. package/src/components/concept-rdf/group-emitter.ts +0 -69
  56. package/src/components/concept-rdf/image-variant-emitter.ts +0 -46
  57. package/src/components/concept-rdf/jsonld-writer.ts +0 -82
  58. package/src/components/concept-rdf/predicates.ts +0 -261
  59. package/src/components/concept-rdf/provenance.ts +0 -80
  60. package/src/components/concept-rdf/rdf-graph.ts +0 -211
  61. package/src/components/concept-rdf/sections-builder.ts +0 -62
  62. package/src/components/concept-rdf/table-formula-emitter.ts +0 -101
  63. package/src/components/concept-rdf/turtle-writer.ts +0 -116
  64. package/src/components/concept-rdf/use-rdf-document.ts +0 -72
  65. package/src/components/concept-rdf/version-emitter.ts +0 -65
  66. package/src/components/concept-rdf/vocabulary-emitter.ts +0 -62
  67. /package/src/components/{concept-rdf/RdfInstanceHeader.vue → RdfInstanceHeader.vue} +0 -0
  68. /package/src/components/{concept-rdf/RdfInstanceSection.vue → RdfInstanceSection.vue} +0 -0
  69. /package/src/components/{concept-rdf/RdfPrefixLegend.vue → RdfPrefixLegend.vue} +0 -0
  70. /package/src/components/{concept-rdf/RdfSourcePanel.vue → RdfSourcePanel.vue} +0 -0
  71. /package/src/components/{concept-rdf/rdf-prefixes.ts → rdf-prefixes.ts} +0 -0
@@ -1,46 +0,0 @@
1
- import { FOAF, DCTERMS, DCAT, XSD } from './predicates';
2
- import { RdfGraph } from './rdf-graph';
3
-
4
- export interface ImageVariantInput {
5
- readonly registerId: string;
6
- readonly figureId: string;
7
- readonly lang?: string;
8
- readonly format: 'svg' | 'png' | 'webp' | 'jpg' | 'gif';
9
- readonly byteSize?: number;
10
- readonly downloadUrl: string;
11
- }
12
-
13
- export function imageVariantIri(input: ImageVariantInput, baseUri = 'https://glossarist.org'): string {
14
- const ext = input.format;
15
- const langSuffix = input.lang ? `${input.lang}.` : '';
16
- return `${baseUri}/${input.registerId}/image/${input.figureId}/${langSuffix}${ext}`;
17
- }
18
-
19
- export function emitImageVariantGraph(input: ImageVariantInput, baseUri = 'https://glossarist.org'): RdfGraph {
20
- const graph = new RdfGraph();
21
- const iri = imageVariantIri(input, baseUri);
22
- const mimeType = mimeForFormat(input.format);
23
- const w = graph.declare(iri, {
24
- types: [FOAF.Image],
25
- label: `${input.figureId} (${input.format}${input.lang ? `/${input.lang}` : ''})`,
26
- classLabel: 'Image',
27
- classId: FOAF.Image,
28
- });
29
- w.literal(DCTERMS.format, mimeType);
30
- if (input.lang) w.literal(DCTERMS.language, input.lang);
31
- if (input.byteSize != null) {
32
- w.literal(DCAT.byteSize, String(input.byteSize), { datatype: XSD.integer });
33
- }
34
- w.iri(DCAT.downloadURL, input.downloadUrl);
35
- return graph;
36
- }
37
-
38
- function mimeForFormat(format: ImageVariantInput['format']): string {
39
- switch (format) {
40
- case 'svg': return 'image/svg+xml';
41
- case 'png': return 'image/png';
42
- case 'webp': return 'image/webp';
43
- case 'jpg': return 'image/jpeg';
44
- case 'gif': return 'image/gif';
45
- }
46
- }
@@ -1,82 +0,0 @@
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
- }
@@ -1,261 +0,0 @@
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;
@@ -1,80 +0,0 @@
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
- }
@@ -1,211 +0,0 @@
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
- }