@glossarist/concept-browser 0.7.128 → 0.7.130
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.130",
|
|
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": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"astro": "^7.0.7",
|
|
44
44
|
"d3": "^7.9.0",
|
|
45
45
|
"favicons": "^7.2.0",
|
|
46
|
-
"glossarist": "^0.4.
|
|
46
|
+
"glossarist": "^0.4.56",
|
|
47
47
|
"js-yaml": "^4.1.0",
|
|
48
48
|
"jszip": "^3.10.1",
|
|
49
49
|
"n3": "^1.26.0",
|
package/scripts/generate-data.js
CHANGED
|
@@ -1163,6 +1163,7 @@ function extractInlineRefs(localizedData, refMaps2, conceptSources = []) {
|
|
|
1163
1163
|
}
|
|
1164
1164
|
for (const m of fullText.matchAll(/\{\{([^{}]+?)\}\}/g)) {
|
|
1165
1165
|
const body = m[1];
|
|
1166
|
+
if (/^(bib|link|image):/i.test(body)) continue;
|
|
1166
1167
|
let segments;
|
|
1167
1168
|
try {
|
|
1168
1169
|
segments = parseMentions(`{{${body}}}`);
|
|
@@ -1188,6 +1189,8 @@ function extractInlineRefs(localizedData, refMaps2, conceptSources = []) {
|
|
|
1188
1189
|
if (pattern) {
|
|
1189
1190
|
refs.push({ id: buildConceptUri(refMaps2.uriBase, pattern.datasetId, pattern.conceptId), term: seg.label ?? target.urn });
|
|
1190
1191
|
}
|
|
1192
|
+
} else if (target.type === "entity_id" && target.id) {
|
|
1193
|
+
refs.push({ id: buildConceptUri(refMaps2.uriBase, refMaps2.register, target.id), term: seg.label ?? target.id });
|
|
1191
1194
|
}
|
|
1192
1195
|
}
|
|
1193
1196
|
}
|
package/scripts/generate-data.ts
CHANGED
|
@@ -375,54 +375,46 @@ function extractInlineRefs(localizedData, refMaps, conceptSources = []) {
|
|
|
375
375
|
if (pattern) { refs.push(pattern); }
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
// Double-brace mentions: use the
|
|
379
|
-
//
|
|
380
|
-
// NEVER silently passed through. The build FAILS on any invalid syntax.
|
|
378
|
+
// Double-brace mentions: use the strict parser from glossarist@0.4.56.
|
|
379
|
+
// Bare numeric IDs and designations are implicit same-dataset concept refs.
|
|
381
380
|
for (const m of fullText.matchAll(/\{\{([^{}]+?)\}\}/g)) {
|
|
382
381
|
const body = m[1];
|
|
383
382
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
// Skip deployment-specific kinds (not concept cross-references)
|
|
384
|
+
if (/^(bib|link|image):/i.test(body)) continue;
|
|
385
|
+
|
|
387
386
|
let segments;
|
|
388
387
|
try {
|
|
389
388
|
segments = parseMentions(`{{${body}}}`);
|
|
390
389
|
} catch (e) {
|
|
391
390
|
if (e instanceof InvalidMentionError) {
|
|
392
|
-
// Report the error but don't crash — collect for batch reporting.
|
|
393
391
|
console.warn(` Invalid mention: {{${body}}} — ${e.reason}`);
|
|
394
392
|
continue;
|
|
395
393
|
}
|
|
396
394
|
throw e;
|
|
397
395
|
}
|
|
398
396
|
|
|
399
|
-
// Extract mention segments (skip text segments).
|
|
400
397
|
for (const seg of segments) {
|
|
401
398
|
if (seg.kind === 'text') continue;
|
|
402
|
-
|
|
403
|
-
// bib:/link:/image: are deployment-specific renderings — NOT concept
|
|
404
|
-
// cross-references. They must NOT appear in gl:references.
|
|
405
399
|
if (seg.kind === 'bib' || seg.kind === 'link' || seg.kind === 'image') continue;
|
|
406
400
|
|
|
407
|
-
// For concept/cite/fig/table/formula — resolve to URIs.
|
|
408
|
-
// fig/table/formula resolve to entity refs (handled separately).
|
|
409
401
|
if (seg.kind === 'concept' || seg.kind === 'cite') {
|
|
410
402
|
const target = seg.target;
|
|
411
403
|
if (target.type === 'dataset_qualified' && target.dataset && target.id) {
|
|
412
|
-
//
|
|
413
|
-
// If the dataset doesn't exist in this deployment's refPrefixMap,
|
|
414
|
-
// skip — the runtime resolver will handle it (unresolved text or
|
|
415
|
-
// external link via routing). Creating a URI for a non-existent
|
|
416
|
-
// dataset produces a 404.
|
|
404
|
+
// Cross-dataset: only if co-deployed
|
|
417
405
|
const dsId = refMaps.refPrefixMap[target.dataset];
|
|
418
406
|
if (dsId) {
|
|
419
407
|
refs.push({ id: buildConceptUri(refMaps.uriBase, dsId, target.id), term: seg.label ?? `${target.dataset}:${target.id}` });
|
|
420
408
|
}
|
|
421
409
|
} else if (target.type === 'urn' && target.urn) {
|
|
410
|
+
// URN: resolve via pattern index
|
|
422
411
|
const pattern = refMaps.patternIndex.resolve(target.urn);
|
|
423
412
|
if (pattern) {
|
|
424
413
|
refs.push({ id: buildConceptUri(refMaps.uriBase, pattern.datasetId, pattern.conceptId), term: seg.label ?? target.urn });
|
|
425
414
|
}
|
|
415
|
+
} else if (target.type === 'entity_id' && target.id) {
|
|
416
|
+
// Same-dataset concept reference (bare numeric ID or designation)
|
|
417
|
+
refs.push({ id: buildConceptUri(refMaps.uriBase, refMaps.register, target.id), term: seg.label ?? target.id });
|
|
426
418
|
}
|
|
427
419
|
}
|
|
428
420
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for citeResolver DATASET:ID matching logic.
|
|
5
|
+
*
|
|
6
|
+
* The citeResolver must parse {{cite:IEV:702-02-07}} and match it
|
|
7
|
+
* against sources that have:
|
|
8
|
+
* - id: "702-02-07" (the ID part)
|
|
9
|
+
* - origin.ref: { source: "IEV", id: "702-02-07" }
|
|
10
|
+
*
|
|
11
|
+
* Previously, it only matched s.id === key, so "IEV:702-02-07"
|
|
12
|
+
* never matched source.id "702-02-07".
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface SourceLike {
|
|
16
|
+
id: string | null;
|
|
17
|
+
origin?: {
|
|
18
|
+
ref?: { source?: string; id?: string } | null;
|
|
19
|
+
link?: string | null;
|
|
20
|
+
} | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function findSource(key: string, sources: SourceLike[]): SourceLike | undefined {
|
|
24
|
+
const lastColon = key.lastIndexOf(':');
|
|
25
|
+
const idPart = lastColon > 0 ? key.slice(lastColon + 1) : key;
|
|
26
|
+
const datasetPart = lastColon > 0 ? key.slice(0, lastColon) : '';
|
|
27
|
+
|
|
28
|
+
return sources.find(s => {
|
|
29
|
+
if (s.id === key || s.id === idPart) return true;
|
|
30
|
+
const ref = s.origin?.ref;
|
|
31
|
+
if (ref) {
|
|
32
|
+
if (datasetPart && ref.source === datasetPart && ref.id === idPart) return true;
|
|
33
|
+
if (ref.id === key || ref.id === idPart) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('citeResolver DATASET:ID matching', () => {
|
|
40
|
+
const sources: SourceLike[] = [
|
|
41
|
+
{ id: null, origin: { ref: { source: 'IEV', id: '845-01-01' }, link: 'https://electropedia.org/...' } },
|
|
42
|
+
{ id: '845-01-01', origin: { ref: { source: 'IEV', id: '845-01-01' }, link: 'https://electropedia.org/...' } },
|
|
43
|
+
{ id: '702-02-07', origin: { ref: { source: 'IEV', id: '702-02-07' }, link: 'https://electropedia.org/...' } },
|
|
44
|
+
{ id: null, origin: { ref: { source: 'CIE S 017:2011', id: '17-370' } } },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
it('matches cite:IEV:845-01-01 via origin.ref', () => {
|
|
48
|
+
const s = findSource('IEV:845-01-01', sources);
|
|
49
|
+
expect(s).toBeDefined();
|
|
50
|
+
expect(s!.origin!.ref!.id).toBe('845-01-01');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('matches cite:IEV:702-02-07 via source id', () => {
|
|
54
|
+
const s = findSource('IEV:702-02-07', sources);
|
|
55
|
+
expect(s).toBeDefined();
|
|
56
|
+
expect(s!.id).toBe('702-02-07');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('matches cite:845-01-01 (bare ID without dataset prefix)', () => {
|
|
60
|
+
const s = findSource('845-01-01', sources);
|
|
61
|
+
expect(s).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('matches cite with CIE dataset prefix', () => {
|
|
65
|
+
const s = findSource('CIE S 017:2011:17-370', sources);
|
|
66
|
+
expect(s).toBeDefined();
|
|
67
|
+
expect(s!.origin!.ref!.id).toBe('17-370');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns undefined for unknown source', () => {
|
|
71
|
+
const s = findSource('UNKNOWN:999', sources);
|
|
72
|
+
expect(s).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -65,7 +65,24 @@ export function useRenderOptions(
|
|
|
65
65
|
return `<span class="cite-ref">${escapeHtml(label ?? key)}</span>`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// Parse DATASET:ID format. {{cite:IEV:702-02-07}} → dataset="IEV", idPart="702-02-07"
|
|
69
|
+
// Match against: raw id, id part, or origin.ref { source: dataset, id: idPart }
|
|
70
|
+
const lastColon = key.lastIndexOf(':');
|
|
71
|
+
const idPart = lastColon > 0 ? key.slice(lastColon + 1) : key;
|
|
72
|
+
const datasetPart = lastColon > 0 ? key.slice(0, lastColon) : '';
|
|
73
|
+
|
|
74
|
+
const source = sources.find(s => {
|
|
75
|
+
// Match by source id (raw or id part)
|
|
76
|
+
if (s.id === key || s.id === idPart) return true;
|
|
77
|
+
// Match by origin.ref
|
|
78
|
+
const ref = s.origin?.ref;
|
|
79
|
+
if (ref) {
|
|
80
|
+
if (datasetPart && ref.source === datasetPart && ref.id === idPart) return true;
|
|
81
|
+
if (ref.id === key || ref.id === idPart) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
});
|
|
85
|
+
|
|
69
86
|
if (!source?.origin) {
|
|
70
87
|
return `<span class="cite-unresolved">${escapeHtml(label ?? key)}</span>`;
|
|
71
88
|
}
|