@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.62",
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: { register: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
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 = parseNeighborUri(otherUri, edge.register || props.registerId);
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.register,
302
- register: parsed.register,
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: { register: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
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 = parseNeighborUri(otherUri, edge.register || props.registerId);
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.register,
351
- register: parsed.register,
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,