@glossarist/concept-browser 0.7.89 → 0.7.91
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/src/adapters/model-bridge/bridges.ts +159 -0
- package/src/adapters/model-bridge/concept.ts +87 -0
- package/src/adapters/model-bridge/entity-refs.ts +49 -0
- package/src/adapters/model-bridge/index.ts +46 -0
- package/src/adapters/model-bridge/mappers.ts +277 -0
- package/src/adapters/model-bridge/partitive.ts +79 -0
- package/src/components/RelationSphere.vue +35 -313
- package/src/components/RelationSphere.vue.bak +1921 -0
- package/src/components/relation-sphere/forces.ts +156 -0
- package/src/components/relation-sphere/rake-bundles.ts +221 -0
- package/src/components/relation-sphere/svg-markers.ts +52 -0
- package/src/components/relation-sphere/types.ts +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.91",
|
|
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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeakMap bridges for fields not yet in glossarist-js's published d.ts.
|
|
3
|
+
*
|
|
4
|
+
* Each bridge attaches data the model doesn't expose via typed
|
|
5
|
+
* properties. Getters retrieve it; attachBridges() populates it after
|
|
6
|
+
* Concept.fromJSON creates the model instances.
|
|
7
|
+
*
|
|
8
|
+
* Remove each bridge when glossarist-js publishes native support
|
|
9
|
+
* (tracked by PR glossarist/glossarist-js#31 and BRIDGES.md).
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
Concept,
|
|
13
|
+
LocalizedConcept,
|
|
14
|
+
ConceptRef,
|
|
15
|
+
Citation,
|
|
16
|
+
DetailedDefinition,
|
|
17
|
+
} from 'glossarist';
|
|
18
|
+
|
|
19
|
+
// Annotations: LocalizedConcept.annotations
|
|
20
|
+
const extraAnnotations = new WeakMap<LocalizedConcept, DetailedDefinition[]>();
|
|
21
|
+
|
|
22
|
+
export function getAnnotations(lc: LocalizedConcept): DetailedDefinition[] {
|
|
23
|
+
return extraAnnotations.get(lc) ?? [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Designation relationship targets: RelatedConcept.target (string)
|
|
27
|
+
const designationTargets = new WeakMap<object, string>();
|
|
28
|
+
|
|
29
|
+
export function getDesignationTarget(rc: { type?: string | null; content?: string | null; target?: string | null; ref?: any }): string | null {
|
|
30
|
+
return designationTargets.get(rc) ?? rc.target ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ConceptRef text: human-readable label alongside source/id
|
|
34
|
+
const refTexts = new WeakMap<ConceptRef, string>();
|
|
35
|
+
|
|
36
|
+
export function getRefText(ref: ConceptRef): string | null {
|
|
37
|
+
return refTexts.get(ref) ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// RelatedConcept.sourceId: links a citation reference back to its source entry
|
|
41
|
+
const relatedSourceIds = new WeakMap<object, string>();
|
|
42
|
+
|
|
43
|
+
export function getRelatedSourceId(rc: object): string | null {
|
|
44
|
+
return relatedSourceIds.get(rc) ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// RelatedConcept.citation: embedded citation data for cite-ref references
|
|
48
|
+
const relatedCitations = new WeakMap<object, Record<string, unknown>>();
|
|
49
|
+
|
|
50
|
+
export function getRelatedCitation(rc: object): Record<string, unknown> | null {
|
|
51
|
+
return relatedCitations.get(rc) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Relationship types whose target is a designation string, not a concept ref.
|
|
55
|
+
export const DESIGNATION_REL_TYPES = new Set(['abbreviated_form_for', 'short_form_for']);
|
|
56
|
+
|
|
57
|
+
export function attachSourcedFromToSources(
|
|
58
|
+
sources: readonly { sourced_from?: unknown }[],
|
|
59
|
+
rawSources: unknown,
|
|
60
|
+
): void {
|
|
61
|
+
if (!Array.isArray(rawSources)) return;
|
|
62
|
+
for (let i = 0; i < sources.length && i < rawSources.length; i++) {
|
|
63
|
+
const raw = rawSources[i] as Record<string, unknown> | undefined;
|
|
64
|
+
if (raw?.sourced_from && Array.isArray(raw.sourced_from)) {
|
|
65
|
+
(sources[i] as { sourced_from: unknown }).sourced_from = raw.sourced_from.map(
|
|
66
|
+
(sf: unknown) => sf instanceof Citation ? sf : Citation.fromJSON(sf as Record<string, unknown>)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Attach bridged fields (ref text, sourceId, citation) to RelatedConcept instances
|
|
74
|
+
* from the raw deserialized data. Called after Concept.fromJSON creates the model
|
|
75
|
+
* instances, since RelatedConcept.fromJSON only reads type/content/ref.
|
|
76
|
+
*/
|
|
77
|
+
function attachRelatedBridges(
|
|
78
|
+
modelRelated: Array<{ type?: string | null; content?: Record<string, string> | string | null; ref?: any; related?: any[] }>,
|
|
79
|
+
rawRelated: unknown[],
|
|
80
|
+
): void {
|
|
81
|
+
for (const rawRel of rawRelated) {
|
|
82
|
+
if (!rawRel || typeof rawRel !== 'object') continue;
|
|
83
|
+
const rel = rawRel as Record<string, unknown>;
|
|
84
|
+
const relType = rel.type as string | undefined;
|
|
85
|
+
const rc = relType ? modelRelated.find(r => r.type === relType) : undefined;
|
|
86
|
+
if (!rc) continue;
|
|
87
|
+
|
|
88
|
+
// Ref text
|
|
89
|
+
if (rc.ref) {
|
|
90
|
+
const rawRef = rel.ref as Record<string, unknown> | undefined;
|
|
91
|
+
if (rawRef?.text && typeof rawRef.text === 'string') {
|
|
92
|
+
refTexts.set(rc.ref, rawRef.text);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Designation target
|
|
97
|
+
if (rel.target && typeof rel.target === 'string') {
|
|
98
|
+
designationTargets.set(rc, rel.target);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Source ID — links citation reference back to the ConceptSource entry
|
|
102
|
+
if (rel.sourceId && typeof rel.sourceId === 'string') {
|
|
103
|
+
relatedSourceIds.set(rc, rel.sourceId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Citation — embedded origin data for cite-ref references
|
|
107
|
+
if (rel.citation && typeof rel.citation === 'object') {
|
|
108
|
+
relatedCitations.set(rc, rel.citation as Record<string, unknown>);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function attachBridges(concept: Concept, localizations: Record<string, unknown>): void {
|
|
114
|
+
for (const lang of concept.languages) {
|
|
115
|
+
const lc = concept.localization(lang);
|
|
116
|
+
const raw = localizations[lang];
|
|
117
|
+
if (!lc || !raw || typeof raw !== 'object') continue;
|
|
118
|
+
const rawObj = raw as Record<string, unknown>;
|
|
119
|
+
|
|
120
|
+
// Attach sourced_from to localization-level sources
|
|
121
|
+
attachSourcedFromToSources(lc.sources as unknown as { sourced_from?: unknown }[], rawObj.sources);
|
|
122
|
+
|
|
123
|
+
// Annotations
|
|
124
|
+
const annList = rawObj.annotations;
|
|
125
|
+
if (Array.isArray(annList) && annList.length > 0) {
|
|
126
|
+
extraAnnotations.set(lc, annList.map((a: Record<string, unknown>) =>
|
|
127
|
+
DetailedDefinition.fromJSON({ content: (a.content as string) ?? '' }) as DetailedDefinition,
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Designation-level relationship targets, ref text, sourceId, citation
|
|
132
|
+
const rawTerms = rawObj.terms;
|
|
133
|
+
if (Array.isArray(rawTerms)) {
|
|
134
|
+
for (let ti = 0; ti < rawTerms.length; ti++) {
|
|
135
|
+
const rawTerm = rawTerms[ti];
|
|
136
|
+
if (!rawTerm || typeof rawTerm !== 'object') continue;
|
|
137
|
+
const rawT = rawTerm as Record<string, unknown>;
|
|
138
|
+
const rawDesignation = rawT.designation as string | undefined;
|
|
139
|
+
if (!rawDesignation) continue;
|
|
140
|
+
const designation = lc.terms.find(d => d.designation === rawDesignation);
|
|
141
|
+
if (!designation) continue;
|
|
142
|
+
const rawRelated = rawT.related;
|
|
143
|
+
if (!Array.isArray(rawRelated)) continue;
|
|
144
|
+
attachRelatedBridges(designation.related, rawRelated);
|
|
145
|
+
// Attach sourced_from to designation-level sources
|
|
146
|
+
attachSourcedFromToSources(
|
|
147
|
+
designation.sources as unknown as { sourced_from?: unknown }[],
|
|
148
|
+
rawT.sources,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Localization-level related concepts
|
|
154
|
+
const rawRelated = rawObj.related;
|
|
155
|
+
if (Array.isArray(rawRelated)) {
|
|
156
|
+
attachRelatedBridges(lc.related, rawRelated);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level Concept loading: JSON-LD detection, Concept.fromJSON
|
|
3
|
+
* orchestration, summary projection, URI construction.
|
|
4
|
+
*
|
|
5
|
+
* This module is the orchestration layer — mappers, bridges, and
|
|
6
|
+
* partitive mapping live in their respective sibling modules.
|
|
7
|
+
*/
|
|
8
|
+
import { Concept } from 'glossarist';
|
|
9
|
+
import type { ConceptSummary } from '../types';
|
|
10
|
+
import { ConceptIdentity } from '../concept-identity';
|
|
11
|
+
import { GL } from '../wire-keys';
|
|
12
|
+
import { isJsonLd, mapLocalizedFromJsonLd, mapRelatedFromJsonLd } from './mappers';
|
|
13
|
+
import { mapPartitiveRelationFromJsonLd } from './partitive';
|
|
14
|
+
import { normalizeEntityRefs } from './entity-refs';
|
|
15
|
+
import { attachBridges, attachSourcedFromToSources } from './bridges';
|
|
16
|
+
import type { JsonLdConcept } from './jsonld-types';
|
|
17
|
+
|
|
18
|
+
function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
19
|
+
const id = String(doc[GL.IDENTIFIER] ?? doc['@id']?.split('/').pop() ?? '');
|
|
20
|
+
const localizations: Record<string, unknown> = {};
|
|
21
|
+
|
|
22
|
+
const rawLc = doc[GL.LOCALIZED_CONCEPT] ?? {};
|
|
23
|
+
for (const [lang, lc] of Object.entries(rawLc)) {
|
|
24
|
+
if (lc && typeof lc === 'object') {
|
|
25
|
+
localizations[lang] = mapLocalizedFromJsonLd(lc);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const related = (doc[GL.RELATED] ?? []).map(mapRelatedFromJsonLd);
|
|
30
|
+
const partitiveRelations = (doc[GL.PARTITIVE_RELATIONS] ?? [])
|
|
31
|
+
.map(mapPartitiveRelationFromJsonLd)
|
|
32
|
+
.filter((r): r is Record<string, unknown> => r !== null);
|
|
33
|
+
const tagsArr = doc[GL.TAGS];
|
|
34
|
+
const tags = Array.isArray(tagsArr) ? [...tagsArr] : [];
|
|
35
|
+
|
|
36
|
+
const concept = Concept.fromJSON({
|
|
37
|
+
id,
|
|
38
|
+
term: doc[GL.TERM] ?? null,
|
|
39
|
+
uri: doc['@id'] ?? null,
|
|
40
|
+
localizations,
|
|
41
|
+
related,
|
|
42
|
+
partitive_relations: partitiveRelations,
|
|
43
|
+
tags,
|
|
44
|
+
figures: normalizeEntityRefs(doc[GL.FIGURE_REF]),
|
|
45
|
+
tables: normalizeEntityRefs(doc[GL.TABLE_REF]),
|
|
46
|
+
formulas: normalizeEntityRefs(doc[GL.FORMULA_REF]),
|
|
47
|
+
status: null,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
attachBridges(concept, localizations);
|
|
51
|
+
return concept;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function conceptFromJson(doc: Record<string, unknown>): Concept {
|
|
55
|
+
if (isJsonLd(doc)) {
|
|
56
|
+
return conceptFromJsonLd(doc as JsonLdConcept);
|
|
57
|
+
}
|
|
58
|
+
const concept = Concept.fromJSON(doc);
|
|
59
|
+
const locs = (doc as Record<string, unknown>).localizations as Record<string, unknown> | undefined;
|
|
60
|
+
if (locs) attachBridges(concept, locs);
|
|
61
|
+
attachSourcedFromToSources(
|
|
62
|
+
concept.sources as unknown as { sourced_from?: unknown }[],
|
|
63
|
+
(doc as Record<string, unknown>).sources,
|
|
64
|
+
);
|
|
65
|
+
return concept;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function conceptToSummary(concept: Concept): ConceptSummary {
|
|
69
|
+
const designations: Record<string, string> = {};
|
|
70
|
+
for (const lang of concept.languages) {
|
|
71
|
+
const lc = concept.localization(lang);
|
|
72
|
+
if (lc?.primaryDesignation) {
|
|
73
|
+
designations[lang] = lc.primaryDesignation;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
id: concept.id,
|
|
78
|
+
designations,
|
|
79
|
+
eng: designations.eng || Object.values(designations)[0] || '',
|
|
80
|
+
status: concept.status ?? 'valid',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function conceptUri(concept: Concept, registerId: string, uriBase: string): string {
|
|
85
|
+
if (concept.uri) return concept.uri;
|
|
86
|
+
return new ConceptIdentity(concept.id, registerId, uriBase).uri;
|
|
87
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-verbal entity reference normalization.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes JSON-LD structural entity refs (`gl:figureRef` /
|
|
5
|
+
* `gl:tableRef` / `gl:formulaRef`) into the shape
|
|
6
|
+
* `NonVerbalReference.fromJSON` expects.
|
|
7
|
+
*
|
|
8
|
+
* Accepts three wire forms:
|
|
9
|
+
* - bare string ID
|
|
10
|
+
* - `{ "@id": "../kind/foo" }`
|
|
11
|
+
* - `{ "@id": "../kind/foo", "gl:display": "Figure 3" }`
|
|
12
|
+
*
|
|
13
|
+
* Emits the canonical `{ ref, display? }` shape. The path's last
|
|
14
|
+
* segment is the entity id; the field name (`figureRef` vs `tableRef`
|
|
15
|
+
* vs `formulaRef`) is the kind discriminator upstream.
|
|
16
|
+
*/
|
|
17
|
+
import { GL } from '../wire-keys';
|
|
18
|
+
|
|
19
|
+
export function normalizeEntityRefs(raw: unknown): unknown[] {
|
|
20
|
+
if (!Array.isArray(raw)) return [];
|
|
21
|
+
return raw.map(normalizeOneEntityRef).filter((v): v is Record<string, string> => v !== null);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeOneEntityRef(entry: unknown): Record<string, string> | null {
|
|
25
|
+
if (typeof entry === 'string') {
|
|
26
|
+
const trimmed = entry.trim();
|
|
27
|
+
return trimmed ? { ref: trimmed } : null;
|
|
28
|
+
}
|
|
29
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
30
|
+
const obj = entry as Record<string, unknown>;
|
|
31
|
+
const atId = typeof obj['@id'] === 'string' ? (obj['@id'] as string) : null;
|
|
32
|
+
const explicitRef = typeof obj.ref === 'string' ? obj.ref
|
|
33
|
+
: typeof obj.entityId === 'string' ? obj.entityId
|
|
34
|
+
: typeof obj.entity_id === 'string' ? obj.entity_id
|
|
35
|
+
: null;
|
|
36
|
+
const entityId = (atId ? lastPathSegment(atId) : null) ?? explicitRef;
|
|
37
|
+
if (!entityId) return null;
|
|
38
|
+
const out: Record<string, string> = { ref: entityId };
|
|
39
|
+
const displayRaw = obj[GL.DISPLAY] ?? obj['gloss:display'] ?? obj.display;
|
|
40
|
+
if (typeof displayRaw === 'string') out.display = displayRaw;
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function lastPathSegment(p: string): string | null {
|
|
45
|
+
const cleaned = p.replace(/[?#].*$/, '').replace(/\/+$/, '');
|
|
46
|
+
const segments = cleaned.split('/');
|
|
47
|
+
const last = segments[segments.length - 1];
|
|
48
|
+
return last ? decodeURIComponent(last) : null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model bridge: converts between wire-format JSON and glossarist-js
|
|
3
|
+
* model instances.
|
|
4
|
+
*
|
|
5
|
+
* Supports two input formats:
|
|
6
|
+
* 1. JSON-LD (gl:-prefixed) — format produced by generate-data.mjs
|
|
7
|
+
* 2. Glossarist native — snake_case format from glossarist-js Concept.toJSON()
|
|
8
|
+
*
|
|
9
|
+
* Public API is re-exported here. Internal organization:
|
|
10
|
+
* - jsonld-types.ts — wire-format interface declarations
|
|
11
|
+
* - bridges.ts — WeakMap bridges for fields not yet in glossarist-js
|
|
12
|
+
* - mappers.ts — JSON-LD → snake_case mappers
|
|
13
|
+
* - partitive.ts — PartitiveRelation mapping + MECE migration
|
|
14
|
+
* - entity-refs.ts — non-verbal entity reference normalization
|
|
15
|
+
* - concept.ts — top-level Concept orchestration + URI construction
|
|
16
|
+
*/
|
|
17
|
+
export {
|
|
18
|
+
conceptFromJson,
|
|
19
|
+
conceptToSummary,
|
|
20
|
+
conceptUri,
|
|
21
|
+
} from './concept';
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
getAnnotations,
|
|
25
|
+
getDesignationTarget,
|
|
26
|
+
getRefText,
|
|
27
|
+
getRelatedSourceId,
|
|
28
|
+
getRelatedCitation,
|
|
29
|
+
} from './bridges';
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
JsonLdConcept,
|
|
33
|
+
JsonLdLocalizedConcept,
|
|
34
|
+
JsonLdPartitiveMember,
|
|
35
|
+
JsonLdPartitiveRelation,
|
|
36
|
+
JsonLdDesignation,
|
|
37
|
+
JsonLdSource,
|
|
38
|
+
JsonLdOrigin,
|
|
39
|
+
JsonLdRelated,
|
|
40
|
+
JsonLdRef,
|
|
41
|
+
JsonLdLocality,
|
|
42
|
+
JsonLdDate,
|
|
43
|
+
JsonLdContent,
|
|
44
|
+
JsonLdPronunciation,
|
|
45
|
+
JsonLdGrammarInfo,
|
|
46
|
+
} from './jsonld-types';
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-LD → Glossarist native mapping functions.
|
|
3
|
+
*
|
|
4
|
+
* Each mapper reads the wire-format JSON-LD (gl:-prefixed keys, see
|
|
5
|
+
* src/adapters/wire-keys.ts for the SSOT) and emits the snake_case
|
|
6
|
+
* dict that glossarist-js's Model.fromJSON constructors expect.
|
|
7
|
+
*
|
|
8
|
+
* Mappers are pure functions — no side effects, no global state.
|
|
9
|
+
* Bridge machinery (WeakMaps) lives in bridges.ts.
|
|
10
|
+
*/
|
|
11
|
+
import { GL } from '../wire-keys';
|
|
12
|
+
import { DESIGNATION_REL_TYPES } from './bridges';
|
|
13
|
+
import type {
|
|
14
|
+
JsonLdDesignation,
|
|
15
|
+
JsonLdRef,
|
|
16
|
+
JsonLdLocality,
|
|
17
|
+
JsonLdOrigin,
|
|
18
|
+
JsonLdSource,
|
|
19
|
+
JsonLdRelated,
|
|
20
|
+
JsonLdLocalizedConcept,
|
|
21
|
+
JsonLdConcept,
|
|
22
|
+
} from './jsonld-types';
|
|
23
|
+
|
|
24
|
+
export function isJsonLd(doc: Record<string, unknown>): boolean {
|
|
25
|
+
return '@type' in doc && GL.LOCALIZED_CONCEPT in doc;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown> {
|
|
29
|
+
const result: Record<string, unknown> = {};
|
|
30
|
+
const rawType = d['@type'] || '';
|
|
31
|
+
|
|
32
|
+
if (rawType.includes('Abbreviation')) result.type = 'abbreviation';
|
|
33
|
+
else if (rawType.includes('LetterSymbol')) result.type = 'letter_symbol';
|
|
34
|
+
else if (rawType.includes('GraphicalSymbol')) result.type = 'graphical_symbol';
|
|
35
|
+
else if (rawType.includes('Symbol')) result.type = 'symbol';
|
|
36
|
+
else result.type = 'expression';
|
|
37
|
+
|
|
38
|
+
result.designation = d[GL.TERM] ?? '';
|
|
39
|
+
result.normative_status = d[GL.NORMATIVE_STATUS] ?? null;
|
|
40
|
+
|
|
41
|
+
if (d[GL.ABSENT] != null) result.absent = d[GL.ABSENT];
|
|
42
|
+
if (d[GL.FIELD_OF_APPLICATION]) result.field_of_application = d[GL.FIELD_OF_APPLICATION];
|
|
43
|
+
if (d[GL.USAGE_INFO]) result.usage_info = d[GL.USAGE_INFO];
|
|
44
|
+
if (d[GL.GEOGRAPHICAL_AREA]) result.geographical_area = d[GL.GEOGRAPHICAL_AREA];
|
|
45
|
+
if (d[GL.LANGUAGE]) result.language = d[GL.LANGUAGE];
|
|
46
|
+
if (d[GL.SCRIPT]) result.script = d[GL.SCRIPT];
|
|
47
|
+
if (d[GL.SYSTEM]) result.system = d[GL.SYSTEM];
|
|
48
|
+
if (d[GL.INTERNATIONAL] != null) result.international = d[GL.INTERNATIONAL];
|
|
49
|
+
if (d[GL.TERM_TYPE]) result.term_type = d[GL.TERM_TYPE];
|
|
50
|
+
|
|
51
|
+
const pronunciation = d[GL.PRONUNCIATION];
|
|
52
|
+
if (pronunciation?.length) {
|
|
53
|
+
result.pronunciation = pronunciation.map(p => ({
|
|
54
|
+
content: p[GL.CONTENT] ?? null,
|
|
55
|
+
language: p[GL.LANGUAGE] ?? null,
|
|
56
|
+
script: p[GL.SCRIPT] ?? null,
|
|
57
|
+
system: p[GL.SYSTEM] ?? null,
|
|
58
|
+
country: p[GL.COUNTRY] ?? null,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const sources = d[GL.SOURCE];
|
|
63
|
+
if (sources?.length) {
|
|
64
|
+
result.sources = sources.map(mapSourceFromJsonLd);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const related = d[GL.RELATED];
|
|
68
|
+
if (related?.length) {
|
|
69
|
+
result.related = related.map(r => {
|
|
70
|
+
const relType = r[GL.RELATIONSHIP_TYPE] ?? 'references';
|
|
71
|
+
if (DESIGNATION_REL_TYPES.has(relType) && r[GL.TARGET]) {
|
|
72
|
+
return { type: relType, target: r[GL.TARGET] };
|
|
73
|
+
}
|
|
74
|
+
return mapRelatedFromJsonLd(r);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (d[GL.PREFIX] != null) result.prefix = d[GL.PREFIX];
|
|
79
|
+
if (d[GL.GENDER]) {
|
|
80
|
+
result.grammar_info = [{ gender: d[GL.GENDER] }];
|
|
81
|
+
}
|
|
82
|
+
const grammarInfo = d[GL.GRAMMAR_INFO];
|
|
83
|
+
if (grammarInfo?.length) {
|
|
84
|
+
result.grammar_info = grammarInfo.map(gi => ({
|
|
85
|
+
gender: gi[GL.GENDER] ?? null,
|
|
86
|
+
number: gi[GL.NUMBER] ?? null,
|
|
87
|
+
part_of_speech: gi[GL.PART_OF_SPEECH] ?? null,
|
|
88
|
+
noun: gi[GL.NOUN] ?? false,
|
|
89
|
+
verb: gi[GL.VERB] ?? false,
|
|
90
|
+
adj: gi[GL.ADJ] ?? false,
|
|
91
|
+
adverb: gi[GL.ADVERB] ?? false,
|
|
92
|
+
preposition: gi[GL.PREPOSITION] ?? false,
|
|
93
|
+
participle: gi[GL.PARTICIPLE] ?? false,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function mapRefFromJsonLd(rawRef: JsonLdRef | string | undefined): Record<string, unknown> | null {
|
|
101
|
+
if (!rawRef) return null;
|
|
102
|
+
if (typeof rawRef === 'string') return { source: rawRef };
|
|
103
|
+
const refObj: Record<string, unknown> = {};
|
|
104
|
+
// gl:-prefixed keys take precedence over unprefixed keys
|
|
105
|
+
refObj.source = rawRef[GL.SOURCE] ?? rawRef.source;
|
|
106
|
+
refObj.id = rawRef[GL.LOCAL_ID] ?? rawRef.id;
|
|
107
|
+
refObj.version = rawRef[GL.VERSION] ?? rawRef.version;
|
|
108
|
+
if (rawRef[GL.TEXT]) refObj.text = rawRef[GL.TEXT];
|
|
109
|
+
return (refObj.source ?? refObj.id ?? refObj.version ?? refObj.text) != null
|
|
110
|
+
? refObj : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Map JSON-LD locality to glossarist's snake_case format.
|
|
115
|
+
* Always uses snake_case (reference_from/reference_to) for consistency
|
|
116
|
+
* with the glossarist model.
|
|
117
|
+
*/
|
|
118
|
+
export function mapLocalityFromJsonLd(rawLoc: JsonLdLocality | undefined): Record<string, unknown> | null {
|
|
119
|
+
if (!rawLoc) return null;
|
|
120
|
+
const locObj: Record<string, unknown> = {};
|
|
121
|
+
locObj.type = rawLoc[GL.LOCALITY_TYPE] ?? rawLoc.type;
|
|
122
|
+
locObj.reference_from = rawLoc[GL.REFERENCE_FROM] ?? rawLoc.reference_from;
|
|
123
|
+
locObj.reference_to = rawLoc[GL.REFERENCE_TO] ?? rawLoc.reference_to;
|
|
124
|
+
return (locObj.type ?? locObj.reference_from ?? locObj.reference_to) != null
|
|
125
|
+
? locObj : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function mapDetailedDefinitionFromJsonLd(d: any): Record<string, unknown> {
|
|
129
|
+
const result: Record<string, unknown> = { content: d[GL.CONTENT] ?? '' };
|
|
130
|
+
if (d[GL.EXAMPLES]?.length) {
|
|
131
|
+
result.examples = d[GL.EXAMPLES].map(mapDetailedDefinitionFromJsonLd);
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function mapOriginFromJsonLd(o: JsonLdOrigin): Record<string, unknown> {
|
|
137
|
+
const origin: Record<string, unknown> = {};
|
|
138
|
+
const ref = mapRefFromJsonLd(o[GL.REF]);
|
|
139
|
+
if (ref) origin.ref = ref;
|
|
140
|
+
const loc = mapLocalityFromJsonLd(o[GL.LOCALITY]);
|
|
141
|
+
if (loc) origin.locality = loc;
|
|
142
|
+
if (o[GL.LINK]) origin.link = o[GL.LINK];
|
|
143
|
+
if (o[GL.LOCAL_ID]) origin.id = o[GL.LOCAL_ID];
|
|
144
|
+
if (o[GL.VERSION]) origin.version = o[GL.VERSION];
|
|
145
|
+
if (o[GL.SOURCE]) origin.source = o[GL.SOURCE];
|
|
146
|
+
return origin;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function mapSourceFromJsonLd(s: JsonLdSource): Record<string, unknown> {
|
|
150
|
+
const result: Record<string, unknown> = {};
|
|
151
|
+
// JsonLdSource uses GL.LOCAL_ID ('gl:id') for the source's local id,
|
|
152
|
+
// distinct from JSON-LD's '@id' (the concept IRI).
|
|
153
|
+
if (s[GL.LOCAL_ID]) result.id = s[GL.LOCAL_ID];
|
|
154
|
+
if (s[GL.SOURCE_TYPE]) result.type = s[GL.SOURCE_TYPE];
|
|
155
|
+
if (s[GL.SOURCE_STATUS]) result.status = s[GL.SOURCE_STATUS];
|
|
156
|
+
if (s[GL.MODIFICATION]) result.modification = s[GL.MODIFICATION];
|
|
157
|
+
|
|
158
|
+
const origin = s[GL.ORIGIN];
|
|
159
|
+
if (origin) {
|
|
160
|
+
result.origin = mapOriginFromJsonLd(origin);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const sf = s[GL.SOURCED_FROM] ?? s[GL.SOURCED_FROM_ALT];
|
|
164
|
+
if (sf?.length) {
|
|
165
|
+
result.sourced_from = sf.map(item => mapOriginFromJsonLd(item));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function mapRelatedFromJsonLd(r: JsonLdRelated): Record<string, unknown> {
|
|
172
|
+
const result: Record<string, unknown> = { type: 'references' };
|
|
173
|
+
|
|
174
|
+
if (r[GL.RELATIONSHIP_TYPE]) {
|
|
175
|
+
result.type = r[GL.RELATIONSHIP_TYPE];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (r[GL.REF]) {
|
|
179
|
+
const ref = mapRefFromJsonLd(r[GL.REF]);
|
|
180
|
+
if (ref) result.ref = ref;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!result.ref && r['@id']) {
|
|
184
|
+
const uri = r['@id'];
|
|
185
|
+
const idMatch = uri.match(/\/concept\/([^/]+)$/);
|
|
186
|
+
result.ref = idMatch
|
|
187
|
+
? { source: uri.split('/').slice(-3, -2)[0] || '', id: idMatch[1] }
|
|
188
|
+
: { source: uri, id: null };
|
|
189
|
+
}
|
|
190
|
+
if (r[GL.TERM]) result.content = r[GL.TERM];
|
|
191
|
+
|
|
192
|
+
// Bridged fields — stored in raw dict, extracted by attachBridges()
|
|
193
|
+
if (r[GL.SOURCE_ID]) result.sourceId = r[GL.SOURCE_ID];
|
|
194
|
+
const rawCitation = r[GL.CITATION];
|
|
195
|
+
if (rawCitation) {
|
|
196
|
+
const c = rawCitation;
|
|
197
|
+
const citation: Record<string, unknown> = {};
|
|
198
|
+
if (c[GL.REF]) {
|
|
199
|
+
const cr = mapRefFromJsonLd(c[GL.REF]);
|
|
200
|
+
if (cr) citation.ref = cr;
|
|
201
|
+
}
|
|
202
|
+
const loc = mapLocalityFromJsonLd(c[GL.LOCALITY]);
|
|
203
|
+
if (loc) citation.locality = loc;
|
|
204
|
+
if (c[GL.LINK]) citation.link = c[GL.LINK];
|
|
205
|
+
if (Object.keys(citation).length > 0) result.citation = citation;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function mapLocalizedFromJsonLd(lc: JsonLdLocalizedConcept): Record<string, unknown> {
|
|
212
|
+
const data: Record<string, unknown> = {};
|
|
213
|
+
|
|
214
|
+
if (lc[GL.LANGUAGE_CODE]) data.language_code = lc[GL.LANGUAGE_CODE];
|
|
215
|
+
if (lc[GL.ENTRY_STATUS]) data.entry_status = lc[GL.ENTRY_STATUS];
|
|
216
|
+
if (lc[GL.CLASSIFICATION]) data.classification = lc[GL.CLASSIFICATION];
|
|
217
|
+
if (lc[GL.REVIEW_TYPE]) data.review_type = lc[GL.REVIEW_TYPE];
|
|
218
|
+
if (lc[GL.DOMAIN]) data.domain = lc[GL.DOMAIN];
|
|
219
|
+
if (lc[GL.RELEASE]) data.release = lc[GL.RELEASE];
|
|
220
|
+
if (lc[GL.LINEAGE_SOURCE_SIMILARITY] != null) data.lineage_source_similarity = lc[GL.LINEAGE_SOURCE_SIMILARITY];
|
|
221
|
+
if (lc[GL.SCRIPT]) data.script = lc[GL.SCRIPT];
|
|
222
|
+
if (lc[GL.SYSTEM]) data.system = lc[GL.SYSTEM];
|
|
223
|
+
|
|
224
|
+
const designation = lc[GL.DESIGNATION];
|
|
225
|
+
if (designation?.length) {
|
|
226
|
+
data.terms = designation.map(mapDesignationFromJsonLd);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const definition = lc[GL.DEFINITION];
|
|
230
|
+
if (definition?.length) {
|
|
231
|
+
data.definition = definition.map(mapDetailedDefinitionFromJsonLd);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const notes = lc[GL.NOTES];
|
|
235
|
+
if (notes?.length) {
|
|
236
|
+
data.notes = notes.map(mapDetailedDefinitionFromJsonLd);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const annotations = lc[GL.ANNOTATIONS];
|
|
240
|
+
if (annotations?.length) {
|
|
241
|
+
data.annotations = annotations.map(mapDetailedDefinitionFromJsonLd);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const examples = lc[GL.EXAMPLES];
|
|
245
|
+
if (examples?.length) {
|
|
246
|
+
data.examples = examples.map(mapDetailedDefinitionFromJsonLd);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const lcSources = lc[GL.SOURCE];
|
|
250
|
+
if (lcSources?.length) {
|
|
251
|
+
data.sources = lcSources.map(mapSourceFromJsonLd);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const dates = lc[GL.DATES];
|
|
255
|
+
if (dates?.length) {
|
|
256
|
+
data.dates = dates.map(d => ({
|
|
257
|
+
date: d[GL.DATE] ?? null,
|
|
258
|
+
type: d[GL.DATE_TYPE] ?? null,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const lcReferences = lc[GL.REFERENCES];
|
|
263
|
+
if (lcReferences?.length) {
|
|
264
|
+
data.related = lcReferences.map(mapRelatedFromJsonLd);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (lc[GL.REVIEW_DATE]) data.review_date = lc[GL.REVIEW_DATE];
|
|
268
|
+
if (lc[GL.REVIEW_DECISION_DATE]) data.review_decision_date = lc[GL.REVIEW_DECISION_DATE];
|
|
269
|
+
if (lc[GL.REVIEW_DECISION_EVENT]) data.review_decision_event = lc[GL.REVIEW_DECISION_EVENT];
|
|
270
|
+
if (lc[GL.REVIEW_STATUS]) data.review_status = lc[GL.REVIEW_STATUS];
|
|
271
|
+
if (lc[GL.REVIEW_DECISION]) data.review_decision = lc[GL.REVIEW_DECISION];
|
|
272
|
+
if (lc[GL.REVIEW_DECISION_NOTES]) data.review_decision_notes = lc[GL.REVIEW_DECISION_NOTES];
|
|
273
|
+
|
|
274
|
+
return data;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export type { JsonLdConcept };
|