@glossarist/concept-browser 0.7.51 → 0.7.53
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/App.vue +2 -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__/config/group-renderers.test.ts +35 -0
- package/src/__tests__/config/group-types.test.ts +76 -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/glossarist-augment.d.ts +7 -0
- package/src/adapters/non-verbal-resolver.ts +2 -1
- 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/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/groups/DatasetGroupRenderer.vue +32 -0
- package/src/components/groups/DefaultGroupSidebar.vue +50 -0
- package/src/components/groups/LineageGroupSidebar.vue +75 -0
- package/src/composables/use-color-theme.ts +82 -0
- package/src/composables/use-format-registry.ts +42 -0
- package/src/composables/useDatasetSeries.ts +258 -0
- package/src/composables/useSphereProjection.ts +125 -0
- package/src/config/group-renderers.ts +27 -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 +187 -9
- package/src/views/DatasetView.vue +6 -0
- package/src/views/HomeView.vue +5 -0
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,443 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
}
|