@glossarist/concept-browser 0.3.4 → 0.4.0
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/README.md +3 -2
- package/cli/index.mjs +2 -1
- package/env.d.ts +5 -0
- package/package.json +4 -3
- package/scripts/build-edges.js +78 -10
- package/scripts/generate-data.mjs +152 -20
- package/scripts/generate-ontology-data.mjs +184 -0
- package/scripts/generate-ontology-schema.mjs +315 -0
- package/src/__tests__/about-view.test.ts +98 -0
- package/src/__tests__/app-footer.test.ts +38 -0
- package/src/__tests__/app-header.test.ts +130 -0
- package/src/__tests__/app-sidebar.test.ts +159 -0
- package/src/__tests__/asciidoc-lite.test.ts +1 -1
- package/src/__tests__/concept-card.test.ts +115 -0
- package/src/__tests__/concept-detail-interaction.test.ts +273 -0
- package/src/__tests__/concept-formats.test.ts +32 -30
- package/src/__tests__/concept-timeline.test.ts +200 -0
- package/src/__tests__/concept-view.test.ts +88 -0
- package/src/__tests__/contributors-view.test.ts +103 -0
- package/src/__tests__/dataset-adapter.test.ts +172 -23
- package/src/__tests__/dataset-view.test.ts +232 -0
- package/src/__tests__/designation-registry.test.ts +161 -0
- package/src/__tests__/format-downloads.test.ts +98 -0
- package/src/__tests__/graph-view.test.ts +69 -0
- package/src/__tests__/graph.test.ts +62 -0
- package/src/__tests__/home-interaction.test.ts +157 -0
- package/src/__tests__/language-detail.test.ts +203 -0
- package/src/__tests__/nav-icon.test.ts +48 -0
- package/src/__tests__/news-view.test.ts +87 -0
- package/src/__tests__/ontology-registry.test.ts +109 -0
- package/src/__tests__/page-view.test.ts +83 -0
- package/src/__tests__/relationship-categories.test.ts +62 -0
- package/src/__tests__/resolve-view.test.ts +77 -0
- package/src/__tests__/router.test.ts +65 -0
- package/src/__tests__/search-bar.test.ts +219 -0
- package/src/__tests__/search-view.test.ts +41 -0
- package/src/__tests__/stats-view.test.ts +77 -0
- package/src/__tests__/test-helpers.ts +171 -0
- package/src/__tests__/ui-store.test.ts +100 -0
- package/src/__tests__/v-math.test.ts +8 -7
- package/src/adapters/DatasetAdapter.ts +188 -63
- package/src/adapters/model-bridge.ts +277 -0
- package/src/adapters/ontology-registry.ts +75 -0
- package/src/adapters/ontology-schema.ts +100 -0
- package/src/adapters/types.ts +53 -78
- package/src/components/AppSidebar.vue +1 -1
- package/src/components/CitationDisplay.vue +35 -0
- package/src/components/ConceptDetail.vue +349 -146
- package/src/components/ConceptRdfView.vue +397 -0
- package/src/components/ConceptTimeline.vue +57 -60
- package/src/components/GraphPanel.vue +96 -31
- package/src/components/LanguageDetail.vue +46 -61
- package/src/components/NavIcon.vue +1 -0
- package/src/components/NonVerbalRepDisplay.vue +38 -0
- package/src/components/RelationshipList.vue +99 -0
- package/src/composables/use-render-options.ts +1 -4
- package/src/config/use-site-config.ts +3 -0
- package/src/data/ontology-schema.json +1551 -0
- package/src/data/taxonomies.json +543 -0
- package/src/graph/GraphEngine.ts +7 -4
- package/src/router/index.ts +6 -1
- package/src/shims/empty.ts +1 -0
- package/src/shims/node-crypto.ts +6 -0
- package/src/shims/node-path.ts +10 -0
- package/src/stores/vocabulary.ts +82 -32
- package/src/style.css +74 -20
- package/src/utils/asciidoc-lite.ts +17 -19
- package/src/utils/concept-formats.ts +22 -20
- package/src/utils/concept-helpers.ts +54 -0
- package/src/utils/designation-registry.ts +124 -0
- package/src/utils/escape.ts +7 -0
- package/src/utils/markdown-lite.ts +1 -3
- package/src/utils/math.ts +2 -11
- package/src/utils/plurimath.ts +2 -7
- package/src/utils/relationship-categories.ts +84 -0
- package/src/views/ConceptView.vue +22 -1
- package/src/views/DatasetView.vue +7 -2
- package/src/views/OntologySchemaView.vue +302 -0
- package/src/views/PageView.vue +28 -17
- package/src/views/StatsView.vue +34 -12
- package/vite.config.ts +8 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model bridge: converts between wire-format JSON and glossarist-js model instances.
|
|
3
|
+
*
|
|
4
|
+
* Supports two input formats:
|
|
5
|
+
* 1. JSON-LD (gl:-prefixed) — legacy format from current generate-data.mjs
|
|
6
|
+
* 2. Glossarist native — snake_case format from glossarist-js Concept.toJSON()
|
|
7
|
+
*
|
|
8
|
+
* All downstream code works exclusively with Concept instances.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
Concept,
|
|
12
|
+
LocalizedConcept,
|
|
13
|
+
Designation,
|
|
14
|
+
Expression,
|
|
15
|
+
Abbreviation,
|
|
16
|
+
Symbol as SymbolDesignation,
|
|
17
|
+
GraphicalSymbol,
|
|
18
|
+
Citation,
|
|
19
|
+
ConceptSource,
|
|
20
|
+
RelatedConcept,
|
|
21
|
+
ConceptDate,
|
|
22
|
+
DetailedDefinition,
|
|
23
|
+
NonVerbRep,
|
|
24
|
+
RELATIONSHIP_TYPES,
|
|
25
|
+
DATE_TYPES,
|
|
26
|
+
} from 'glossarist';
|
|
27
|
+
import {
|
|
28
|
+
LetterSymbol,
|
|
29
|
+
GrammarInfo,
|
|
30
|
+
Pronunciation,
|
|
31
|
+
ConceptReference,
|
|
32
|
+
Locality,
|
|
33
|
+
GRAMMAR_GENDERS,
|
|
34
|
+
GRAMMAR_NUMBERS,
|
|
35
|
+
GRAMMAR_PARTS_OF_SPEECH,
|
|
36
|
+
} from 'glossarist/models';
|
|
37
|
+
import type { ConceptSummary } from './types';
|
|
38
|
+
|
|
39
|
+
// ── Detection ─────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function isJsonLd(doc: Record<string, unknown>): boolean {
|
|
42
|
+
return '@type' in doc && 'gl:localizedConcept' in doc;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── JSON-LD → Glossarist native mapping ───────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function mapDesignationFromJsonLd(d: any): Record<string, unknown> {
|
|
48
|
+
const result: Record<string, unknown> = {};
|
|
49
|
+
const rawType = (d['@type'] as string) || '';
|
|
50
|
+
|
|
51
|
+
// Map type
|
|
52
|
+
if (rawType.includes('Abbreviation')) result.type = 'abbreviation';
|
|
53
|
+
else if (rawType.includes('LetterSymbol')) result.type = 'letter_symbol';
|
|
54
|
+
else if (rawType.includes('GraphicalSymbol')) result.type = 'graphical_symbol';
|
|
55
|
+
else if (rawType.includes('Symbol')) result.type = 'symbol';
|
|
56
|
+
else result.type = 'expression';
|
|
57
|
+
|
|
58
|
+
result.designation = d['gl:term'] ?? '';
|
|
59
|
+
result.normative_status = d['gl:normativeStatus'] ?? null;
|
|
60
|
+
|
|
61
|
+
if (d['gl:absent'] != null) result.absent = d['gl:absent'];
|
|
62
|
+
if (d['gl:fieldOfApplication']) result.field_of_application = d['gl:fieldOfApplication'];
|
|
63
|
+
if (d['gl:usageInfo']) result.usage_info = d['gl:usageInfo'];
|
|
64
|
+
if (d['gl:geographicalArea']) result.geographical_area = d['gl:geographicalArea'];
|
|
65
|
+
if (d['gl:language']) result.language = d['gl:language'];
|
|
66
|
+
if (d['gl:script']) result.script = d['gl:script'];
|
|
67
|
+
if (d['gl:system']) result.system = d['gl:system'];
|
|
68
|
+
if (d['gl:international'] != null) result.international = d['gl:international'];
|
|
69
|
+
if (d['gl:termType']) result.term_type = d['gl:termType'];
|
|
70
|
+
|
|
71
|
+
if (d['gl:pronunciation']?.length) {
|
|
72
|
+
result.pronunciation = d['gl:pronunciation'].map((p: any) => ({
|
|
73
|
+
content: p['gl:content'] ?? null,
|
|
74
|
+
language: p['gl:language'] ?? null,
|
|
75
|
+
script: p['gl:script'] ?? null,
|
|
76
|
+
system: p['gl:system'] ?? null,
|
|
77
|
+
country: p['gl:country'] ?? null,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (d['gl:source']?.length) {
|
|
82
|
+
result.sources = d['gl:source'].map(mapSourceFromJsonLd);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (d['gl:related']?.length) {
|
|
86
|
+
result.related = d['gl:related'].map(mapRelatedFromJsonLd);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Expression-specific
|
|
90
|
+
if (d['gl:prefix'] != null) result.prefix = d['gl:prefix'];
|
|
91
|
+
if (d['gl:gender']) {
|
|
92
|
+
result.grammar_info = [{ gender: d['gl:gender'] }];
|
|
93
|
+
}
|
|
94
|
+
if (d['gl:grammarInfo']?.length) {
|
|
95
|
+
result.grammar_info = d['gl:grammarInfo'].map((gi: any) => ({
|
|
96
|
+
gender: gi['gl:gender'] ?? null,
|
|
97
|
+
number: gi['gl:number'] ?? null,
|
|
98
|
+
part_of_speech: gi['gl:partOfSpeech'] ?? null,
|
|
99
|
+
noun: gi['gl:noun'] ?? false,
|
|
100
|
+
verb: gi['gl:verb'] ?? false,
|
|
101
|
+
adj: gi['gl:adj'] ?? false,
|
|
102
|
+
adverb: gi['gl:adverb'] ?? false,
|
|
103
|
+
preposition: gi['gl:preposition'] ?? false,
|
|
104
|
+
participle: gi['gl:participle'] ?? false,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function mapSourceFromJsonLd(s: any): Record<string, unknown> {
|
|
112
|
+
const result: Record<string, unknown> = {};
|
|
113
|
+
if (s['gl:sourceType']) result.type = s['gl:sourceType'];
|
|
114
|
+
if (s['gl:sourceStatus']) result.status = s['gl:sourceStatus'];
|
|
115
|
+
if (s['gl:modification']) result.modification = s['gl:modification'];
|
|
116
|
+
|
|
117
|
+
if (s['gl:origin']) {
|
|
118
|
+
const origin: Record<string, unknown> = {};
|
|
119
|
+
const o = s['gl:origin'];
|
|
120
|
+
if (o['gl:ref']) {
|
|
121
|
+
const rawRef = o['gl:ref'];
|
|
122
|
+
const refObj: Record<string, unknown> = {};
|
|
123
|
+
// v3: ref is always an object { gl:source, gl:id, gl:version }
|
|
124
|
+
if (rawRef['gl:source']) refObj.source = rawRef['gl:source'];
|
|
125
|
+
if (rawRef['gl:id']) refObj.id = rawRef['gl:id'];
|
|
126
|
+
if (rawRef['gl:version']) refObj.version = rawRef['gl:version'];
|
|
127
|
+
if (rawRef['source']) refObj.source = rawRef['source'];
|
|
128
|
+
if (rawRef['id']) refObj.id = rawRef['id'];
|
|
129
|
+
if (rawRef['version']) refObj.version = rawRef['version'];
|
|
130
|
+
if (Object.keys(refObj).length > 0) origin.ref = refObj;
|
|
131
|
+
}
|
|
132
|
+
if (o['gl:locality']) {
|
|
133
|
+
const loc: Record<string, unknown> = {};
|
|
134
|
+
const rawLoc = o['gl:locality'];
|
|
135
|
+
if (rawLoc['gl:localityType']) loc.type = rawLoc['gl:localityType'];
|
|
136
|
+
if (rawLoc['gl:referenceFrom']) loc.reference_from = rawLoc['gl:referenceFrom'];
|
|
137
|
+
if (rawLoc['gl:referenceTo']) loc.reference_to = rawLoc['gl:referenceTo'];
|
|
138
|
+
if (rawLoc['type']) loc.type = rawLoc['type'];
|
|
139
|
+
if (rawLoc['reference_from']) loc.reference_from = rawLoc['reference_from'];
|
|
140
|
+
if (rawLoc['reference_to']) loc.reference_to = rawLoc['reference_to'];
|
|
141
|
+
origin.locality = loc;
|
|
142
|
+
}
|
|
143
|
+
if (o['gl:link']) origin.link = o['gl:link'];
|
|
144
|
+
if (o['gl:id']) origin.id = o['gl:id'];
|
|
145
|
+
if (o['gl:version']) origin.version = o['gl:version'];
|
|
146
|
+
if (o['gl:source']) origin.source = o['gl:source'];
|
|
147
|
+
result.origin = origin;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mapRelatedFromJsonLd(r: any): Record<string, unknown> {
|
|
154
|
+
const result: Record<string, unknown> = { type: 'references' };
|
|
155
|
+
if (r['@id']) {
|
|
156
|
+
// Extract concept ID from URI: ".../concept/3.1.1.6" → source=register, id=3.1.1.6
|
|
157
|
+
const uri = r['@id'] as string;
|
|
158
|
+
const idMatch = uri.match(/\/concept\/([^/]+)$/);
|
|
159
|
+
result.ref = idMatch
|
|
160
|
+
? { source: uri.split('/').slice(-3, -2)[0] || '', id: idMatch[1] }
|
|
161
|
+
: { source: uri, id: null };
|
|
162
|
+
}
|
|
163
|
+
if (r['gl:term']) result.content = r['gl:term'];
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
|
|
168
|
+
const data: Record<string, unknown> = {};
|
|
169
|
+
|
|
170
|
+
if (lc['gl:languageCode']) data.language_code = lc['gl:languageCode'];
|
|
171
|
+
if (lc['gl:entryStatus']) data.entry_status = lc['gl:entryStatus'];
|
|
172
|
+
if (lc['gl:classification']) data.classification = lc['gl:classification'];
|
|
173
|
+
if (lc['gl:reviewType']) data.review_type = lc['gl:reviewType'];
|
|
174
|
+
if (lc['gl:domain']) data.domain = lc['gl:domain'];
|
|
175
|
+
if (lc['gl:release']) data.release = lc['gl:release'];
|
|
176
|
+
if (lc['gl:lineageSourceSimilarity'] != null) data.lineage_source_similarity = lc['gl:lineageSourceSimilarity'];
|
|
177
|
+
if (lc['gl:script']) data.script = lc['gl:script'];
|
|
178
|
+
if (lc['gl:system']) data.system = lc['gl:system'];
|
|
179
|
+
|
|
180
|
+
if (lc['gl:designation']?.length) {
|
|
181
|
+
data.terms = lc['gl:designation'].map(mapDesignationFromJsonLd);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (lc['gl:definition']?.length) {
|
|
185
|
+
data.definition = lc['gl:definition'].map((d: any) => {
|
|
186
|
+
const def: Record<string, unknown> = { content: d['gl:content'] ?? '' };
|
|
187
|
+
return def;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (lc['gl:notes']?.length) {
|
|
192
|
+
data.notes = lc['gl:notes'].map((n: any) => ({ content: n['gl:content'] ?? '' }));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (lc['gl:examples']?.length) {
|
|
196
|
+
data.examples = lc['gl:examples'].map((e: any) => ({ content: e['gl:content'] ?? '' }));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (lc['gl:source']?.length) {
|
|
200
|
+
data.sources = lc['gl:source'].map(mapSourceFromJsonLd);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (lc['gl:dates']?.length) {
|
|
204
|
+
data.dates = lc['gl:dates'].map((d: any) => ({
|
|
205
|
+
date: d['gl:date'] ?? null,
|
|
206
|
+
type: d['gl:dateType'] ?? null,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (lc['gl:references']?.length) {
|
|
211
|
+
data.related = lc['gl:references'].map(mapRelatedFromJsonLd);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Review metadata — passed through to LocalizedConcept constructor
|
|
215
|
+
if (lc['gl:reviewDate']) data.review_date = lc['gl:reviewDate'];
|
|
216
|
+
if (lc['gl:reviewDecisionDate']) data.review_decision_date = lc['gl:reviewDecisionDate'];
|
|
217
|
+
if (lc['gl:reviewDecisionEvent']) data.review_decision_event = lc['gl:reviewDecisionEvent'];
|
|
218
|
+
if (lc['gl:reviewStatus']) data.review_status = lc['gl:reviewStatus'];
|
|
219
|
+
if (lc['gl:reviewDecision']) data.review_decision = lc['gl:reviewDecision'];
|
|
220
|
+
if (lc['gl:reviewDecisionNotes']) data.review_decision_notes = lc['gl:reviewDecisionNotes'];
|
|
221
|
+
|
|
222
|
+
return data;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function conceptFromJsonLd(doc: Record<string, any>): Concept {
|
|
226
|
+
const id = String(doc['gl:identifier'] ?? doc['@id']?.split('/').pop() ?? '');
|
|
227
|
+
const localizations: Record<string, any> = {};
|
|
228
|
+
|
|
229
|
+
const rawLc = doc['gl:localizedConcept'] ?? {};
|
|
230
|
+
for (const [lang, lc] of Object.entries(rawLc)) {
|
|
231
|
+
if (lc && typeof lc === 'object') {
|
|
232
|
+
localizations[lang] = mapLocalizedFromJsonLd(lc);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const related = (doc['gl:references'] ?? []).map(mapRelatedFromJsonLd);
|
|
237
|
+
|
|
238
|
+
return Concept.fromJSON({
|
|
239
|
+
id,
|
|
240
|
+
term: doc['gl:term'] ?? null,
|
|
241
|
+
uri: doc['@id'] ?? null,
|
|
242
|
+
localizations,
|
|
243
|
+
related,
|
|
244
|
+
status: null,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
export function conceptFromJson(doc: Record<string, any>): Concept {
|
|
251
|
+
if (isJsonLd(doc)) {
|
|
252
|
+
return conceptFromJsonLd(doc);
|
|
253
|
+
}
|
|
254
|
+
// glossarist native format — use fromJSON directly
|
|
255
|
+
return Concept.fromJSON(doc as Record<string, unknown>);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function conceptToSummary(concept: Concept): ConceptSummary {
|
|
259
|
+
const designations: Record<string, string> = {};
|
|
260
|
+
for (const lang of concept.languages) {
|
|
261
|
+
const lc = concept.localization(lang);
|
|
262
|
+
if (lc?.primaryDesignation) {
|
|
263
|
+
designations[lang] = lc.primaryDesignation;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
id: concept.id,
|
|
268
|
+
designations,
|
|
269
|
+
eng: designations.eng || Object.values(designations)[0] || '',
|
|
270
|
+
status: concept.status ?? 'valid',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function conceptUri(concept: Concept, registerId: string, uriBase: string): string {
|
|
275
|
+
if (concept.uri) return concept.uri;
|
|
276
|
+
return `${uriBase}/${registerId}/concept/${concept.id}`;
|
|
277
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ontology Registry — taxonomy-driven labels and definitions for the browser.
|
|
3
|
+
*
|
|
4
|
+
* All enumeration labels, definitions, and colors come from the SKOS taxonomy
|
|
5
|
+
* data extracted at build time from concept-model/ontologies/taxonomies/*.ttl.
|
|
6
|
+
* The browser never hardcodes taxonomy values — it looks them up here.
|
|
7
|
+
*/
|
|
8
|
+
import taxonomyData from '../data/taxonomies.json';
|
|
9
|
+
|
|
10
|
+
export interface TaxonomyConcept {
|
|
11
|
+
id: string;
|
|
12
|
+
iri: string;
|
|
13
|
+
prefLabel: string;
|
|
14
|
+
altLabel?: string;
|
|
15
|
+
definition?: string;
|
|
16
|
+
broader?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Taxonomy {
|
|
20
|
+
scheme: string;
|
|
21
|
+
schemeLabel: string | null;
|
|
22
|
+
schemeDefinition: string | null;
|
|
23
|
+
concepts: Record<string, TaxonomyConcept>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type TaxonomyKey = keyof typeof taxonomyData;
|
|
27
|
+
|
|
28
|
+
class OntologyRegistry {
|
|
29
|
+
private data: Record<string, Taxonomy>;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
this.data = taxonomyData as unknown as Record<string, Taxonomy>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getConcept(taxonomy: TaxonomyKey, id: string): TaxonomyConcept | null {
|
|
36
|
+
return this.data[taxonomy]?.concepts[id] ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getLabel(taxonomy: TaxonomyKey, id: string | null | undefined): string {
|
|
40
|
+
if (!id) return '';
|
|
41
|
+
return this.getConcept(taxonomy, id)?.prefLabel ?? id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getAltLabel(taxonomy: TaxonomyKey, id: string): string | null {
|
|
45
|
+
return this.getConcept(taxonomy, id)?.altLabel ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getDefinition(taxonomy: TaxonomyKey, id: string): string | null {
|
|
49
|
+
return this.getConcept(taxonomy, id)?.definition ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getAll(taxonomy: TaxonomyKey): TaxonomyConcept[] {
|
|
53
|
+
return Object.values(this.data[taxonomy]?.concepts ?? {});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getScheme(taxonomy: TaxonomyKey): string {
|
|
57
|
+
return this.data[taxonomy]?.scheme ?? '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
has(taxonomy: TaxonomyKey, id: string): boolean {
|
|
61
|
+
return id in (this.data[taxonomy]?.concepts ?? {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Get broader concept ID, if any (for hierarchical taxonomies like designation-type). */
|
|
65
|
+
getBroader(taxonomy: TaxonomyKey, id: string): string | null {
|
|
66
|
+
return this.getConcept(taxonomy, id)?.broader ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Get all child concept IDs of a given concept. */
|
|
70
|
+
getNarrower(taxonomy: TaxonomyKey, id: string): TaxonomyConcept[] {
|
|
71
|
+
return this.getAll(taxonomy).filter(c => c.broader === id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const ontology = new OntologyRegistry();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ontology schema loader — provides class/property definitions parsed from
|
|
3
|
+
* the Glossarist OWL ontology for the Ontospy-style concept view.
|
|
4
|
+
*/
|
|
5
|
+
import schemaData from '../data/ontology-schema.json';
|
|
6
|
+
|
|
7
|
+
export interface OwlClass {
|
|
8
|
+
iri: string;
|
|
9
|
+
compact: string;
|
|
10
|
+
label: string;
|
|
11
|
+
comment: string | null;
|
|
12
|
+
subClassOf: string | null;
|
|
13
|
+
disjointWith: string | null;
|
|
14
|
+
children: string[];
|
|
15
|
+
ancestors: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OwlProperty {
|
|
19
|
+
iri: string;
|
|
20
|
+
compact: string;
|
|
21
|
+
label: string;
|
|
22
|
+
comment: string | null;
|
|
23
|
+
type: 'object' | 'datatype';
|
|
24
|
+
domain: string | null;
|
|
25
|
+
domainUnion: string[] | null;
|
|
26
|
+
range: string | null;
|
|
27
|
+
rangeUnion: string[] | null;
|
|
28
|
+
inverseOf: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface OntologySchema {
|
|
32
|
+
ontologyIri: string;
|
|
33
|
+
ontologyLabel: string;
|
|
34
|
+
classes: Record<string, OwlClass>;
|
|
35
|
+
classHierarchyRoots: string[];
|
|
36
|
+
properties: Record<string, OwlProperty>;
|
|
37
|
+
propertiesByDomain: Record<string, { object: string[]; datatype: string[] }>;
|
|
38
|
+
stats: { classCount: number; objectPropertyCount: number; datatypePropertyCount: number };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const data = schemaData as unknown as OntologySchema;
|
|
42
|
+
|
|
43
|
+
export function getClass(id: string): OwlClass | null {
|
|
44
|
+
return data.classes[id] ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getProperty(id: string): OwlProperty | null {
|
|
48
|
+
return data.properties[id] ?? null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getPropertiesForDomain(domain: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
52
|
+
const group = data.propertiesByDomain[domain];
|
|
53
|
+
if (!group) return { object: [], datatype: [] };
|
|
54
|
+
return {
|
|
55
|
+
object: group.object.map(id => data.properties[id]).filter(Boolean),
|
|
56
|
+
datatype: group.datatype.map(id => data.properties[id]).filter(Boolean),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Get all properties applicable to a class, including inherited ones. */
|
|
61
|
+
export function getAllPropertiesForClass(classId: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
62
|
+
const cls = data.classes[classId];
|
|
63
|
+
if (!cls) return { object: [], datatype: [] };
|
|
64
|
+
|
|
65
|
+
const classChain = [classId, ...cls.ancestors];
|
|
66
|
+
const objectProps: OwlProperty[] = [];
|
|
67
|
+
const datatypeProps: OwlProperty[] = [];
|
|
68
|
+
const seen = new Set<string>();
|
|
69
|
+
|
|
70
|
+
for (const c of classChain) {
|
|
71
|
+
const props = getPropertiesForDomain(c);
|
|
72
|
+
for (const p of props.object) {
|
|
73
|
+
if (!seen.has(p.compact)) { seen.add(p.compact); objectProps.push(p); }
|
|
74
|
+
}
|
|
75
|
+
for (const p of props.datatype) {
|
|
76
|
+
if (!seen.has(p.compact)) { seen.add(p.compact); datatypeProps.push(p); }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { object: objectProps, datatype: datatypeProps };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Get the full class hierarchy tree starting from roots. */
|
|
84
|
+
export function getClassTree(): OwlClass[] {
|
|
85
|
+
return data.classHierarchyRoots
|
|
86
|
+
.map(id => data.classes[id])
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getAllClasses(): OwlClass[] {
|
|
91
|
+
return Object.values(data.classes);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getAllProperties(): OwlProperty[] {
|
|
95
|
+
return Object.values(data.properties);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getStats() {
|
|
99
|
+
return data.stats;
|
|
100
|
+
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure types for the vocabulary browser.
|
|
3
|
+
* Concept model types come from glossarist-js via model-bridge.ts.
|
|
4
|
+
*/
|
|
5
|
+
import type { RELATIONSHIP_TYPES } from 'glossarist';
|
|
6
|
+
|
|
7
|
+
// Re-export glossarist model types for convenience
|
|
8
|
+
export type {
|
|
9
|
+
Concept,
|
|
10
|
+
LocalizedConcept,
|
|
11
|
+
Designation,
|
|
12
|
+
Expression,
|
|
13
|
+
Abbreviation,
|
|
14
|
+
Symbol as SymbolDesignation,
|
|
15
|
+
GraphicalSymbol,
|
|
16
|
+
Citation,
|
|
17
|
+
ConceptSource,
|
|
18
|
+
RelatedConcept,
|
|
19
|
+
ConceptDate,
|
|
20
|
+
DetailedDefinition,
|
|
21
|
+
NonVerbRep,
|
|
22
|
+
} from 'glossarist';
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
LetterSymbol,
|
|
26
|
+
GrammarInfo,
|
|
27
|
+
Pronunciation,
|
|
28
|
+
ConceptReference,
|
|
29
|
+
Locality,
|
|
30
|
+
} from 'glossarist/models';
|
|
31
|
+
|
|
32
|
+
export { RELATIONSHIP_TYPES, DATE_TYPES } from 'glossarist';
|
|
33
|
+
export { GRAMMAR_GENDERS, GRAMMAR_NUMBERS, GRAMMAR_PARTS_OF_SPEECH } from 'glossarist/models';
|
|
34
|
+
|
|
35
|
+
// ── Dataset metadata ──────────────────────────────────────────────────────
|
|
2
36
|
|
|
3
37
|
export interface Manifest {
|
|
4
38
|
id: string;
|
|
@@ -34,11 +68,12 @@ export interface ConceptIndex {
|
|
|
34
68
|
conceptCount: number;
|
|
35
69
|
chunkSize: number;
|
|
36
70
|
chunks: { file: string; count: number }[];
|
|
37
|
-
concepts: ConceptSummary[];
|
|
71
|
+
concepts: (ConceptSummary | undefined)[];
|
|
38
72
|
}
|
|
39
73
|
|
|
40
74
|
export interface ConceptSummary {
|
|
41
75
|
id: string;
|
|
76
|
+
designations: Record<string, string>;
|
|
42
77
|
eng: string;
|
|
43
78
|
status: string;
|
|
44
79
|
}
|
|
@@ -50,83 +85,25 @@ export interface ConceptEntry {
|
|
|
50
85
|
status: string;
|
|
51
86
|
}
|
|
52
87
|
|
|
53
|
-
export interface ConceptDocument {
|
|
54
|
-
'@context': string;
|
|
55
|
-
'@id': string;
|
|
56
|
-
'@type': string;
|
|
57
|
-
'gl:identifier': string;
|
|
58
|
-
'gl:localizedConcept': Record<string, LocalizedConcept>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface LocalizedConcept {
|
|
62
|
-
'@id': string;
|
|
63
|
-
'@type': string;
|
|
64
|
-
'gl:languageCode': string;
|
|
65
|
-
'gl:entryStatus'?: string;
|
|
66
|
-
'gl:designation'?: Designation[];
|
|
67
|
-
'gl:definition'?: DetailedDefinition[];
|
|
68
|
-
'gl:notes'?: DetailedDefinition[];
|
|
69
|
-
'gl:examples'?: DetailedDefinition[];
|
|
70
|
-
'gl:source'?: ConceptSource[];
|
|
71
|
-
'gl:release'?: number;
|
|
72
|
-
'gl:reviewDate'?: string;
|
|
73
|
-
'gl:reviewDecisionDate'?: string;
|
|
74
|
-
'gl:reviewDecisionEvent'?: string;
|
|
75
|
-
'gl:reviewStatus'?: string;
|
|
76
|
-
'gl:reviewDecision'?: string;
|
|
77
|
-
'gl:reviewDecisionNotes'?: string;
|
|
78
|
-
'gl:dates'?: ConceptDate[];
|
|
79
|
-
'gl:references'?: CrossReference[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface Designation {
|
|
83
|
-
'@type': string;
|
|
84
|
-
'gl:normativeStatus': string;
|
|
85
|
-
'gl:term': string;
|
|
86
|
-
'gl:gender'?: string;
|
|
87
|
-
'gl:plurality'?: string;
|
|
88
|
-
'gl:international'?: boolean;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface DetailedDefinition {
|
|
92
|
-
'@type': string;
|
|
93
|
-
'gl:content': string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface ConceptSource {
|
|
97
|
-
'@type': string;
|
|
98
|
-
'gl:sourceType'?: string;
|
|
99
|
-
'gl:sourceStatus'?: string;
|
|
100
|
-
'gl:modification'?: string;
|
|
101
|
-
'gl:origin'?: {
|
|
102
|
-
'@type': string;
|
|
103
|
-
'gl:ref'?: string;
|
|
104
|
-
'gl:clause'?: string;
|
|
105
|
-
'gl:link'?: string;
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface ConceptDate {
|
|
110
|
-
'gl:dateType': string;
|
|
111
|
-
'gl:date': string;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface CrossReference {
|
|
115
|
-
'@id': string;
|
|
116
|
-
'gl:term': string;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
88
|
export interface DatasetRegistry {
|
|
120
89
|
id: string;
|
|
121
90
|
manifestUrl: string;
|
|
122
91
|
}
|
|
123
92
|
|
|
93
|
+
// ── Graph types ────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export const EDGE_TYPE = {
|
|
96
|
+
REFERENCES: 'references',
|
|
97
|
+
DOMAIN: 'domain',
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
124
100
|
export interface GraphEdge {
|
|
125
|
-
source: string;
|
|
126
|
-
target: string;
|
|
101
|
+
source: string;
|
|
102
|
+
target: string;
|
|
127
103
|
type: string;
|
|
128
104
|
label?: string;
|
|
129
105
|
register: string;
|
|
106
|
+
lang?: string;
|
|
130
107
|
}
|
|
131
108
|
|
|
132
109
|
export interface GraphNode {
|
|
@@ -136,8 +113,11 @@ export interface GraphNode {
|
|
|
136
113
|
designations: Record<string, string>;
|
|
137
114
|
status: string;
|
|
138
115
|
loaded: boolean;
|
|
116
|
+
nodeType?: 'concept' | 'domain';
|
|
139
117
|
}
|
|
140
118
|
|
|
119
|
+
// ── Search ─────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
141
121
|
export interface SearchHit {
|
|
142
122
|
conceptId: string;
|
|
143
123
|
registerId: string;
|
|
@@ -147,14 +127,9 @@ export interface SearchHit {
|
|
|
147
127
|
snippet?: string;
|
|
148
128
|
}
|
|
149
129
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
| 'broader'
|
|
154
|
-
| 'see'
|
|
155
|
-
| 'references'
|
|
156
|
-
| 'replaces'
|
|
157
|
-
| 'superseded';
|
|
130
|
+
// ── Resolution ─────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
export type RelationType = typeof RELATIONSHIP_TYPES[number];
|
|
158
133
|
|
|
159
134
|
export type Resolution =
|
|
160
135
|
| { type: 'internal'; registerId: string; conceptId: string; crossDataset: boolean }
|
|
@@ -14,7 +14,7 @@ const route = useRoute();
|
|
|
14
14
|
const { getColor } = useDsStyle();
|
|
15
15
|
const { globalPages, datasetPages, config: siteConfig } = useSiteConfig();
|
|
16
16
|
|
|
17
|
-
const currentDataset = computed(() =>
|
|
17
|
+
const currentDataset = computed(() => route.params.registerId as string ?? '');
|
|
18
18
|
|
|
19
19
|
const datasetEntries = computed(() => {
|
|
20
20
|
const entries: { id: string; title: string; loaded: boolean; conceptCount: number }[] = [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Citation } from 'glossarist';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
citation: Citation;
|
|
6
|
+
}>();
|
|
7
|
+
|
|
8
|
+
function formatRef(c: Citation): string {
|
|
9
|
+
const ref = c.ref;
|
|
10
|
+
if (!ref) return '';
|
|
11
|
+
const parts: string[] = [];
|
|
12
|
+
if (ref.source) parts.push(ref.source);
|
|
13
|
+
if (ref.id) parts.push(ref.id);
|
|
14
|
+
if (ref.version) parts.push(`(${ref.version})`);
|
|
15
|
+
return parts.join(' ');
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<span class="inline">
|
|
21
|
+
<template v-if="citation.ref">
|
|
22
|
+
<span v-if="citation.ref.source" class="font-medium">{{ citation.ref.source }}</span>
|
|
23
|
+
<span v-if="citation.ref.id"> {{ citation.ref.id }}</span>
|
|
24
|
+
<span v-if="citation.ref.version" class="text-ink-400"> ({{ citation.ref.version }})</span>
|
|
25
|
+
</template>
|
|
26
|
+
<template v-if="citation.locality">
|
|
27
|
+
<span v-if="citation.locality.type" class="text-ink-400">, {{ citation.locality.type }}</span>
|
|
28
|
+
<span v-if="citation.locality.referenceFrom" class="text-ink-400">
|
|
29
|
+
{{ citation.locality.referenceTo ? ` ${citation.locality.referenceFrom}–${citation.locality.referenceTo}` : ` ${citation.locality.referenceFrom}` }}
|
|
30
|
+
</span>
|
|
31
|
+
</template>
|
|
32
|
+
<a v-if="citation.link" :href="citation.link" target="_blank" rel="noopener" class="concept-link ml-1">[link]</a>
|
|
33
|
+
<span v-if="citation.original" class="text-xs text-ink-300 ml-1">(orig: {{ citation.original }})</span>
|
|
34
|
+
</span>
|
|
35
|
+
</template>
|