@glossarist/concept-browser 0.7.62 → 0.7.63
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.63",
|
|
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": {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { UriRouter } from '../adapters/UriRouter';
|
|
3
|
+
|
|
4
|
+
describe('UriRouter.parseUri — deployment-agnostic concept URI parsing', () => {
|
|
5
|
+
// The old parseNeighborUri in RelationSphere.vue hardcoded specific
|
|
6
|
+
// hostnames. UriRouter.parseUri must work for ANY deployment.
|
|
7
|
+
const cases: Array<[string, string, string]> = [
|
|
8
|
+
// [input URI, expected registerId, expected conceptId]
|
|
9
|
+
['https://www.glossarist.org/iala-vocab/iala-2022/concept/1-1-000', 'iala-2022', '1-1-000'],
|
|
10
|
+
['https://glossarist.org/iala-2022/concept/1-1-000', 'iala-2022', '1-1-000'],
|
|
11
|
+
['https://glossarist.org/iso/concept/3.1.1', 'iso', '3.1.1'],
|
|
12
|
+
['https://oimlsmart.github.io/vocab/viml-2022/concept/1.1', 'viml-2022', '1.1'],
|
|
13
|
+
['https://www.oimlsmart.org/vocab/viml-2022/concept/1.1', 'viml-2022', '1.1'],
|
|
14
|
+
['https://metanorma.github.io/iala-vocab/iala-2023/concept/2-1-245', 'iala-2023', '2-1-245'],
|
|
15
|
+
['https://isotc204.geolexica.org/isotc204-2022/concept/3.5.8.8', 'isotc204-2022', '3.5.8.8'],
|
|
16
|
+
['https://vocab.example.org/dataset-x/concept/foo', 'dataset-x', 'foo'],
|
|
17
|
+
['https://custom.host/path/to/register/concept/abc-123', 'register', 'abc-123'],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const [uri, expectedReg, expectedId] of cases) {
|
|
21
|
+
it(`parses ${uri}`, () => {
|
|
22
|
+
const parsed = UriRouter.parseUri(uri);
|
|
23
|
+
expect(parsed).toEqual({ registerId: expectedReg, conceptId: expectedId });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it('returns null for URIs without /concept/', () => {
|
|
28
|
+
expect(UriRouter.parseUri('https://example.org/foo/bar')).toBeNull();
|
|
29
|
+
expect(UriRouter.parseUri('urn:iso:std:iso:14812:2025:3.5.8.8')).toBeNull();
|
|
30
|
+
expect(UriRouter.parseUri('')).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('does NOT hardcode any specific hostname', () => {
|
|
34
|
+
// Every deployment domain should work. If the regex hardcodes
|
|
35
|
+
// a specific host, this test would fail for others.
|
|
36
|
+
const diverse = [
|
|
37
|
+
'https://a.io/r/concept/1',
|
|
38
|
+
'https://b.com/x/y/z/concept/2',
|
|
39
|
+
'https://1.2.3.4/reg/concept/3',
|
|
40
|
+
];
|
|
41
|
+
for (const uri of diverse) {
|
|
42
|
+
expect(UriRouter.parseUri(uri)).not.toBeNull();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -18,6 +18,7 @@ import { useDsStyle } from '../utils/dataset-style';
|
|
|
18
18
|
import { getFactory } from '../adapters/factory';
|
|
19
19
|
import { SPHERE_RELATION_CATEGORIES as RELATION_CATEGORIES, sphereCategoryForType as categoryForType, categorizeRelationForSphere as categorizeRelation, colorForTypeInMode as colorForTypeRaw, relationLabel as relationTypeLabel } from '../utils/relation-sphere-styling';
|
|
20
20
|
import { easeInOutCubic, slerp, fibonacciSpherePosition, project, cardEdge, type Vec3 } from '../composables/useSphereProjection';
|
|
21
|
+
import { UriRouter } from '../adapters/UriRouter';
|
|
21
22
|
import { getPreferredTerm } from '../utils/concept-helpers';
|
|
22
23
|
import { renderContent } from '../utils/content-renderer';
|
|
23
24
|
import { useI18n } from '../i18n';
|
|
@@ -226,14 +227,6 @@ function updateNodeTerm(n: SNode) {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
/* ── Build internal graph from concept + edges (BFS) ────── */
|
|
229
|
-
function parseNeighborUri(uri: string, fallbackReg: string): { register: string; conceptId: string } | null {
|
|
230
|
-
const m = uri.match(/(?:glossarist\.org|oimlsmart\.github\.io\/vocab)\/([^/]+)\/concept\/([^/?#]+)/);
|
|
231
|
-
if (m) return { register: m[1], conceptId: m[2] };
|
|
232
|
-
/* URN or other shape — use fallback register, last path segment as id */
|
|
233
|
-
const parts = uri.split('/').filter(Boolean);
|
|
234
|
-
if (parts.length === 0) return null;
|
|
235
|
-
return { register: fallbackReg, conceptId: parts[parts.length - 1] };
|
|
236
|
-
}
|
|
237
230
|
|
|
238
231
|
function hashSeed(s: string): number {
|
|
239
232
|
let h = 0;
|
|
@@ -280,12 +273,12 @@ function buildGraph() {
|
|
|
280
273
|
|
|
281
274
|
/* First pass: collect depth-1 neighbors (deduped) so we know N for even
|
|
282
275
|
ring placement. */
|
|
283
|
-
const depth1List: Array<{ uri: string; parsed: {
|
|
276
|
+
const depth1List: Array<{ uri: string; parsed: { registerId: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
|
|
284
277
|
for (const edge of props.edges) {
|
|
285
278
|
const isOutgoing = edge.source === focusUri || edge.source === focusId;
|
|
286
279
|
const otherUri = isOutgoing ? edge.target : edge.source;
|
|
287
280
|
if (!otherUri || visited.has(otherUri)) continue;
|
|
288
|
-
const parsed =
|
|
281
|
+
const parsed = UriRouter.parseUri(otherUri);
|
|
289
282
|
if (!parsed || parsed.conceptId === focusId) continue;
|
|
290
283
|
visited.set(otherUri, 1);
|
|
291
284
|
depth1List.push({ uri: otherUri, parsed, isOutgoing, edge });
|
|
@@ -298,8 +291,8 @@ function buildGraph() {
|
|
|
298
291
|
nodeMap.set(parsed.conceptId, {
|
|
299
292
|
id: parsed.conceptId,
|
|
300
293
|
term: edge.label || parsed.conceptId,
|
|
301
|
-
ref: parsed.
|
|
302
|
-
register: parsed.
|
|
294
|
+
ref: parsed.registerId,
|
|
295
|
+
register: parsed.registerId,
|
|
303
296
|
conceptId: parsed.conceptId,
|
|
304
297
|
depth: 1,
|
|
305
298
|
x: pos.x, y: pos.y, z: pos.z,
|
|
@@ -331,13 +324,13 @@ function buildGraph() {
|
|
|
331
324
|
const inEdges = store.graph.getIncomingEdges(uri);
|
|
332
325
|
/* Collect this node's children first so we can place them as an even
|
|
333
326
|
sub-ring around their parent. */
|
|
334
|
-
const children: Array<{ uri: string; parsed: {
|
|
327
|
+
const children: Array<{ uri: string; parsed: { registerId: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
|
|
335
328
|
for (const edge of [...outEdges, ...inEdges]) {
|
|
336
329
|
if (nodeMap.size + children.length >= MAX_NODES) break;
|
|
337
330
|
const isOutgoing = edge.source === uri;
|
|
338
331
|
const otherUri = isOutgoing ? edge.target : edge.source;
|
|
339
332
|
if (!otherUri || visited.has(otherUri)) continue;
|
|
340
|
-
const parsed =
|
|
333
|
+
const parsed = UriRouter.parseUri(otherUri);
|
|
341
334
|
if (!parsed || parsed.conceptId === focusId || nodeMap.has(parsed.conceptId)) continue;
|
|
342
335
|
visited.set(otherUri, depth + 1);
|
|
343
336
|
children.push({ uri: otherUri, parsed, isOutgoing, edge });
|
|
@@ -347,8 +340,8 @@ function buildGraph() {
|
|
|
347
340
|
nodeMap.set(parsed.conceptId, {
|
|
348
341
|
id: parsed.conceptId,
|
|
349
342
|
term: parsed.conceptId,
|
|
350
|
-
ref: parsed.
|
|
351
|
-
register: parsed.
|
|
343
|
+
ref: parsed.registerId,
|
|
344
|
+
register: parsed.registerId,
|
|
352
345
|
conceptId: parsed.conceptId,
|
|
353
346
|
depth: depth + 1,
|
|
354
347
|
x: pos.x, y: pos.y, z: pos.z,
|