@glossarist/concept-browser 0.7.118 → 0.7.120
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 +2 -2
- package/scripts/build-edges.js +13 -0
- package/scripts/build-edges.ts +19 -0
- package/src/__tests__/adapters/cross-dataset-related.test.ts +45 -0
- package/src/__tests__/adapters/graph-source-target.test.ts +31 -0
- package/src/adapters/GraphDataSource.ts +7 -1
- package/src/adapters/model-bridge.ts +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.120",
|
|
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": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"astro": "^7.0.7",
|
|
43
43
|
"d3": "^7.9.0",
|
|
44
44
|
"favicons": "^7.2.0",
|
|
45
|
-
"glossarist": "^0.4.
|
|
45
|
+
"glossarist": "^0.4.54",
|
|
46
46
|
"js-yaml": "^4.1.0",
|
|
47
47
|
"jszip": "^3.10.1",
|
|
48
48
|
"n3": "^1.26.0",
|
package/scripts/build-edges.js
CHANGED
|
@@ -157,6 +157,16 @@ function extractRelated(concept, registerId, uriBase, urnMap) {
|
|
|
157
157
|
const edges = [];
|
|
158
158
|
const sourceUri = concept["@id"];
|
|
159
159
|
for (const r of concept["gl:related"] || []) {
|
|
160
|
+
if (r["gl:target"]) {
|
|
161
|
+
edges.push({
|
|
162
|
+
source: sourceUri,
|
|
163
|
+
target: r["gl:target"],
|
|
164
|
+
type: r["gl:relationshipType"] || "references",
|
|
165
|
+
label: r["gl:term"] || void 0,
|
|
166
|
+
register: r["gl:target"].match(/\/([^/]+)\/concept\//)?.[1] || registerId
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
160
170
|
const ref = r["gl:ref"];
|
|
161
171
|
if (!ref) continue;
|
|
162
172
|
const source = ref["gl:source"] || ref["source"];
|
|
@@ -382,6 +392,9 @@ function main() {
|
|
|
382
392
|
const base = alias.endsWith("*") ? alias.slice(0, -1) : alias;
|
|
383
393
|
if (base) urnMap.set(base, ds);
|
|
384
394
|
}
|
|
395
|
+
if (manifest.ref) {
|
|
396
|
+
urnMap.set(manifest.ref, ds);
|
|
397
|
+
}
|
|
385
398
|
} catch {
|
|
386
399
|
}
|
|
387
400
|
}
|
package/scripts/build-edges.ts
CHANGED
|
@@ -173,6 +173,19 @@ function extractRelated(concept, registerId, uriBase, urnMap) {
|
|
|
173
173
|
const edges = [];
|
|
174
174
|
const sourceUri = concept['@id'];
|
|
175
175
|
for (const r of concept['gl:related'] || []) {
|
|
176
|
+
// Prefer gl:target — already resolved at generate-data build time
|
|
177
|
+
// via refPrefixMap (maps dataset ref labels to dataset IDs).
|
|
178
|
+
if (r['gl:target']) {
|
|
179
|
+
edges.push({
|
|
180
|
+
source: sourceUri,
|
|
181
|
+
target: r['gl:target'],
|
|
182
|
+
type: r['gl:relationshipType'] || 'references',
|
|
183
|
+
label: r['gl:term'] || undefined,
|
|
184
|
+
register: r['gl:target'].match(/\/([^/]+)\/concept\//)?.[1] || registerId,
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// Fallback: resolve from gl:ref using urnMap
|
|
176
189
|
const ref = r['gl:ref'];
|
|
177
190
|
if (!ref) continue;
|
|
178
191
|
const source = ref['gl:source'] || ref['source'];
|
|
@@ -458,6 +471,12 @@ for (const ds of datasets) {
|
|
|
458
471
|
const base = alias.endsWith('*') ? alias.slice(0, -1) : alias;
|
|
459
472
|
if (base) urnMap.set(base, ds);
|
|
460
473
|
}
|
|
474
|
+
// Map the dataset's ref label (e.g. "CIE S 017:2020") to its dataset ID.
|
|
475
|
+
// This enables cross-dataset resolution for gl:ref entries that use
|
|
476
|
+
// source labels instead of URIs.
|
|
477
|
+
if (manifest.ref) {
|
|
478
|
+
urnMap.set(manifest.ref, ds);
|
|
479
|
+
}
|
|
461
480
|
} catch {}
|
|
462
481
|
}
|
|
463
482
|
console.log(`URI resolution map: ${[...urnMap.entries()].map(([k,v]) => `${k}→${v}`).join(', ')}\n`);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { conceptFromJson } from '../../adapters/model-bridge';
|
|
3
|
+
|
|
4
|
+
function makeJsonLdWithRelated(target: string | null) {
|
|
5
|
+
const related = target
|
|
6
|
+
? {
|
|
7
|
+
'gl:relationshipType': 'superseded_by',
|
|
8
|
+
'gl:target': target,
|
|
9
|
+
'gl:ref': { 'gl:source': 'CIE S 017:2020', 'gl:id': '17-21-097' },
|
|
10
|
+
}
|
|
11
|
+
: {
|
|
12
|
+
'gl:relationshipType': 'superseded_by',
|
|
13
|
+
'gl:ref': { 'gl:source': 'CIE S 017:2020', 'gl:id': '17-21-097' },
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
'@type': 'skos:Concept',
|
|
17
|
+
'@id': 'https://example.com/cie-2011/concept/17-50',
|
|
18
|
+
'gl:identifier': '17-50',
|
|
19
|
+
'gl:localizedConcept': {
|
|
20
|
+
eng: {
|
|
21
|
+
'gl:languageCode': 'eng',
|
|
22
|
+
'gl:designation': [{ '@type': 'gl:Expression', 'gl:term': 'test term', 'gl:normativeStatus': 'preferred' }],
|
|
23
|
+
'gl:definition': [{ 'gl:content': 'test definition' }],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
'gl:related': [related],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('cross-dataset related concept resolution — gl:target flows through model-bridge natively', () => {
|
|
31
|
+
it('concept.relatedConcepts entries with gl:target have native target field', () => {
|
|
32
|
+
const json = makeJsonLdWithRelated('https://example.com/cie-2020/concept/17-21-097');
|
|
33
|
+
const concept = conceptFromJson(json as any);
|
|
34
|
+
expect(concept.relatedConcepts).toHaveLength(1);
|
|
35
|
+
expect(concept.relatedConcepts[0].type).toBe('superseded_by');
|
|
36
|
+
expect(concept.relatedConcepts[0].target).toBe('https://example.com/cie-2020/concept/17-21-097');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('concept.relatedConcepts entries without gl:target have null target', () => {
|
|
40
|
+
const json = makeJsonLdWithRelated(null);
|
|
41
|
+
const concept = conceptFromJson(json as any);
|
|
42
|
+
expect(concept.relatedConcepts).toHaveLength(1);
|
|
43
|
+
expect(concept.relatedConcepts[0].target).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { RelatedConcept } from 'glossarist';
|
|
3
|
+
import { resolveRefTarget } from '../../adapters/GraphDataSource';
|
|
4
|
+
|
|
5
|
+
describe('GraphDataSource.resolveRefTarget — prefers native target field', () => {
|
|
6
|
+
it('returns rc.target when set (cross-dataset resolved URI)', () => {
|
|
7
|
+
const rc = new RelatedConcept({
|
|
8
|
+
type: 'superseded_by',
|
|
9
|
+
target: 'https://example.com/cie-2020/concept/17-21-097',
|
|
10
|
+
ref: { source: 'CIE S 017:2020', id: '17-21-097' },
|
|
11
|
+
});
|
|
12
|
+
const target = resolveRefTarget(rc, 'https://example.com/cie-eilv', 'cie-2011', undefined);
|
|
13
|
+
expect(target).toBe('https://example.com/cie-2020/concept/17-21-097');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('falls back to ref resolution when target is null', () => {
|
|
17
|
+
const rc = new RelatedConcept({
|
|
18
|
+
type: 'supersedes',
|
|
19
|
+
ref: { source: 'cie-2020', id: '42' },
|
|
20
|
+
});
|
|
21
|
+
const urnMap = new Map([['cie-2020', 'cie-2020']]);
|
|
22
|
+
const target = resolveRefTarget(rc, 'https://example.com', 'cie-2011', urnMap);
|
|
23
|
+
expect(target).toBe('https://example.com/cie-2020/concept/42');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns empty string when neither target nor ref is set', () => {
|
|
27
|
+
const rc = new RelatedConcept({ type: 'see' });
|
|
28
|
+
const target = resolveRefTarget(rc, 'https://example.com', 'cie-2011', undefined);
|
|
29
|
+
expect(target).toBe('');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -16,7 +16,13 @@ interface DomainNodeJson {
|
|
|
16
16
|
children?: DomainNodeJson[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function resolveRefTarget(rc: RelatedConcept, uriBase: string, registerId: string, urnMap?: ReadonlyMap<string, string>): string {
|
|
19
|
+
export function resolveRefTarget(rc: RelatedConcept, uriBase: string, registerId: string, urnMap?: ReadonlyMap<string, string>): string {
|
|
20
|
+
// Prefer the native target field — already resolved at build time
|
|
21
|
+
// via refPrefixMap in generate-data.ts. This is the author-provided
|
|
22
|
+
// canonical URL for cross-dataset relations.
|
|
23
|
+
if (rc.target) return rc.target;
|
|
24
|
+
|
|
25
|
+
// Fallback: resolve from ref (for data generated before gl:target existed)
|
|
20
26
|
if (!rc.ref) return '';
|
|
21
27
|
const ref = rc.ref;
|
|
22
28
|
if (ref.id) {
|
|
@@ -170,7 +170,8 @@ function attachBridges(concept: Concept, localizations: Record<string, unknown>)
|
|
|
170
170
|
/**
|
|
171
171
|
* Attach bridged fields (ref text, sourceId, citation) to RelatedConcept instances
|
|
172
172
|
* from the raw deserialized data. Called after Concept.fromJSON creates the model
|
|
173
|
-
* instances, since RelatedConcept.fromJSON
|
|
173
|
+
* instances, since RelatedConcept.fromJSON does not read sourceId/citation/text.
|
|
174
|
+
* (It does read type/content/ref/target natively.)
|
|
174
175
|
*/
|
|
175
176
|
function attachRelatedBridges(
|
|
176
177
|
modelRelated: ReadonlyArray<{ type?: string | null; content?: Record<string, string> | string | null; ref?: any; related?: ReadonlyArray<any> }>,
|
|
@@ -367,6 +368,13 @@ function mapRelatedFromJsonLd(r: JsonLdRelated): Record<string, unknown> {
|
|
|
367
368
|
result.type = r[GL.RELATIONSHIP_TYPE];
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
// gl:target is the resolved concept URI for cross-dataset relations
|
|
372
|
+
// (e.g., superseded_by → cie-2020 concept). Without this, the UI
|
|
373
|
+
// renders related concepts as plain text with no clickable link.
|
|
374
|
+
if (r[GL.TARGET]) {
|
|
375
|
+
result.target = r[GL.TARGET];
|
|
376
|
+
}
|
|
377
|
+
|
|
370
378
|
if (r[GL.REF]) {
|
|
371
379
|
const ref = mapRefFromJsonLd(r[GL.REF]);
|
|
372
380
|
if (ref) result.ref = ref;
|