@glossarist/concept-browser 0.2.6 → 0.2.7
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/package.json +1 -1
- package/scripts/generate-data.mjs +43 -17
- package/src/__tests__/data-integration.test.ts +3 -2
- package/src/__tests__/uri-router.test.ts +12 -9
- package/src/adapters/UriRouter.ts +2 -2
- package/src/adapters/factory.ts +2 -2
- package/src/components/AppSidebar.vue +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/use-site-config.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -90,12 +90,35 @@ function sourcesToJsonLd(sources) {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function refsToJsonLd(refs) {
|
|
93
|
+
function refsToJsonLd(refs, refMaps) {
|
|
94
94
|
if (!refs || !Array.isArray(refs)) return [];
|
|
95
|
-
return refs.map(r =>
|
|
96
|
-
'@id': r.id,
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
return refs.map(r => {
|
|
96
|
+
if (r.id) return { '@id': r.id, 'gl:term': r.term };
|
|
97
|
+
if (r.term && refMaps) {
|
|
98
|
+
const uri = resolveRefUri(r.term, refMaps);
|
|
99
|
+
if (uri) return { '@id': uri, 'gl:term': r.term };
|
|
100
|
+
}
|
|
101
|
+
return { '@id': r.id || r.term, 'gl:term': r.term };
|
|
102
|
+
}).filter(r => r['@id']);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveRefUri(term, refMaps) {
|
|
106
|
+
const base = refMaps.uriBase;
|
|
107
|
+
const urnPrefix = 'urn:iso:std:iso:';
|
|
108
|
+
if (term.startsWith(urnPrefix)) {
|
|
109
|
+
const rest = term.slice(urnPrefix.length);
|
|
110
|
+
const match = rest.match(/^(\d+):(.+)$/);
|
|
111
|
+
if (match) {
|
|
112
|
+
const dsId = refMaps.urnStandardMap[match[1]];
|
|
113
|
+
if (dsId) return `${base}/${dsId}/concept/${match[2]}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const ievMatch = term.match(/^IEV:(\d+[-\d]+)$/);
|
|
117
|
+
if (ievMatch) {
|
|
118
|
+
const dsId = refMaps.refPrefixMap['IEV'];
|
|
119
|
+
if (dsId) return `${base}/${dsId}/concept/${ievMatch[1]}`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
function buildRefMaps(config) {
|
|
@@ -119,12 +142,14 @@ function buildRefMaps(config) {
|
|
|
119
142
|
if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
|
|
120
143
|
if (xref.urnStandardMap) Object.assign(urnStandardMap, xref.urnStandardMap);
|
|
121
144
|
|
|
122
|
-
|
|
145
|
+
const uriBase = config.uriBase || `https://${config.domain}`;
|
|
146
|
+
return { refPrefixMap, urnStandardMap, uriBase };
|
|
123
147
|
}
|
|
124
148
|
|
|
125
|
-
function extractInlineRefs(localizedData,
|
|
149
|
+
function extractInlineRefs(localizedData, refMaps) {
|
|
126
150
|
const refs = [];
|
|
127
151
|
const texts = [];
|
|
152
|
+
const { refPrefixMap, urnStandardMap, uriBase } = refMaps;
|
|
128
153
|
|
|
129
154
|
if (localizedData.definition) {
|
|
130
155
|
const defs = Array.isArray(localizedData.definition) ? localizedData.definition : [localizedData.definition];
|
|
@@ -140,17 +165,17 @@ function extractInlineRefs(localizedData, refPrefixMap, urnStandardMap) {
|
|
|
140
165
|
|
|
141
166
|
for (const m of fullText.matchAll(/\{\{([^,}]+),\s*IEV:([^}]+)\}\}/g)) {
|
|
142
167
|
const datasetId = refPrefixMap['IEV'];
|
|
143
|
-
if (datasetId) refs.push({ id:
|
|
168
|
+
if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: m[1].trim() });
|
|
144
169
|
}
|
|
145
170
|
|
|
146
171
|
for (const m of fullText.matchAll(/\{urn:iso:std:iso:(\d+):([^,}]+),([^,}]+)(?:,([^}]+))?\}/g)) {
|
|
147
172
|
const datasetId = urnStandardMap[m[1]];
|
|
148
|
-
if (datasetId) refs.push({ id:
|
|
173
|
+
if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
|
|
149
174
|
}
|
|
150
175
|
|
|
151
176
|
for (const m of fullText.matchAll(/\{\{urn:iso:std:iso:(\d+):([^,}]+),([^,}]+)(?:,([^}]+))?\}\}/g)) {
|
|
152
177
|
const datasetId = urnStandardMap[m[1]];
|
|
153
|
-
if (datasetId) refs.push({ id:
|
|
178
|
+
if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
const seen = new Set();
|
|
@@ -165,9 +190,10 @@ const LANG_CODES = ['eng', 'ara', 'deu', 'fra', 'spa', 'ita', 'jpn', 'kor', 'pol
|
|
|
165
190
|
|
|
166
191
|
function yamlToJsonLd(conceptYaml, register, refMaps) {
|
|
167
192
|
const termid = String(conceptYaml.termid);
|
|
193
|
+
const base = refMaps.uriBase;
|
|
168
194
|
const doc = {
|
|
169
195
|
'@context': 'https://glossarist.org/ns/context.jsonld',
|
|
170
|
-
'@id':
|
|
196
|
+
'@id': `${base}/${register}/concept/${termid}`,
|
|
171
197
|
'@type': 'gl:Concept',
|
|
172
198
|
'gl:identifier': termid,
|
|
173
199
|
};
|
|
@@ -178,7 +204,7 @@ function yamlToJsonLd(conceptYaml, register, refMaps) {
|
|
|
178
204
|
if (!lc) continue;
|
|
179
205
|
|
|
180
206
|
const lDoc = {
|
|
181
|
-
'@id':
|
|
207
|
+
'@id': `${base}/${register}/concept/${termid}/${lang}`,
|
|
182
208
|
'@type': 'gl:LocalizedConcept',
|
|
183
209
|
'gl:languageCode': lang,
|
|
184
210
|
};
|
|
@@ -204,11 +230,11 @@ function yamlToJsonLd(conceptYaml, register, refMaps) {
|
|
|
204
230
|
}));
|
|
205
231
|
}
|
|
206
232
|
if (lc.references && lc.references.length > 0) {
|
|
207
|
-
lDoc['gl:references'] = refsToJsonLd(lc.references);
|
|
233
|
+
lDoc['gl:references'] = refsToJsonLd(lc.references, refMaps);
|
|
208
234
|
} else if (refMaps) {
|
|
209
|
-
const inlineRefs = extractInlineRefs(lc, refMaps
|
|
235
|
+
const inlineRefs = extractInlineRefs(lc, refMaps);
|
|
210
236
|
if (inlineRefs.length > 0) {
|
|
211
|
-
lDoc['gl:references'] = refsToJsonLd(inlineRefs);
|
|
237
|
+
lDoc['gl:references'] = refsToJsonLd(inlineRefs, refMaps);
|
|
212
238
|
}
|
|
213
239
|
}
|
|
214
240
|
|
|
@@ -515,7 +541,7 @@ function processDataset(dir, register, opts) {
|
|
|
515
541
|
fs.writeFileSync(
|
|
516
542
|
path.join(DATA, register, 'graph-nodes.json'),
|
|
517
543
|
JSON.stringify({
|
|
518
|
-
uriPrefix:
|
|
544
|
+
uriPrefix: `${refMaps.uriBase}/${register}/concept/`,
|
|
519
545
|
registerId: register,
|
|
520
546
|
nodes: graphNodeEntries,
|
|
521
547
|
}),
|
|
@@ -583,7 +609,7 @@ function processDataset(dir, register, opts) {
|
|
|
583
609
|
conceptUrlTemplate: '{baseUrl}/concepts/{conceptId}.json',
|
|
584
610
|
indexUrl: '{baseUrl}/index.json',
|
|
585
611
|
contextUrl: 'https://glossarist.org/ns/context.jsonld',
|
|
586
|
-
uriBase:
|
|
612
|
+
uriBase: refMaps.uriBase,
|
|
587
613
|
status: 'valid',
|
|
588
614
|
schemaVersion: '1.0.0',
|
|
589
615
|
tags: opts.tags,
|
|
@@ -56,6 +56,7 @@ describe('AdapterFactory', () => {
|
|
|
56
56
|
mockFetch.mockReturnValueOnce(mockJsonResponse({
|
|
57
57
|
id: 'test',
|
|
58
58
|
datasetUri: 'https://glossarist.org/test/*',
|
|
59
|
+
uriBase: 'https://glossarist.org',
|
|
59
60
|
title: 'Test',
|
|
60
61
|
languages: ['eng'],
|
|
61
62
|
chunkSize: 500,
|
|
@@ -106,7 +107,7 @@ describe('AdapterFactory', () => {
|
|
|
106
107
|
|
|
107
108
|
// Load IEV
|
|
108
109
|
mockFetch.mockReturnValueOnce(mockJsonResponse({
|
|
109
|
-
id: 'iev', datasetUri: 'urn:iec:std:iec:60050:*', title: 'IEV', languages: ['eng'], chunkSize: 500,
|
|
110
|
+
id: 'iev', datasetUri: 'urn:iec:std:iec:60050:*', uriBase: 'https://glossarist.org', title: 'IEV', languages: ['eng'], chunkSize: 500,
|
|
110
111
|
}));
|
|
111
112
|
mockFetch.mockReturnValueOnce(mockJsonResponse({
|
|
112
113
|
registerId: 'iev', conceptCount: 0, chunkSize: 500, chunks: [], concepts: [],
|
|
@@ -115,7 +116,7 @@ describe('AdapterFactory', () => {
|
|
|
115
116
|
|
|
116
117
|
// Load TC 204
|
|
117
118
|
mockFetch.mockReturnValueOnce(mockJsonResponse({
|
|
118
|
-
id: 'isotc204', datasetUri: 'urn:iso:std:iso:14812:*', title: 'TC 204', languages: ['eng'], chunkSize: 500,
|
|
119
|
+
id: 'isotc204', datasetUri: 'urn:iso:std:iso:14812:*', uriBase: 'https://glossarist.org', title: 'TC 204', languages: ['eng'], chunkSize: 500,
|
|
119
120
|
}));
|
|
120
121
|
mockFetch.mockReturnValueOnce(mockJsonResponse({
|
|
121
122
|
registerId: 'isotc204', conceptCount: 0, chunkSize: 500, chunks: [], concepts: [],
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { UriRouter } from '../adapters/UriRouter';
|
|
3
3
|
|
|
4
|
+
const MOCK_MANIFEST = { uriBase: 'https://glossarist.org' } as any;
|
|
5
|
+
|
|
4
6
|
describe('UriRouter', () => {
|
|
5
7
|
it('resolves URIs for registered datasets', () => {
|
|
6
8
|
const router = new UriRouter();
|
|
7
|
-
router.registerDataset('iev', '/data/iev');
|
|
9
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
8
10
|
|
|
9
11
|
const resolved = router.resolveUri('https://glossarist.org/iev/concept/103-01-02');
|
|
10
12
|
expect(resolved).toEqual({ registerId: 'iev', conceptId: '103-01-02' });
|
|
@@ -12,7 +14,7 @@ describe('UriRouter', () => {
|
|
|
12
14
|
|
|
13
15
|
it('resolves URIs with multi-part concept IDs', () => {
|
|
14
16
|
const router = new UriRouter();
|
|
15
|
-
router.registerDataset('isotc204', '/data/isotc204');
|
|
17
|
+
router.registerDataset('isotc204', '/data/isotc204', MOCK_MANIFEST);
|
|
16
18
|
|
|
17
19
|
const resolved = router.resolveUri('https://glossarist.org/isotc204/concept/3.1.1.1');
|
|
18
20
|
expect(resolved).toEqual({ registerId: 'isotc204', conceptId: '3.1.1.1' });
|
|
@@ -20,36 +22,37 @@ describe('UriRouter', () => {
|
|
|
20
22
|
|
|
21
23
|
it('returns null for unknown register', () => {
|
|
22
24
|
const router = new UriRouter();
|
|
23
|
-
router.registerDataset('iev', '/data/iev');
|
|
25
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
24
26
|
|
|
25
27
|
expect(router.resolveUri('https://glossarist.org/unknown/concept/123')).toBeNull();
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
it('returns null for non-matching URI pattern', () => {
|
|
29
31
|
const router = new UriRouter();
|
|
30
|
-
router.registerDataset('iev', '/data/iev');
|
|
32
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
31
33
|
|
|
32
34
|
expect(router.resolveUri('https://example.com/other')).toBeNull();
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
it('builds URIs from register and concept ID', () => {
|
|
36
38
|
const router = new UriRouter();
|
|
39
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
37
40
|
expect(router.buildUri('iev', '103-01-02')).toBe('https://glossarist.org/iev/concept/103-01-02');
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
it('lists all registered IDs', () => {
|
|
41
44
|
const router = new UriRouter();
|
|
42
|
-
router.registerDataset('iev', '/data/iev');
|
|
43
|
-
router.registerDataset('isotc211', '/data/isotc211');
|
|
45
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
46
|
+
router.registerDataset('isotc211', '/data/isotc211', MOCK_MANIFEST);
|
|
44
47
|
|
|
45
48
|
expect(router.getRegisteredIds()).toEqual(['iev', 'isotc211']);
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
it('resolves across multiple registers', () => {
|
|
49
52
|
const router = new UriRouter();
|
|
50
|
-
router.registerDataset('iev', '/data/iev');
|
|
51
|
-
router.registerDataset('isotc211', '/data/isotc211');
|
|
52
|
-
router.registerDataset('isotc204', '/data/isotc204');
|
|
53
|
+
router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
|
|
54
|
+
router.registerDataset('isotc211', '/data/isotc211', MOCK_MANIFEST);
|
|
55
|
+
router.registerDataset('isotc204', '/data/isotc204', MOCK_MANIFEST);
|
|
53
56
|
|
|
54
57
|
expect(router.resolveUri('https://glossarist.org/iev/concept/102-01-01')?.registerId).toBe('iev');
|
|
55
58
|
expect(router.resolveUri('https://glossarist.org/isotc211/concept/10')?.registerId).toBe('isotc211');
|
|
@@ -9,7 +9,7 @@ export class UriRouter {
|
|
|
9
9
|
this.registerMap.set(registerId, {
|
|
10
10
|
baseUrl,
|
|
11
11
|
manifest: manifest ?? null,
|
|
12
|
-
uriBase: manifest?.uriBase ?? '
|
|
12
|
+
uriBase: manifest?.uriBase ?? '',
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -31,7 +31,7 @@ export class UriRouter {
|
|
|
31
31
|
|
|
32
32
|
buildUri(registerId: string, conceptId: string): string {
|
|
33
33
|
const info = this.registerMap.get(registerId);
|
|
34
|
-
const uriBase = info?.uriBase ?? '
|
|
34
|
+
const uriBase = info?.uriBase ?? '';
|
|
35
35
|
return `${uriBase}/${registerId}/concept/${conceptId}`;
|
|
36
36
|
}
|
|
37
37
|
|
package/src/adapters/factory.ts
CHANGED
|
@@ -50,8 +50,8 @@ export class AdapterFactory {
|
|
|
50
50
|
const uriPatterns = [
|
|
51
51
|
manifest.datasetUri,
|
|
52
52
|
...(manifest.uriAliases ?? []),
|
|
53
|
-
|
|
54
|
-
];
|
|
53
|
+
manifest.uriBase ? `${manifest.uriBase}/${registerId}/*` : undefined,
|
|
54
|
+
].filter(Boolean) as string[];
|
|
55
55
|
this.resolver.registerDataset(registerId, uriPatterns);
|
|
56
56
|
|
|
57
57
|
return adapter;
|
|
@@ -130,7 +130,7 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
|
|
|
130
130
|
<div class="text-[11px] text-ink-300">
|
|
131
131
|
Built with the
|
|
132
132
|
<a
|
|
133
|
-
:href="(siteConfig?.features?.poweredBy as any)?.url || 'https://glossarist
|
|
133
|
+
:href="(siteConfig?.features?.poweredBy as any)?.url || 'https://github.com/glossarist/concept-browser'"
|
|
134
134
|
target="_blank"
|
|
135
135
|
rel="noopener"
|
|
136
136
|
class="concept-link"
|
package/src/config/types.ts
CHANGED