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.
@@ -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
- }