@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,89 +0,0 @@
1
- import { GLOSS, DCTERMS, XSD, PROV, FOAF } from './predicates';
2
- import { RdfGraph, lit, iri, blank, triple } from './rdf-graph';
3
-
4
- export interface BuildActivityInput {
5
- readonly runId: string;
6
- readonly startedAt: string;
7
- readonly endedAt: string;
8
- readonly gitSha?: string;
9
- readonly gitBranch?: string;
10
- readonly toolId: string;
11
- readonly toolVersion: string;
12
- readonly datasetRegisters: readonly string[];
13
- readonly conceptCount: number;
14
- readonly associatedAgentIri?: string;
15
- }
16
-
17
- export function activityIri(input: BuildActivityInput): string {
18
- return `activity/build/${input.runId}`;
19
- }
20
-
21
- export function emitBuildActivityGraph(input: BuildActivityInput): RdfGraph {
22
- const graph = new RdfGraph();
23
- const iriStr = activityIri(input);
24
- const w = graph.declare(iriStr, {
25
- types: [PROV.Activity],
26
- label: `build ${input.runId}`,
27
- classLabel: 'Activity',
28
- classId: PROV.Activity,
29
- });
30
-
31
- w.literal(PROV.generatedAtTime, input.endedAt, { datatype: XSD.dateTime });
32
-
33
- const startW = graph.declare(`${iriStr}/start`, {
34
- types: ['prov:StartingPoint'],
35
- label: `start ${input.runId}`,
36
- classLabel: 'StartingPoint',
37
- classId: 'prov:StartingPoint',
38
- });
39
- startW.literal('prov:atTime', input.startedAt, { datatype: XSD.dateTime });
40
-
41
- const endW = graph.declare(`${iriStr}/end`, {
42
- types: ['prov:EndingPoint'],
43
- label: `end ${input.runId}`,
44
- classLabel: 'EndingPoint',
45
- classId: 'prov:EndingPoint',
46
- });
47
- endW.literal('prov:atTime', input.endedAt, { datatype: XSD.dateTime });
48
-
49
- if (input.gitSha) {
50
- const commitIri = `https://glossarist.org/commit/${input.gitSha}`;
51
- w.iri(PROV.used, commitIri);
52
- const commitW = graph.declare(commitIri, {
53
- types: [PROV.Entity],
54
- label: input.gitSha,
55
- classLabel: 'Entity',
56
- classId: PROV.Entity,
57
- });
58
- if (input.gitBranch) commitW.literal(DCTERMS.description, `branch: ${input.gitBranch}`);
59
- }
60
-
61
- const toolIri = `https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`;
62
- w.iri(PROV.used, toolIri);
63
- const toolW = graph.declare(toolIri, {
64
- types: [PROV.Entity, 'prov:SoftwareAgent'],
65
- label: `${input.toolId} ${input.toolVersion}`,
66
- classLabel: 'SoftwareAgent',
67
- classId: 'prov:SoftwareAgent',
68
- });
69
- toolW.literal(DCTERMS.identifier, input.toolVersion);
70
- toolW.literal('prov:version', input.toolVersion);
71
-
72
- for (const register of input.datasetRegisters) {
73
- w.iri(PROV.used, `https://glossarist.org/${register}/`);
74
- }
75
-
76
- w.literal('gloss:conceptCount', String(input.conceptCount), { datatype: XSD.integer });
77
-
78
- if (input.associatedAgentIri) {
79
- w.iri(PROV.wasAssociatedWith, input.associatedAgentIri);
80
- graph.declare(input.associatedAgentIri, {
81
- types: [PROV.Agent, FOAF.Person],
82
- label: input.associatedAgentIri.split('/').pop() ?? 'agent',
83
- classLabel: 'Agent',
84
- classId: PROV.Agent,
85
- });
86
- }
87
-
88
- return graph;
89
- }
@@ -1,443 +0,0 @@
1
- import type { Concept, LocalizedConcept, Designation, NonVerbRep, ConceptSource } from 'glossarist';
2
- import type { Expression, Abbreviation, GraphicalSymbol } from 'glossarist';
3
- import { GLOSS, SKOS, SKOSXL, DCTERMS, RDF, XSD, PROV } from './predicates';
4
- import { RdfGraph, lit, iri, blank, triple } from './rdf-graph';
5
- import type { RdfTriple } from './rdf-graph';
6
-
7
- const DESIGNATION_CLASS: Record<string, string> = {
8
- expression: GLOSS.Expression,
9
- abbreviation: GLOSS.Abbreviation,
10
- symbol: GLOSS.Symbol,
11
- letter_symbol: GLOSS.LetterSymbol,
12
- graphical_symbol: GLOSS.GraphicalSymbol,
13
- };
14
-
15
- function designationClassId(type: string): string {
16
- return DESIGNATION_CLASS[type] ?? GLOSS.Designation;
17
- }
18
-
19
- function desigSlug(designation: string, index: number): string {
20
- const slug = designation.replace(/[^a-zA-Z0-9]/g, '_');
21
- if (/^_+$/.test(slug)) return `d${index}`;
22
- return slug;
23
- }
24
-
25
- function coerceToDateTime(value: string): string {
26
- if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return `${value}T00:00:00Z`;
27
- if (/^\d{4}-\d{2}$/.test(value)) return `${value}-01T00:00:00Z`;
28
- if (/^\d{4}$/.test(value)) return `${value}-01-01T00:00:00Z`;
29
- return value;
30
- }
31
-
32
- interface DetailedDefinitionLike {
33
- readonly content: string;
34
- readonly examples?: readonly DetailedDefinitionLike[];
35
- readonly sources?: readonly ConceptSource[];
36
- }
37
-
38
- function detailedDefinitionTriples(def: DetailedDefinitionLike, lang: string): RdfTriple[] {
39
- const out: RdfTriple[] = [
40
- triple(RDF.type, iri(GLOSS.DetailedDefinition)),
41
- triple(RDF.value, lit(def.content, { lang })),
42
- ];
43
- for (const ex of def.examples ?? []) {
44
- if (ex.content) {
45
- out.push(triple(GLOSS.hasExample, blank(...detailedDefinitionTriples(ex, lang))));
46
- }
47
- }
48
- return out;
49
- }
50
-
51
- const STANDARDS_BODIES = new Set(['iso', 'iec', 'ieee', 'itu', 'iecdis']);
52
-
53
- function lifecycleCounterpartUri(r: { type: string; ref?: { source?: string | null; id?: string | null } | null }): string | undefined {
54
- const source = r.ref?.source ?? undefined;
55
- const id = r.ref?.id ?? undefined;
56
- if (!source || !id) return undefined;
57
- const sourceLc = source.toLowerCase();
58
- const idClean = id.replace(/\s+/g, '');
59
- if (STANDARDS_BODIES.has(sourceLc)) {
60
- return `urn:iso:std:${sourceLc}:${idClean}`;
61
- }
62
- return `urn:gloss:rel:${sourceLc}:${idClean}`;
63
- }
64
-
65
- function formatCitation(origin: ConceptSource['origin']): string {
66
- if (!origin) return '';
67
- const ref = origin.ref;
68
- if (ref?.source) {
69
- return ref.id ? `${ref.source} ${ref.id}` : ref.source;
70
- }
71
- return '';
72
- }
73
-
74
- function sourceTriples(s: ConceptSource): RdfTriple[] {
75
- const out: RdfTriple[] = [
76
- triple(RDF.type, iri(GLOSS.ConceptSource)),
77
- ];
78
-
79
- if (s.status) out.push(triple(GLOSS.sourceStatus, iri(`gloss:srcstatus/${s.status}`)));
80
- if (s.type) out.push(triple(GLOSS.sourceType, iri(`gloss:srctype/${s.type}`)));
81
- if (s.modification) out.push(triple(GLOSS.modification, lit(s.modification)));
82
-
83
- const origin = s.origin;
84
- const ref = origin?.ref;
85
- const locality = origin?.locality;
86
- const link = origin?.link;
87
- const original = origin?.original;
88
- const citation = formatCitation(origin);
89
-
90
- const hasCitationPayload = !!(ref?.source || ref?.id || ref?.version || locality?.type || locality?.referenceFrom || locality?.referenceTo || link || original || citation);
91
-
92
- if (hasCitationPayload) {
93
- const citeTriples: RdfTriple[] = [triple(RDF.type, iri(GLOSS.Citation))];
94
- if (citation) citeTriples.push(triple(DCTERMS.bibliographicCitation, lit(citation)));
95
-
96
- if (ref?.source || ref?.id || ref?.version) {
97
- const refTriples: RdfTriple[] = [triple(RDF.type, iri(GLOSS.CitationRef))];
98
- if (ref.source) refTriples.push(triple(GLOSS.citationRefSource, lit(ref.source)));
99
- if (ref.id) refTriples.push(triple(GLOSS.citationRefId, lit(ref.id)));
100
- if (ref.version) refTriples.push(triple(GLOSS.citationRefVersion, lit(ref.version)));
101
- citeTriples.push(triple(GLOSS.hasCitationRef, blank(...refTriples)));
102
- }
103
-
104
- if (locality?.type || locality?.referenceFrom || locality?.referenceTo) {
105
- const locTriples: RdfTriple[] = [triple(RDF.type, iri(GLOSS.Locality))];
106
- if (locality.type) locTriples.push(triple(GLOSS.localityType, lit(locality.type)));
107
- if (locality.referenceFrom) locTriples.push(triple(GLOSS.referenceFrom, lit(locality.referenceFrom)));
108
- if (locality.referenceTo) locTriples.push(triple(GLOSS.referenceTo, lit(locality.referenceTo)));
109
- citeTriples.push(triple(GLOSS.hasCitationLocality, blank(...locTriples)));
110
- }
111
-
112
- if (link) citeTriples.push(triple(GLOSS.citationLink, lit(link, { datatype: XSD.anyURI })));
113
- if (original) citeTriples.push(triple(GLOSS.citationOriginal, lit(original)));
114
-
115
- out.push(triple(GLOSS.sourceOrigin, blank(...citeTriples)));
116
- }
117
-
118
- return out;
119
- }
120
-
121
- export interface RefResolver {
122
- (ref: { source?: string | null; id?: string | null }): string | undefined;
123
- }
124
-
125
- export interface EmitOptions {
126
- readonly resolveRef?: RefResolver;
127
- }
128
-
129
- export interface EmitResult {
130
- readonly graph: RdfGraph;
131
- readonly designationUris: ReadonlyMap<string, string>;
132
- }
133
-
134
- export function emitConceptGraph(concept: Concept, uri: string, options: EmitOptions = {}): EmitResult {
135
- const graph = new RdfGraph();
136
- const designationUris = new Map<string, string>();
137
- emitConcept(graph, concept, uri, designationUris, options);
138
- return { graph, designationUris };
139
- }
140
-
141
- function emitConcept(
142
- graph: RdfGraph,
143
- concept: Concept,
144
- uri: string,
145
- designationUris: Map<string, string>,
146
- options: EmitOptions = {},
147
- ): void {
148
- const w = graph.declare(uri, {
149
- types: [GLOSS.Concept, SKOS.Concept],
150
- label: concept.id,
151
- classLabel: 'Concept',
152
- classId: GLOSS.Concept,
153
- });
154
-
155
- w.literal(GLOSS.identifier, concept.id);
156
- if (concept.status) w.iri(GLOSS.hasStatus, `gloss:status/${concept.status}`);
157
-
158
- for (const d of concept.domains) {
159
- const ref = d.conceptId || d.urn || '';
160
- if (ref) {
161
- w.blank(GLOSS.hasDomain, [
162
- triple(RDF.type, iri(GLOSS.Reference)),
163
- triple(GLOSS.refId, iri(ref)),
164
- ]);
165
- }
166
- }
167
-
168
- for (const s of concept.sources) {
169
- const triples = sourceTriples(s);
170
- if (triples.length) w.blank(GLOSS.hasSource, triples);
171
- }
172
-
173
- for (const d of concept.dates) {
174
- if (d.type && d.date) {
175
- w.blank(GLOSS.hasDate, [
176
- triple(RDF.type, iri(GLOSS.ConceptDate)),
177
- triple(GLOSS.dateType, iri(`gloss:datetype/${d.type}`)),
178
- triple(GLOSS.dateValue, lit(coerceToDateTime(d.date), { datatype: XSD.dateTime })),
179
- ]);
180
- }
181
- }
182
-
183
- for (const r of concept.relatedConcepts) {
184
- const inner: RdfTriple[] = [
185
- triple(RDF.type, iri(GLOSS.RelatedConcept)),
186
- triple(GLOSS.relationshipType, iri(`gloss:rel/${r.type}`)),
187
- ];
188
- if (r.content) inner.push(triple(GLOSS.relationshipContent, lit(r.content)));
189
- if (r.ref?.source || r.ref?.id) {
190
- const refTriples: RdfTriple[] = [triple(RDF.type, iri(GLOSS.ConceptRef))];
191
- if (r.ref?.source) refTriples.push(triple(GLOSS.conceptRefSource, lit(r.ref.source)));
192
- if (r.ref?.id) refTriples.push(triple(GLOSS.conceptRefId, lit(r.ref.id)));
193
- inner.push(triple(GLOSS.relationshipRef, blank(...refTriples)));
194
- }
195
- w.blank(GLOSS.hasRelatedConcept, inner);
196
-
197
- const resolved = options.resolveRef ? options.resolveRef(r.ref ?? {}) : undefined;
198
- const counterpartUri = resolved ?? lifecycleCounterpartUri(r);
199
- if (counterpartUri) {
200
- if (r.type === 'supersedes') w.iri(DCTERMS.replaces, counterpartUri);
201
- if (r.type === 'superseded_by') w.iri(DCTERMS.isReplacedBy, counterpartUri);
202
- }
203
- }
204
-
205
- const retiredDate = concept.dates.find(d => d.type === 'retired' && d.date);
206
- if ((concept.status === 'withdrawn' || concept.status === 'superseded') && retiredDate?.date) {
207
- w.literal(PROV.invalidatedAtTime, coerceToDateTime(retiredDate.date), { datatype: XSD.dateTime });
208
- }
209
-
210
- for (const lang of concept.languages) {
211
- w.iri(GLOSS.hasLocalization, `${uri}/${lang}`);
212
- }
213
-
214
- for (const lang of concept.languages) {
215
- const lc = concept.localization(lang);
216
- if (!lc) continue;
217
- emitLocalized(graph, lc, uri, lang, designationUris);
218
- }
219
- }
220
-
221
- function emitLocalized(
222
- graph: RdfGraph,
223
- lc: LocalizedConcept,
224
- uri: string,
225
- lang: string,
226
- designationUris: Map<string, string>,
227
- ): void {
228
- const lcUri = `${uri}/${lang}`;
229
- const w = graph.declare(lcUri, {
230
- types: [GLOSS.LocalizedConcept, SKOS.Concept],
231
- label: `${lang}: ${lc.primaryDesignation ?? ''}`,
232
- classLabel: 'LocalizedConcept',
233
- classId: GLOSS.LocalizedConcept,
234
- });
235
-
236
- w.literal(GLOSS.language, lang);
237
- if (lc.script) w.literal(GLOSS.script, lc.script);
238
- if (lc.system) w.literal(GLOSS.conversionSystem, lc.system);
239
- if (lc.entryStatus) w.iri(GLOSS.hasEntryStatus, `gloss:entstatus/${lc.entryStatus}`);
240
- if (lc.reviewType) w.literal(GLOSS.reviewType, lc.reviewType);
241
- w.iri(GLOSS.isLocalizationOf, uri);
242
- if (lc.classification) w.literal(GLOSS.classification, lc.classification);
243
- if (lc.release) w.literal(GLOSS.release, lc.release);
244
- if (lc.lineageSourceSimilarity != null) {
245
- w.literal(GLOSS.lineageSimilarity, String(lc.lineageSourceSimilarity));
246
- }
247
- if (lc.reviewDate) w.literal(GLOSS.reviewDate, lc.reviewDate);
248
- if (lc.reviewDecisionDate) w.literal(GLOSS.reviewDecisionDate, lc.reviewDecisionDate);
249
- if (lc.reviewStatus) w.literal(GLOSS.reviewStatus, lc.reviewStatus);
250
- if (lc.reviewDecision) w.literal(GLOSS.reviewDecision, lc.reviewDecision);
251
- if (lc.reviewDecisionEvent) w.literal(GLOSS.reviewDecisionEvent, lc.reviewDecisionEvent);
252
- if (lc.reviewDecisionNotes) w.literal(GLOSS.reviewDecisionNotes, lc.reviewDecisionNotes);
253
-
254
- for (let di = 0; di < lc.terms.length; di++) {
255
- const d = lc.terms[di];
256
- const slug = desigSlug(d.designation, di);
257
- const desigUri = `${lcUri}/desig/${slug}`;
258
- designationUris.set(`${lang}#${di}`, desigUri);
259
- const isPreferred = d.normativeStatus === 'preferred';
260
- w.iri(isPreferred ? SKOSXL.prefLabel : SKOSXL.altLabel, desigUri);
261
- w.literal(isPreferred ? SKOS.prefLabel : SKOS.altLabel, d.designation, { lang });
262
- emitDesignation(graph, d, desigUri, lang);
263
- }
264
-
265
- for (const def of lc.definitions) {
266
- if (def.content) {
267
- w.literal(SKOS.definition, def.content, { lang });
268
- w.blank(GLOSS.hasDefinition, detailedDefinitionTriples(def, lang));
269
- }
270
- }
271
-
272
- for (const n of lc.notes) {
273
- if (n.content) {
274
- w.blank(GLOSS.hasNote, detailedDefinitionTriples(n, lang));
275
- }
276
- }
277
-
278
- for (const e of lc.examples) {
279
- if (e.content) {
280
- w.blank(GLOSS.hasExample, detailedDefinitionTriples(e, lang));
281
- }
282
- }
283
-
284
- for (const ann of lc.annotations ?? []) {
285
- if (ann.content) w.literal(GLOSS.hasAnnotation, ann.content, { lang });
286
- }
287
-
288
- for (let i = 0; i < (lc.nonVerbalRep ?? []).length; i++) {
289
- const nvr = lc.nonVerbalRep[i];
290
- const emission = nonVerbalRepEmission(nvr, lang, uri, i);
291
- if (emission.kind === 'blank') {
292
- w.blank(GLOSS.hasNonVerbalRep, emission.triples);
293
- continue;
294
- }
295
- const nvrW = graph.declare(emission.nvrUri, {
296
- types: [GLOSS.NonVerbalRep, SKOS.Concept],
297
- label: nvr.caption ?? nvr.type ?? emission.nvrUri,
298
- classLabel: 'NonVerbalRep',
299
- classId: GLOSS.NonVerbalRep,
300
- });
301
- w.iri(GLOSS.hasNonVerbalRepresentation, emission.nvrUri);
302
- nvrW.addTriples(emission.triples);
303
- }
304
-
305
- for (const s of lc.sources) {
306
- const triples = sourceTriples(s);
307
- if (triples.length) w.blank(GLOSS.hasSource, triples);
308
- }
309
-
310
- if (lc.domain) w.literal(GLOSS.domain, lc.domain);
311
- }
312
-
313
- function nonVerbalRepTriples(nvr: NonVerbRep, lang: string): RdfTriple[] {
314
- const out: RdfTriple[] = [];
315
- const imageSrc = nvr.images?.find(i => i.src ?? false)?.src ?? undefined;
316
- const canonicalType = nvr.type === 'figure' ? 'image' : nvr.type;
317
-
318
- if ((canonicalType === 'image' || canonicalType === 'table' || canonicalType === 'formula') && imageSrc) {
319
- out.push(triple(RDF.type, iri('gloss:NonVerbalRepresentation')));
320
- out.push(triple(GLOSS.representationType, lit(canonicalType)));
321
- out.push(triple(GLOSS.representationRef, lit(imageSrc, { datatype: XSD.anyURI })));
322
- } else {
323
- out.push(triple(RDF.type, iri(GLOSS.DetailedDefinition)));
324
- if (nvr.caption) out.push(triple(RDF.value, lit(nvr.caption, { lang })));
325
- }
326
-
327
- if (nvr.caption) out.push(triple(GLOSS.caption, lit(nvr.caption, { lang })));
328
- if (nvr.description) out.push(triple(DCTERMS.description, lit(nvr.description, { lang })));
329
- if (nvr.alt) out.push(triple(GLOSS.altText, lit(nvr.alt, { lang })));
330
- return out;
331
- }
332
-
333
- type NonVerbalRepEmission =
334
- | { readonly kind: 'blank'; readonly triples: RdfTriple[] }
335
- | { readonly kind: 'uri'; readonly nvrUri: string; readonly triples: RdfTriple[] };
336
-
337
- function nvrSlug(nvr: NonVerbRep, index: number): string {
338
- const imageSrc = nvr.images?.find(i => i.src ?? false)?.src ?? undefined;
339
- if (imageSrc) {
340
- const basename = imageSrc.split('/').pop() ?? imageSrc;
341
- const slug = basename.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, '');
342
- if (slug) return slug;
343
- }
344
- if (nvr.caption) {
345
- const slug = (nvr.caption as string).replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, '').slice(0, 40);
346
- if (slug) return slug;
347
- }
348
- return `n${index + 1}`;
349
- }
350
-
351
- function nonVerbalRepEmission(nvr: NonVerbRep, lang: string, conceptUri: string, index: number): NonVerbalRepEmission {
352
- const imageSrc = nvr.images?.find(i => i.src ?? false)?.src ?? undefined;
353
- const caption = (nvr as any).caption ?? null;
354
- const alt = (nvr as any).alt ?? null;
355
- const description = (nvr as any).description ?? null;
356
- const hasPrefLabel = !!caption || !!alt;
357
-
358
- if (!hasPrefLabel && !imageSrc) {
359
- return { kind: 'blank', triples: nonVerbalRepTriples(nvr, lang) };
360
- }
361
-
362
- const nvrUri = `${conceptUri}/nvr/${nvrSlug(nvr, index)}`;
363
- const out: RdfTriple[] = [triple(RDF.type, iri(GLOSS.NonVerbalRep))];
364
- if (nvr.type) {
365
- const desigClass = designationClassId(nvr.type);
366
- if (desigClass !== GLOSS.Designation) {
367
- out.push(triple(RDF.type, iri(desigClass)));
368
- }
369
- }
370
- const label = caption ?? alt;
371
- if (label) {
372
- out.push(triple(SKOSXL.prefLabel, blank(
373
- triple(RDF.type, iri(SKOSXL.Label)),
374
- triple(SKOSXL.literalForm, lit(label, { lang })),
375
- )));
376
- }
377
- if (alt && alt !== caption) {
378
- out.push(triple(GLOSS.altText, lit(alt, { lang })));
379
- }
380
- if (imageSrc) {
381
- out.push(triple(GLOSS.image, lit(imageSrc, { datatype: XSD.anyURI })));
382
- }
383
- if (description) {
384
- out.push(triple(DCTERMS.description, lit(description, { lang })));
385
- }
386
- const link = nvr.sources?.[0]?.origin?.link ?? null;
387
- if (link) {
388
- out.push(triple(PROV.wasDerivedFrom, lit(link, { datatype: XSD.anyURI })));
389
- }
390
- return { kind: 'uri', nvrUri, triples: out };
391
- }
392
-
393
- function emitDesignation(
394
- graph: RdfGraph,
395
- d: Designation,
396
- desigUri: string,
397
- lang: string,
398
- ): void {
399
- const classId = designationClassId(d.type);
400
- const w = graph.declare(desigUri, {
401
- types: [classId, SKOSXL.Label],
402
- label: d.designation,
403
- classLabel: classId.replace('gloss:', ''),
404
- classId,
405
- });
406
-
407
- w.literal(SKOSXL.literalForm, d.designation, { lang });
408
- if (d.normativeStatus) w.iri(GLOSS.normativeStatus, `gloss:norm/${d.normativeStatus}`);
409
- if (d.geographicalArea) w.literal(GLOSS.geographicalArea, d.geographicalArea);
410
- if (d.international) w.literal(GLOSS.isInternational, 'true', { datatype: XSD.boolean });
411
- if (d.absent) w.literal(GLOSS.isAbsent, 'true', { datatype: XSD.boolean });
412
- if (d.termType) w.literal(GLOSS.hasTermType, d.termType);
413
- for (const p of d.pronunciations ?? []) {
414
- if (p.content) w.literal(GLOSS.hasPronunciation, p.content);
415
- }
416
-
417
- if (d.type === 'expression' || d.type === 'abbreviation') {
418
- const expr = d as Expression;
419
- if (expr.prefix) w.literal(GLOSS.prefix, expr.prefix);
420
- if (expr.usageInfo) w.literal(GLOSS.usageInfo, expr.usageInfo);
421
- if (expr.fieldOfApplication) w.literal(GLOSS.fieldOfApplication, expr.fieldOfApplication);
422
- for (const gi of expr.grammarInfo ?? []) {
423
- const parts: string[] = [];
424
- if (gi.gender) parts.push(`gender:${gi.gender}`);
425
- if (gi.number) parts.push(`number:${gi.number}`);
426
- if (gi.partOfSpeech) parts.push(`pos:${gi.partOfSpeech}`);
427
- if (parts.length) w.literal(GLOSS.hasGrammarInfo, parts.join(', '));
428
- }
429
- }
430
-
431
- if (d.type === 'abbreviation') {
432
- const abbr = d as Abbreviation;
433
- if (abbr.acronym) w.literal(GLOSS.isAcronym, 'true', { datatype: XSD.boolean });
434
- if (abbr.initialism) w.literal(GLOSS.isInitialism, 'true', { datatype: XSD.boolean });
435
- if (abbr.truncation) w.literal(GLOSS.isTruncation, 'true', { datatype: XSD.boolean });
436
- }
437
-
438
- if (d.type === 'graphical_symbol') {
439
- const gs = d as GraphicalSymbol;
440
- if (gs.text) w.literal(GLOSS.text, gs.text);
441
- if (gs.image) w.literal(GLOSS.image, gs.image);
442
- }
443
- }
@@ -1,95 +0,0 @@
1
- import { GLOSS, SKOS, DCTERMS, DCAT, RDF, RDFS, XSD, PROV } from './predicates';
2
- import { RdfGraph, lit, iri, blank, triple } from './rdf-graph';
3
-
4
- export interface DatasetEmitterInput {
5
- readonly datasetIri: string;
6
- readonly registerId: string;
7
- readonly title: string;
8
- readonly description?: string;
9
- readonly modified: string;
10
- readonly languages: readonly string[];
11
- readonly distributions: readonly DatasetDistribution[];
12
- readonly topConceptUris: readonly string[];
13
- readonly sections: readonly DatasetSection[];
14
- readonly sourceRepoUrl?: string;
15
- readonly publisherIri?: string;
16
- readonly contactIri?: string;
17
- }
18
-
19
- export interface DatasetDistribution {
20
- readonly id: string;
21
- readonly title: string;
22
- readonly mediaType: string;
23
- readonly downloadUrl: string;
24
- readonly byteSize?: number;
25
- }
26
-
27
- export interface DatasetSection {
28
- readonly collectionIri: string;
29
- readonly title: string;
30
- readonly memberUris: readonly string[];
31
- readonly parentCollectionIri?: string;
32
- readonly childCollectionIris?: readonly string[];
33
- }
34
-
35
- export function emitDatasetGraph(input: DatasetEmitterInput): RdfGraph {
36
- const graph = new RdfGraph();
37
- const w = graph.declare(input.datasetIri, {
38
- types: [DCAT.Dataset, SKOS.ConceptScheme],
39
- label: input.title,
40
- classLabel: 'Dataset',
41
- classId: DCAT.Dataset,
42
- });
43
-
44
- w.literal(DCTERMS.title, input.title);
45
- if (input.description) w.literal(DCTERMS.description, input.description);
46
- w.literal(DCTERMS.modified, input.modified, { datatype: XSD.date });
47
- w.literal(DCTERMS.identifier, input.registerId);
48
-
49
- for (const lang of input.languages) {
50
- const langIri = `http://id.loc.gov/vocabulary/iso639-1/${lang}`;
51
- w.iri(DCTERMS.language, langIri);
52
- }
53
-
54
- for (const dist of input.distributions) {
55
- const distTriples = [
56
- triple(RDF.type, iri(DCAT.Distribution)),
57
- triple(DCTERMS.title, lit(dist.title)),
58
- triple(DCAT.mediaType, lit(dist.mediaType)),
59
- triple(DCAT.downloadURL, iri(dist.downloadUrl)),
60
- ];
61
- if (dist.byteSize != null) {
62
- distTriples.push(triple(DCAT.byteSize, lit(String(dist.byteSize), { datatype: XSD.integer })));
63
- }
64
- w.blank(DCAT.distribution, distTriples);
65
- }
66
-
67
- for (const concept of input.topConceptUris) {
68
- w.iri(SKOS.hasTopConcept, concept);
69
- }
70
-
71
- if (input.sourceRepoUrl) w.iri(PROV.wasDerivedFrom, input.sourceRepoUrl);
72
- if (input.publisherIri) w.iri(DCTERMS.publisher, input.publisherIri);
73
- if (input.contactIri) w.iri(DCAT.contactPoint, input.contactIri);
74
-
75
- for (const section of input.sections) {
76
- const collectionW = graph.declare(section.collectionIri, {
77
- types: [SKOS.Collection],
78
- label: section.title,
79
- classLabel: 'Collection',
80
- classId: SKOS.Collection,
81
- });
82
- collectionW.literal(DCTERMS.title, section.title);
83
- for (const member of section.memberUris) {
84
- collectionW.iri(SKOS.member, member);
85
- }
86
- if (section.parentCollectionIri) {
87
- collectionW.iri('gloss:hasParentSection', section.parentCollectionIri);
88
- }
89
- for (const child of section.childCollectionIris ?? []) {
90
- collectionW.iri('gloss:hasChildSection', child);
91
- }
92
- }
93
-
94
- return graph;
95
- }
@@ -1,69 +0,0 @@
1
- import { DCAT, DCTERMS, RDF, SKOS, XSD, PROV } from './predicates';
2
- import { RdfGraph, lit, iri, blank, triple } from './rdf-graph';
3
- import type { RdfTriple } from './rdf-graph';
4
- import type { DatasetGroupKind } from '../../config/types';
5
-
6
- export interface GroupEmitInput {
7
- readonly groupId: string;
8
- readonly groupIri: string;
9
- readonly kind: DatasetGroupKind;
10
- readonly title: string;
11
- readonly description?: string;
12
- readonly memberIris: readonly string[];
13
- readonly currentMemberIri?: string;
14
- readonly baseUri?: string;
15
- readonly subject?: string;
16
- readonly themes?: readonly string[];
17
- readonly keywords?: readonly string[];
18
- readonly publisher?: string;
19
- readonly contact?: string;
20
- readonly sourceRepo?: string;
21
- }
22
-
23
- function rdfClassForKind(kind: DatasetGroupKind): string[] {
24
- switch (kind) {
25
- case 'lineage': return [DCAT.DatasetSeries, SKOS.ConceptScheme];
26
- case 'topic':
27
- case 'family':
28
- case 'collection': return [DCAT.Catalog, SKOS.ConceptScheme];
29
- default: return [];
30
- }
31
- }
32
-
33
- export function emitGroupGraph(input: GroupEmitInput): RdfGraph {
34
- const graph = new RdfGraph();
35
- const classes = rdfClassForKind(input.kind);
36
- if (classes.length === 0) return graph;
37
-
38
- const w = graph.declare(input.groupIri, {
39
- types: classes,
40
- label: input.title,
41
- classLabel: 'Group',
42
- classId: classes[0],
43
- });
44
-
45
- w.literal(DCTERMS.title, input.title);
46
- w.literal(DCTERMS.identifier, input.groupId);
47
- if (input.description) w.literal(DCTERMS.description, input.description);
48
- if (input.subject) w.literal(DCTERMS.subject, input.subject);
49
- for (const theme of input.themes ?? []) w.iri(DCAT.theme, theme);
50
- for (const kw of input.keywords ?? []) w.literal(DCAT.keyword, kw);
51
- if (input.publisher) w.iri(DCTERMS.publisher, input.publisher);
52
- if (input.contact) w.iri(DCAT.contactPoint, input.contact);
53
- if (input.sourceRepo) w.iri(PROV.wasDerivedFrom, input.sourceRepo);
54
-
55
- if (input.kind === 'lineage') {
56
- for (const memberIri of input.memberIris) {
57
- w.iri(DCAT.hasVersion, memberIri);
58
- }
59
- if (input.currentMemberIri) {
60
- w.iri(DCAT.hasCurrentVersion, input.currentMemberIri);
61
- }
62
- } else {
63
- for (const memberIri of input.memberIris) {
64
- w.iri(DCAT.dataset, memberIri);
65
- }
66
- }
67
-
68
- return graph;
69
- }