glossarist 0.4.39 → 0.4.41
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/models/concept.ts +499 -0
- package/src/models/designation.ts +297 -0
- package/src/models/gcr-metadata.ts +118 -0
- package/src/models/gcr-statistics.ts +76 -0
- package/src/models/hyperedge-colors.ts +87 -0
- package/src/models/localized-concept.ts +284 -0
- package/src/models/register.ts +315 -0
- package/src/models/relation-categories.ts +167 -0
- package/src/models/{relation-colors.js → relation-colors.ts} +36 -23
- package/src/models/{relation-loader.js → relation-loader.ts} +50 -46
- package/src/models/section.ts +61 -0
- package/src/models/concept.js +0 -420
- package/src/models/designation.js +0 -213
- package/src/models/gcr-metadata.js +0 -68
- package/src/models/gcr-statistics.js +0 -51
- package/src/models/hyperedge-colors.js +0 -71
- package/src/models/localized-concept.js +0 -187
- package/src/models/register.js +0 -253
- package/src/models/relation-categories.js +0 -182
- package/src/models/section.js +0 -44
package/src/models/concept.js
DELETED
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
import { GlossaristModel } from './base.js';
|
|
2
|
-
import { LocalizedConcept } from './localized-concept.js';
|
|
3
|
-
import { RelatedConcept } from './related-concept.js';
|
|
4
|
-
import { PartitiveHyperedge } from './partitive-hyperedge.js';
|
|
5
|
-
import { AbstractHyperedge } from './abstract-hyperedge.js';
|
|
6
|
-
import { HyperedgeRegistry, groupHyperedgesByWireKey } from './hyperedge-registry.js';
|
|
7
|
-
import { migrateHyperedgeToRelation } from '../migration/hyperedge-migrator.js';
|
|
8
|
-
import { ConceptReference } from './concept-reference.js';
|
|
9
|
-
import { ConceptDate } from './concept-date.js';
|
|
10
|
-
import { ConceptSource } from './concept-source.js';
|
|
11
|
-
import { FigureReference } from './non-verbal-references.js';
|
|
12
|
-
import { TableReference } from './non-verbal-references.js';
|
|
13
|
-
import { FormulaReference } from './non-verbal-references.js';
|
|
14
|
-
import { diffConcepts } from '../diff/concept-diff.js';
|
|
15
|
-
|
|
16
|
-
export class Concept extends GlossaristModel {
|
|
17
|
-
// Scalar fields the diff layer tracks at the concept level. Adding
|
|
18
|
-
// a new scalar metadata field requires only appending its name here;
|
|
19
|
-
// diff detection, patch application, and similarity scoring all pick
|
|
20
|
-
// it up automatically. (Invariant N2 — TODO.hyperedges-v2/07.)
|
|
21
|
-
static get DIFF_FIELDS() {
|
|
22
|
-
return Object.freeze(['status', 'term', 'uri', 'schemaVersion']);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
static wireNameFor(field) {
|
|
26
|
-
return CONCEPT_WIRE_NAMES[field] ?? field;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
constructor(data = {}) {
|
|
30
|
-
super();
|
|
31
|
-
this.id = String(data.id ?? data.termid ?? '');
|
|
32
|
-
this.term = data.term ?? null;
|
|
33
|
-
this.uri = data.uri ?? null;
|
|
34
|
-
this._rawLocalizations = data.localizations ?? {};
|
|
35
|
-
this._cache = {};
|
|
36
|
-
|
|
37
|
-
this.relatedConcepts = _mapInstances(data.relatedConcepts ?? data.related ?? data.related_concepts ?? [], RelatedConcept);
|
|
38
|
-
|
|
39
|
-
// Unified hyperedge array. Every hyperedge (PartitiveHyperedge,
|
|
40
|
-
// GenericHyperedge, future TemporalHyperedge / AssociativeHyperedge /
|
|
41
|
-
// SequentialHyperedge) lives here. The single public API is
|
|
42
|
-
// `.relations`; external systems that need to filter by type use
|
|
43
|
-
// `concept.relations.filter(r => r instanceof X)` or dispatch via
|
|
44
|
-
// HyperedgeRegistry (preferred — see Phases 6 and 11).
|
|
45
|
-
//
|
|
46
|
-
// Throws on ambiguous input (caller passed both `relations` and any
|
|
47
|
-
// typed wire key) and on duplicate relations (same identity twice).
|
|
48
|
-
// Silent drop / silent dedupe are footguns the audit (C4, C6)
|
|
49
|
-
// flagged; explicit throws make corruption loud.
|
|
50
|
-
this.relations = _resolveHyperedges(data);
|
|
51
|
-
|
|
52
|
-
this.domains = _normalizeDomains(data.domains, data.groups);
|
|
53
|
-
this.groups = Array.isArray(data.groups)
|
|
54
|
-
? data.groups.map(g => typeof g === 'string' ? g : (g?.id ?? g?.sectionId ?? null)).filter(Boolean)
|
|
55
|
-
: [];
|
|
56
|
-
this.sections = Array.isArray(data.sections)
|
|
57
|
-
? data.sections.map(s => typeof s === 'string' ? s : (s?.id ?? s?.sectionId ?? null)).filter(Boolean)
|
|
58
|
-
: [];
|
|
59
|
-
this.tags = Array.isArray(data.tags) ? [...data.tags] : [];
|
|
60
|
-
this.dates = _mapInstances(data.dates ?? [], ConceptDate);
|
|
61
|
-
this.sources = _mapInstances(data.sources ?? [], ConceptSource);
|
|
62
|
-
this._rawFigures = data.figures ?? [];
|
|
63
|
-
this._rawTables = data.tables ?? [];
|
|
64
|
-
this._rawFormulas = data.formulas ?? [];
|
|
65
|
-
this._figures = null;
|
|
66
|
-
this._tables = null;
|
|
67
|
-
this._formulas = null;
|
|
68
|
-
this.status = data.status ?? null;
|
|
69
|
-
this.schemaVersion = data.schemaVersion ?? data.schema_version ?? '3';
|
|
70
|
-
this.raw = data.raw ?? null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get termid() { return this.id; }
|
|
74
|
-
|
|
75
|
-
// No typed projections. Use concept.relations directly and filter
|
|
76
|
-
// by instanceof, or dispatch via HyperedgeRegistry. The old
|
|
77
|
-
// .partitiveRelations / .genericRelations / .partitiveHyperedges
|
|
78
|
-
// getters were removed per TODO Phase 4 — they were footguns
|
|
79
|
-
// (audit C3: .partitiveHyperedges returned v2 instances but callers
|
|
80
|
-
// expected v1 shape; multiple validators silently no-op'd).
|
|
81
|
-
//
|
|
82
|
-
// Snippet migration guide:
|
|
83
|
-
// concept.partitiveRelations → concept.relations.filter(r => r instanceof PartitiveHyperedge)
|
|
84
|
-
// concept.genericRelations → concept.relations.filter(r => r instanceof GenericHyperedge)
|
|
85
|
-
// concept.partitiveHyperedges → concept.relations.filter(r => r instanceof PartitiveHyperedge)
|
|
86
|
-
|
|
87
|
-
get languages() {
|
|
88
|
-
return Object.keys(this._rawLocalizations);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get languageCodes() {
|
|
92
|
-
return this.languages;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
localization(lang) {
|
|
96
|
-
if (!(lang in this._rawLocalizations)) return undefined;
|
|
97
|
-
if (!this._cache[lang]) {
|
|
98
|
-
const raw = this._rawLocalizations[lang];
|
|
99
|
-
this._cache[lang] = new LocalizedConcept({ ...raw, language_code: lang });
|
|
100
|
-
}
|
|
101
|
-
return this._cache[lang];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
diff(other, language = 'eng') {
|
|
105
|
-
return diffConcepts(this, other, language);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
primaryDesignation(lang) {
|
|
109
|
-
return this.localization(lang)?.primaryDesignation ?? null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
definition(lang) {
|
|
113
|
-
return this.localization(lang)?.primaryDefinition ?? null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
setLocalization(lang, lc) {
|
|
117
|
-
if (lc instanceof LocalizedConcept) {
|
|
118
|
-
this._cache[lang] = lc;
|
|
119
|
-
const json = lc.toJSON();
|
|
120
|
-
delete json.language_code;
|
|
121
|
-
this._rawLocalizations[lang] = json;
|
|
122
|
-
} else {
|
|
123
|
-
this._rawLocalizations[lang] = lc;
|
|
124
|
-
delete this._cache[lang];
|
|
125
|
-
}
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
hasLocalization(lang) {
|
|
130
|
-
return lang in this._rawLocalizations;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Find a source by its local id within this concept.
|
|
135
|
-
*
|
|
136
|
-
* The lookup walks concept-level, localization-level, and
|
|
137
|
-
* designation-level sources in that order, returning the first
|
|
138
|
-
* source whose `id` matches. Sources without an `id` are
|
|
139
|
-
* skipped. The id is local to the concept; uniqueness is
|
|
140
|
-
* enforced by the validator (see CiteRefIntegrityRule).
|
|
141
|
-
*
|
|
142
|
-
* @param {string} id
|
|
143
|
-
* @returns {ConceptSource | null}
|
|
144
|
-
*/
|
|
145
|
-
findSourceById(id) {
|
|
146
|
-
if (typeof id !== 'string' || id.length === 0) return null;
|
|
147
|
-
|
|
148
|
-
// 1. Concept-level sources.
|
|
149
|
-
for (const source of this.sources) {
|
|
150
|
-
if (source.id === id) return source;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 2. Localization-level sources.
|
|
154
|
-
for (const lang of this.languages) {
|
|
155
|
-
const lc = this.localization(lang);
|
|
156
|
-
if (!lc) continue;
|
|
157
|
-
for (const source of lc.sources) {
|
|
158
|
-
if (source.id === id) return source;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 3. Designation-level sources.
|
|
162
|
-
for (const designation of lc.terms) {
|
|
163
|
-
for (const source of designation.sources) {
|
|
164
|
-
if (source.id === id) return source;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
get figures() {
|
|
173
|
-
return this._lazy('_figures', '_rawFigures',
|
|
174
|
-
r => FigureReference.fromJSON(r));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
get tables() {
|
|
178
|
-
return this._lazy('_tables', '_rawTables',
|
|
179
|
-
r => TableReference.fromJSON(r));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
get formulas() {
|
|
183
|
-
return this._lazy('_formulas', '_rawFormulas',
|
|
184
|
-
r => FormulaReference.fromJSON(r));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Yield every content-text fragment in this concept across all
|
|
189
|
-
* localizations, recursing through nested examples. Each yielded
|
|
190
|
-
* `{ text, source }` carries a dotted path rooted at
|
|
191
|
-
* `localizations.<lang>.<slot>[i]...content`.
|
|
192
|
-
*
|
|
193
|
-
* Designations are not included; they live on `LocalizedConcept.terms`
|
|
194
|
-
* and have a different shape.
|
|
195
|
-
*/
|
|
196
|
-
*walkTexts() {
|
|
197
|
-
for (const lang of this.languages) {
|
|
198
|
-
const lc = this.localization(lang);
|
|
199
|
-
if (!lc) continue;
|
|
200
|
-
yield* lc.walkTexts(`localizations.${lang}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
toJSON() {
|
|
205
|
-
const obj = { id: this.id };
|
|
206
|
-
if (this.term != null) obj.term = this.term;
|
|
207
|
-
if (this.uri != null) obj.uri = this.uri;
|
|
208
|
-
|
|
209
|
-
if (Object.keys(this._rawLocalizations).length > 0) {
|
|
210
|
-
obj.localizations = {};
|
|
211
|
-
for (const lang of this.languages) {
|
|
212
|
-
const lc = this.localization(lang);
|
|
213
|
-
if (lc) {
|
|
214
|
-
const json = lc.toJSON();
|
|
215
|
-
delete json.language_code;
|
|
216
|
-
obj.localizations[lang] = json;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
obj.localizations = {};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (this.relatedConcepts.length > 0) {
|
|
224
|
-
obj.related = this.relatedConcepts.map(rc => rc.toJSON());
|
|
225
|
-
}
|
|
226
|
-
// Emit per-type wire keys from the unified relations array,
|
|
227
|
-
// partitioned via HyperedgeRegistry. Adding a new hyperedge type
|
|
228
|
-
// means its wire key appears here automatically — no edits to
|
|
229
|
-
// Concept.toJSON.
|
|
230
|
-
const grouped = groupHyperedgesByWireKey(this.relations);
|
|
231
|
-
for (const [wireKey, rels] of Object.entries(grouped)) {
|
|
232
|
-
if (rels.length > 0) obj[wireKey] = rels.map(r => r.toJSON());
|
|
233
|
-
}
|
|
234
|
-
if (this.domains.length > 0) {
|
|
235
|
-
obj.domains = this.domains.map(d => d.toJSON());
|
|
236
|
-
}
|
|
237
|
-
if (this.tags.length > 0) {
|
|
238
|
-
obj.tags = [...this.tags];
|
|
239
|
-
}
|
|
240
|
-
if (this.dates.length > 0) {
|
|
241
|
-
obj.dates = this.dates.map(d => d.toJSON());
|
|
242
|
-
}
|
|
243
|
-
if (this.sources.length > 0) {
|
|
244
|
-
obj.sources = this.sources.map(s => s.toJSON());
|
|
245
|
-
}
|
|
246
|
-
this._serialize(obj, 'figures', '_figures', '_rawFigures');
|
|
247
|
-
this._serialize(obj, 'tables', '_tables', '_rawTables');
|
|
248
|
-
this._serialize(obj, 'formulas', '_formulas', '_rawFormulas');
|
|
249
|
-
if (this.status != null) obj.status = this.status;
|
|
250
|
-
obj.schema_version = this.schemaVersion;
|
|
251
|
-
return obj;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
static fromJSON(data) {
|
|
255
|
-
return new Concept(data);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function _mapInstances(arr, Cls) {
|
|
260
|
-
return arr.map(item => item instanceof Cls ? item : new Cls(item));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Resolve the unified hyperedge array from any input shape.
|
|
264
|
-
//
|
|
265
|
-
// Three input categories are accepted; mixing them throws (audit C4 —
|
|
266
|
-
// silent drop was a footgun):
|
|
267
|
-
//
|
|
268
|
-
// 1. data.relations — preferred unified shape. Entries
|
|
269
|
-
// are typed instances or hashes
|
|
270
|
-
// carrying a `type` discriminator
|
|
271
|
-
// (looked up via HyperedgeRegistry).
|
|
272
|
-
// 2. Per-type v2 wire keys — one of HyperedgeRegistry.allWireKeys()
|
|
273
|
-
// (partitive_relations, generic_relations).
|
|
274
|
-
// 3. Per-type v1 legacy keys — declared on each class via
|
|
275
|
-
// `static v1WireKeys` (e.g.
|
|
276
|
-
// partitive_hyperedges → migrated).
|
|
277
|
-
//
|
|
278
|
-
// Adding a new hyperedge type means: declare the leaf class with its
|
|
279
|
-
// metadata block, register it. Concept, parser, serializer, diff,
|
|
280
|
-
// patch, renderer, RDF emitter do not change. (Phase 11 OCP contract.)
|
|
281
|
-
function _resolveHyperedges(data) {
|
|
282
|
-
_assertNoAmbiguousInput(data);
|
|
283
|
-
|
|
284
|
-
if (Array.isArray(data.relations)) {
|
|
285
|
-
return _dedupeOrThrow(data.relations.map(r => _coerceHyperedge(r)).filter(r => r != null));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const out = [];
|
|
289
|
-
|
|
290
|
-
for (const cls of HyperedgeRegistry.allClasses()) {
|
|
291
|
-
const arr = data[cls.wireKey] ?? data[_camelCase(cls.wireKey)];
|
|
292
|
-
if (!Array.isArray(arr)) continue;
|
|
293
|
-
for (const r of arr) {
|
|
294
|
-
const coerced = _coerceHyperedge(r, cls);
|
|
295
|
-
if (coerced) out.push(coerced);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
for (const cls of HyperedgeRegistry.allClasses()) {
|
|
300
|
-
for (const v1Key of cls.v1WireKeys ?? []) {
|
|
301
|
-
const camelV1 = _camelCase(v1Key);
|
|
302
|
-
const arr = data[v1Key] ?? data[camelV1];
|
|
303
|
-
if (!Array.isArray(arr)) continue;
|
|
304
|
-
for (const h of arr) {
|
|
305
|
-
const migrated = _migrateV1Hash(cls, v1Key, h);
|
|
306
|
-
if (migrated) out.push(new cls(migrated));
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return _dedupeOrThrow(out);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Audit C4: silent drop on mixed input is a footgun. Throw instead.
|
|
315
|
-
// Allows `relations` alone, or any combination of typed keys alone —
|
|
316
|
-
// but not both at once.
|
|
317
|
-
function _assertNoAmbiguousInput(data) {
|
|
318
|
-
if (!Array.isArray(data.relations)) return;
|
|
319
|
-
const typedKeys = [];
|
|
320
|
-
for (const cls of HyperedgeRegistry.allClasses()) {
|
|
321
|
-
if (Array.isArray(data[cls.wireKey])) typedKeys.push(cls.wireKey);
|
|
322
|
-
const camelWire = _camelCase(cls.wireKey);
|
|
323
|
-
if (camelWire !== cls.wireKey && Array.isArray(data[camelWire])) {
|
|
324
|
-
typedKeys.push(camelWire);
|
|
325
|
-
}
|
|
326
|
-
for (const v1 of cls.v1WireKeys ?? []) {
|
|
327
|
-
if (Array.isArray(data[v1])) typedKeys.push(v1);
|
|
328
|
-
const camelV1 = _camelCase(v1);
|
|
329
|
-
if (camelV1 !== v1 && Array.isArray(data[camelV1])) typedKeys.push(camelV1);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (typedKeys.length > 0) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
`Concept input is ambiguous: pass either 'relations' (unified) ` +
|
|
335
|
-
`or typed wire keys (${typedKeys.join(', ')}), not both. ` +
|
|
336
|
-
` Mixing them silently drops the typed keys, which has caused ` +
|
|
337
|
-
` data loss in production (see audit C4).`,
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Audit C6: do NOT silently dedupe. Duplicate relations indicate a bug
|
|
343
|
-
// in the caller; surface it. The identity function delegates to each
|
|
344
|
-
// class's static identityOf (polymorphic — handles mixed types).
|
|
345
|
-
function _dedupeOrThrow(relations) {
|
|
346
|
-
const seen = new Map();
|
|
347
|
-
for (const r of relations) {
|
|
348
|
-
const id = _hyperedgeIdentity(r);
|
|
349
|
-
if (seen.has(id)) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`Concept input has duplicate hyperedge (identity=${id}). ` +
|
|
352
|
-
`Passing the same relation twice is a bug — the parser and ` +
|
|
353
|
-
`serializer would silently emit it twice on disk. ` +
|
|
354
|
-
`Audit C6 requires we surface this loudly instead.`,
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
seen.set(id, r);
|
|
358
|
-
}
|
|
359
|
-
return relations;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function _hyperedgeIdentity(r) {
|
|
363
|
-
if (r == null) return '';
|
|
364
|
-
const Cls = r.constructor;
|
|
365
|
-
if (typeof Cls?.identityOf === 'function') {
|
|
366
|
-
return `${Cls.name}::${Cls.identityOf(r)}`;
|
|
367
|
-
}
|
|
368
|
-
return String(r);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function _coerceHyperedge(value, fallbackClass) {
|
|
372
|
-
if (value == null) return null;
|
|
373
|
-
if (value instanceof AbstractHyperedge) return value;
|
|
374
|
-
|
|
375
|
-
const hash = typeof value.toJSON === 'function' ? value.toJSON() : value;
|
|
376
|
-
const cls = _classForHash(hash) ?? fallbackClass;
|
|
377
|
-
if (!cls) return null;
|
|
378
|
-
return new cls(hash);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function _classForHash(hash) {
|
|
382
|
-
if (!hash || typeof hash !== 'object') return null;
|
|
383
|
-
const type = hash.type;
|
|
384
|
-
if (typeof type !== 'string') return null;
|
|
385
|
-
return HyperedgeRegistry.forTypeTag(type);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// v1 → v2 migration for hashes from legacy wire keys. Currently only
|
|
389
|
-
// PartitiveHyperedge has v1 wire keys (partitive_hyperedges). Future
|
|
390
|
-
// types extend this dispatch — but it lives here in the migration
|
|
391
|
-
// helper, not in the model classes, since v1 is a legacy concern.
|
|
392
|
-
function _migrateV1Hash(cls, v1Key, hash) {
|
|
393
|
-
if (cls === PartitiveHyperedge && v1Key === 'partitive_hyperedges') {
|
|
394
|
-
const normalized = hash;
|
|
395
|
-
return migrateHyperedgeToRelation(normalized);
|
|
396
|
-
}
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// snake_case → camelCase
|
|
401
|
-
function _camelCase(s) {
|
|
402
|
-
if (typeof s !== 'string' || !s.includes('_')) return s;
|
|
403
|
-
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const CONCEPT_WIRE_NAMES = Object.freeze({
|
|
407
|
-
schemaVersion: 'schema_version',
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
function _normalizeDomains(domains, groups) {
|
|
411
|
-
if (domains) {
|
|
412
|
-
return domains.map(d => d instanceof ConceptReference ? d : new ConceptReference(d));
|
|
413
|
-
}
|
|
414
|
-
if (groups) {
|
|
415
|
-
return groups.map(g => typeof g === 'string'
|
|
416
|
-
? ConceptReference.domain(g)
|
|
417
|
-
: new ConceptReference(g));
|
|
418
|
-
}
|
|
419
|
-
return [];
|
|
420
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { RegistrableModel } from './registrable.js';
|
|
2
|
-
import { ConceptSource } from './concept-source.js';
|
|
3
|
-
import { Pronunciation } from './pronunciation.js';
|
|
4
|
-
import { GrammarInfo } from './grammar-info.js';
|
|
5
|
-
import { RelatedConcept } from './related-concept.js';
|
|
6
|
-
import { DesignationRelationship, DESIGNATION_RELATIONSHIP_TYPES } from './designation-relationship.js';
|
|
7
|
-
|
|
8
|
-
// Maps a normative-status local-name to the matching SKOS/SKOS-XL label
|
|
9
|
-
// predicate local-name. Lives on the model so a new status requires a
|
|
10
|
-
// single edit here, not edits in every emitter.
|
|
11
|
-
const SKOS_LABEL_BY_NORMATIVE_STATUS = Object.freeze({
|
|
12
|
-
preferred: 'prefLabel',
|
|
13
|
-
deprecated: 'hiddenLabel',
|
|
14
|
-
admitted: 'altLabel',
|
|
15
|
-
deprecated_: 'altLabel',
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export class Designation extends RegistrableModel {
|
|
19
|
-
constructor(data = {}) {
|
|
20
|
-
super();
|
|
21
|
-
this.designation = data.designation ?? '';
|
|
22
|
-
this.type = data.type ?? 'expression';
|
|
23
|
-
this.normativeStatus = data.normative_status ?? null;
|
|
24
|
-
this.absent = data.absent ?? null;
|
|
25
|
-
this.fieldOfApplication = data.field_of_application ?? null;
|
|
26
|
-
this.usageInfo = data.usage_info ?? null;
|
|
27
|
-
this.geographicalArea = data.geographical_area ?? null;
|
|
28
|
-
this.language = data.language ?? null;
|
|
29
|
-
this.script = data.script ?? null;
|
|
30
|
-
this.system = data.system ?? null;
|
|
31
|
-
this.international = data.international ?? null;
|
|
32
|
-
this.termType = data.term_type ?? null;
|
|
33
|
-
this.pronunciations = (data.pronunciation ?? []).map(
|
|
34
|
-
p => p instanceof Pronunciation ? p : new Pronunciation(p)
|
|
35
|
-
);
|
|
36
|
-
this.sources = (data.sources ?? []).map(
|
|
37
|
-
s => s instanceof ConceptSource ? s : new ConceptSource(s)
|
|
38
|
-
);
|
|
39
|
-
this.related = (data.related ?? []).map(
|
|
40
|
-
r => (r instanceof DesignationRelationship || r instanceof RelatedConcept)
|
|
41
|
-
? r
|
|
42
|
-
: DESIGNATION_RELATIONSHIP_TYPES.includes(r.type)
|
|
43
|
-
? DesignationRelationship.fromJSON(r)
|
|
44
|
-
: RelatedConcept.fromJSON(r)
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
toJSON() {
|
|
49
|
-
const obj = { type: this.type, designation: this.designation };
|
|
50
|
-
if (this.normativeStatus != null) obj.normative_status = this.normativeStatus;
|
|
51
|
-
if (this.absent != null) obj.absent = this.absent;
|
|
52
|
-
if (this.fieldOfApplication != null) obj.field_of_application = this.fieldOfApplication;
|
|
53
|
-
if (this.usageInfo != null) obj.usage_info = this.usageInfo;
|
|
54
|
-
if (this.geographicalArea != null) obj.geographical_area = this.geographicalArea;
|
|
55
|
-
if (this.language != null) obj.language = this.language;
|
|
56
|
-
if (this.script != null) obj.script = this.script;
|
|
57
|
-
if (this.system != null) obj.system = this.system;
|
|
58
|
-
if (this.international != null) obj.international = this.international;
|
|
59
|
-
if (this.termType != null) obj.term_type = this.termType;
|
|
60
|
-
if (this.pronunciations.length > 0) obj.pronunciation = this.pronunciations.map(p => p.toJSON());
|
|
61
|
-
if (this.sources.length > 0) obj.sources = this.sources.map(s => s.toJSON());
|
|
62
|
-
if (this.related.length > 0) obj.related = this.related.map(r => r.toJSON());
|
|
63
|
-
return obj;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// RDF class local-name for this designation subtype. Override in
|
|
67
|
-
// subclasses that map to a different ontology class.
|
|
68
|
-
rdfClass() {
|
|
69
|
-
return 'Expression';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// SKOS label predicate URI (skos:prefLabel / skos:altLabel / skos:hiddenLabel)
|
|
73
|
-
// appropriate for this designation's normative status. Falls back to
|
|
74
|
-
// skos:altLabel for unknown statuses.
|
|
75
|
-
skosLabelPredicate(skosNs) {
|
|
76
|
-
const label = this._skosLabelLocalName();
|
|
77
|
-
return `${skosNs}${label}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// SKOS-XL label predicate URI (skosxl:prefLabel / etc.).
|
|
81
|
-
skosxlLabelPredicate(skosxlNs) {
|
|
82
|
-
const label = this._skosLabelLocalName();
|
|
83
|
-
return `${skosxlNs}${label}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
_skosLabelLocalName() {
|
|
87
|
-
const status = String(this.normativeStatus ?? '')
|
|
88
|
-
.split(/[/#]/)
|
|
89
|
-
.pop()
|
|
90
|
-
.trim();
|
|
91
|
-
return SKOS_LABEL_BY_NORMATIVE_STATUS[status] ?? 'altLabel';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Static identity works on a model instance OR its plain JSON form.
|
|
95
|
-
// The diff layer uses this so that patch-side identity (computed on
|
|
96
|
-
// plain JSON from toJSON) matches diff-side identity (computed on
|
|
97
|
-
// model instances).
|
|
98
|
-
static identityOf(value) {
|
|
99
|
-
const v = value ?? {};
|
|
100
|
-
const type = v.type ?? 'expression';
|
|
101
|
-
const text = String(v.designation ?? '').toLowerCase().trim();
|
|
102
|
-
return `${type}|${text}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
identity() {
|
|
106
|
-
return Designation.identityOf(this);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
static fromJSON(data) {
|
|
110
|
-
return Designation.fromData(data);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export class Expression extends Designation {
|
|
115
|
-
constructor(data = {}) {
|
|
116
|
-
super(data);
|
|
117
|
-
this.prefix = data.prefix ?? null;
|
|
118
|
-
this.grammarInfo = (data.grammar_info ?? []).map(
|
|
119
|
-
g => g instanceof GrammarInfo ? g : new GrammarInfo(g)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
toJSON() {
|
|
124
|
-
const obj = super.toJSON();
|
|
125
|
-
if (this.prefix != null) obj.prefix = this.prefix;
|
|
126
|
-
if (this.grammarInfo.length > 0) obj.grammar_info = this.grammarInfo.map(g => g.toJSON());
|
|
127
|
-
return obj;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
static fromJSON(data) { return new Expression(data); }
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
Designation.register('expression', Expression);
|
|
134
|
-
|
|
135
|
-
export class Abbreviation extends Expression {
|
|
136
|
-
constructor(data = {}) {
|
|
137
|
-
super(data);
|
|
138
|
-
this.acronym = data.acronym ?? false;
|
|
139
|
-
this.initialism = data.initialism ?? false;
|
|
140
|
-
this.truncation = data.truncation ?? false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
rdfClass() {
|
|
144
|
-
return 'Abbreviation';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
toJSON() {
|
|
148
|
-
const obj = super.toJSON();
|
|
149
|
-
if (this.acronym) obj.acronym = true;
|
|
150
|
-
if (this.initialism) obj.initialism = true;
|
|
151
|
-
if (this.truncation) obj.truncation = true;
|
|
152
|
-
return obj;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
static fromJSON(data) { return new Abbreviation(data); }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Designation.register('abbreviation', Abbreviation);
|
|
159
|
-
|
|
160
|
-
export class Symbol extends Designation {
|
|
161
|
-
rdfClass() {
|
|
162
|
-
return 'Symbol';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
static fromJSON(data) { return new Symbol(data); }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
Designation.register('symbol', Symbol);
|
|
169
|
-
|
|
170
|
-
export class LetterSymbol extends Symbol {
|
|
171
|
-
constructor(data = {}) {
|
|
172
|
-
super(data);
|
|
173
|
-
this.text = data.text ?? null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
rdfClass() {
|
|
177
|
-
return 'LetterSymbol';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
toJSON() {
|
|
181
|
-
const obj = super.toJSON();
|
|
182
|
-
if (this.text != null) obj.text = this.text;
|
|
183
|
-
return obj;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
static fromJSON(data) { return new LetterSymbol(data); }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
Designation.register('letter_symbol', LetterSymbol);
|
|
190
|
-
|
|
191
|
-
export class GraphicalSymbol extends Symbol {
|
|
192
|
-
constructor(data = {}) {
|
|
193
|
-
super(data);
|
|
194
|
-
this.text = data.text ?? null;
|
|
195
|
-
this.image = data.image ?? null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
rdfClass() {
|
|
199
|
-
return 'GraphicalSymbol';
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
toJSON() {
|
|
203
|
-
const obj = super.toJSON();
|
|
204
|
-
if (this.text != null) obj.text = this.text;
|
|
205
|
-
if (this.image != null) obj.image = this.image;
|
|
206
|
-
return obj;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
static fromJSON(data) { return new GraphicalSymbol(data); }
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
Designation.register('graphical symbol', GraphicalSymbol);
|
|
213
|
-
Designation.register('graphical_symbol', GraphicalSymbol);
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import * as yaml from 'js-yaml';
|
|
2
|
-
import { GlossaristModel } from './base.js';
|
|
3
|
-
import { GcrStatistics } from './gcr-statistics.js';
|
|
4
|
-
|
|
5
|
-
export class GcrMetadata extends GlossaristModel {
|
|
6
|
-
constructor(data = {}) {
|
|
7
|
-
super();
|
|
8
|
-
this.shortname = data.shortname ?? null;
|
|
9
|
-
this.version = data.version ?? null;
|
|
10
|
-
this.title = data.title ?? null;
|
|
11
|
-
this.description = data.description ?? null;
|
|
12
|
-
this.owner = data.owner ?? null;
|
|
13
|
-
this.tags = data.tags ?? [];
|
|
14
|
-
this.conceptCount = data.concept_count ?? data.conceptCount ?? 0;
|
|
15
|
-
this.languages = data.languages ?? [];
|
|
16
|
-
this.createdAt = data.created_at ?? data.createdAt ?? null;
|
|
17
|
-
this.glossaristVersion = data.glossarist_version ?? data.glossaristVersion ?? null;
|
|
18
|
-
this.schemaVersion = data.schema_version ?? data.schemaVersion ?? '1';
|
|
19
|
-
this.homepage = data.homepage ?? null;
|
|
20
|
-
this.repository = data.repository ?? null;
|
|
21
|
-
this.license = data.license ?? null;
|
|
22
|
-
this.uriPrefix = data.uri_prefix ?? data.uriPrefix ?? null;
|
|
23
|
-
this.conceptUriTemplate = data.concept_uri_template ?? data.conceptUriTemplate ?? null;
|
|
24
|
-
this.compiledFormats = data.compiled_formats ?? data.compiledFormats ?? [];
|
|
25
|
-
this.statistics = data.statistics
|
|
26
|
-
? (data.statistics instanceof GcrStatistics ? data.statistics : new GcrStatistics(data.statistics))
|
|
27
|
-
: null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get concept_count() { return this.conceptCount; }
|
|
31
|
-
get created_at() { return this.createdAt; }
|
|
32
|
-
get glossarist_version() { return this.glossaristVersion; }
|
|
33
|
-
get schema_version() { return this.schemaVersion; }
|
|
34
|
-
get uri_prefix() { return this.uriPrefix; }
|
|
35
|
-
get concept_uri_template() { return this.conceptUriTemplate; }
|
|
36
|
-
get compiled_formats() { return this.compiledFormats; }
|
|
37
|
-
|
|
38
|
-
toJSON() {
|
|
39
|
-
const obj = {};
|
|
40
|
-
if (this.shortname != null) obj.shortname = this.shortname;
|
|
41
|
-
if (this.version != null) obj.version = this.version;
|
|
42
|
-
if (this.title != null) obj.title = this.title;
|
|
43
|
-
if (this.description != null) obj.description = this.description;
|
|
44
|
-
if (this.owner != null) obj.owner = this.owner;
|
|
45
|
-
if (this.tags.length > 0) obj.tags = this.tags;
|
|
46
|
-
if (this.conceptCount > 0) obj.concept_count = this.conceptCount;
|
|
47
|
-
if (this.languages.length > 0) obj.languages = this.languages;
|
|
48
|
-
if (this.createdAt != null) obj.created_at = this.createdAt;
|
|
49
|
-
if (this.glossaristVersion != null) obj.glossarist_version = this.glossaristVersion;
|
|
50
|
-
if (this.schemaVersion != null) obj.schema_version = this.schemaVersion;
|
|
51
|
-
if (this.homepage != null) obj.homepage = this.homepage;
|
|
52
|
-
if (this.repository != null) obj.repository = this.repository;
|
|
53
|
-
if (this.license != null) obj.license = this.license;
|
|
54
|
-
if (this.uriPrefix != null) obj.uri_prefix = this.uriPrefix;
|
|
55
|
-
if (this.conceptUriTemplate != null) obj.concept_uri_template = this.conceptUriTemplate;
|
|
56
|
-
if (this.compiledFormats.length > 0) obj.compiled_formats = this.compiledFormats;
|
|
57
|
-
if (this.statistics != null) obj.statistics = this.statistics.toJSON();
|
|
58
|
-
return obj;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static fromJSON(data) {
|
|
62
|
-
return new GcrMetadata(data);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static fromYaml(yamlString) {
|
|
66
|
-
return new GcrMetadata(yaml.load(yamlString));
|
|
67
|
-
}
|
|
68
|
-
}
|