glossarist 0.3.3 → 0.3.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "glossarist",
3
- "version": "0.3.3",
4
- "description": "JavaScript SDK for Glossarist GCR packages \u2014 read, write, validate, and manage terminology concepts",
3
+ "version": "0.3.4",
4
+ "description": "JavaScript SDK for Glossarist GCR packages read, write, validate, and manage terminology concepts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "types": "src/index.d.ts",
@@ -62,14 +62,22 @@ export class ConceptCollection {
62
62
  for (const t of lc.terms) {
63
63
  if ((t.designation ?? '').toLowerCase().includes(q)) return true;
64
64
  }
65
- for (const d of lc.definitions) {
66
- if ((d.content ?? '').toLowerCase().includes(q)) return true;
67
- }
65
+ if (this._searchDefs(lc.definitions, q)) return true;
66
+ if (this._searchDefs(lc.notes, q)) return true;
67
+ if (this._searchDefs(lc.examples, q)) return true;
68
+ if (this._searchDefs(lc.annotations, q)) return true;
68
69
  }
69
70
  return false;
70
71
  }));
71
72
  }
72
73
 
74
+ _searchDefs(arr, q) {
75
+ for (const d of arr) {
76
+ if ((d.content ?? '').toLowerCase().includes(q)) return true;
77
+ }
78
+ return false;
79
+ }
80
+
73
81
  allLanguages() {
74
82
  const set = new Set();
75
83
  for (const c of this[_items]) {
@@ -1,6 +1,5 @@
1
1
  import yaml from 'js-yaml';
2
2
  import { Concept } from './models/concept.js';
3
- import { ConceptRef } from './models/concept-ref.js';
4
3
  import { RelatedConcept } from './models/related-concept.js';
5
4
  import { InvalidInputError, YamlParseError } from './errors.js';
6
5
 
@@ -88,17 +87,13 @@ function _normalizeRelated(arr) {
88
87
  if (!arr || !Array.isArray(arr)) return [];
89
88
  return arr.map(r => {
90
89
  if (r instanceof RelatedConcept) return r;
91
- const data = { ...r };
92
- if (data.ref) {
93
- if (typeof data.ref !== 'object' || data.ref === null) {
94
- throw new InvalidInputError(
95
- `RelatedConcept.ref must be an object { source, id }, got: ${typeof data.ref}`,
96
- 'object',
97
- );
98
- }
99
- data.ref = new ConceptRef(data.ref);
90
+ if (r.ref != null && typeof r.ref !== 'object') {
91
+ throw new InvalidInputError(
92
+ `RelatedConcept.ref must be an object { source, id }, got: ${typeof r.ref}`,
93
+ 'object',
94
+ );
100
95
  }
101
- return new RelatedConcept(data);
96
+ return new RelatedConcept(r);
102
97
  });
103
98
  }
104
99
 
package/src/index.js CHANGED
@@ -32,8 +32,8 @@ export {
32
32
  REGISTER_STATUSES, ORDERING_METHODS,
33
33
  Concept, LocalizedConcept,
34
34
  Designation, Expression, Abbreviation, Symbol, GraphicalSymbol,
35
- Citation, ConceptRef, ConceptSource, RelatedConcept, ConceptReference, ConceptDate,
35
+ Citation, ConceptRef, ConceptSource, RelatedConcept, DesignationRelationship, ConceptReference, ConceptDate,
36
36
  DetailedDefinition, NonVerbRep,
37
37
  GcrMetadata, GcrStatistics,
38
- RELATIONSHIP_TYPES, DATE_TYPES,
38
+ RELATIONSHIP_TYPES, DESIGNATION_RELATIONSHIP_TYPES, DATE_TYPES,
39
39
  } from './models/index.js';
@@ -5,17 +5,24 @@ export class ConceptRef extends GlossaristModel {
5
5
  super();
6
6
  this.source = data.source ?? null;
7
7
  this.id = data.id ?? null;
8
+ this.text = data.text ?? null;
8
9
  }
9
10
 
10
11
  toString() {
11
- if (this.source && this.id) return `${this.source} ${this.id}`;
12
- return this.source ?? this.id ?? '';
12
+ const parts = [];
13
+ if (this.source) parts.push(this.source);
14
+ if (this.id) parts.push(this.id);
15
+ const base = parts.join(' ');
16
+ if (this.text && base) return `${base} (${this.text})`;
17
+ if (this.text) return this.text;
18
+ return base;
13
19
  }
14
20
 
15
21
  toJSON() {
16
22
  const obj = {};
17
23
  if (this.source != null) obj.source = this.source;
18
24
  if (this.id != null) obj.id = this.id;
25
+ if (this.text != null) obj.text = this.text;
19
26
  return obj;
20
27
  }
21
28
 
@@ -0,0 +1,27 @@
1
+ import { GlossaristModel } from './base.js';
2
+
3
+ export const DESIGNATION_RELATIONSHIP_TYPES = Object.freeze([
4
+ // TBX (ISO 30042) / ISO 12620 term-level relationships
5
+ 'abbreviated_form_for', 'short_form_for',
6
+ ]);
7
+
8
+ export class DesignationRelationship extends GlossaristModel {
9
+ constructor(data = {}) {
10
+ super();
11
+ this.type = data.type ?? null;
12
+ this.content = data.content ?? null;
13
+ this.target = data.target ?? null;
14
+ }
15
+
16
+ toJSON() {
17
+ const obj = {};
18
+ if (this.type != null) obj.type = this.type;
19
+ if (this.content != null) obj.content = this.content;
20
+ if (this.target != null) obj.target = this.target;
21
+ return obj;
22
+ }
23
+
24
+ static fromJSON(data) {
25
+ return new DesignationRelationship(data);
26
+ }
27
+ }
@@ -3,6 +3,7 @@ import { ConceptSource } from './concept-source.js';
3
3
  import { Pronunciation } from './pronunciation.js';
4
4
  import { GrammarInfo } from './grammar-info.js';
5
5
  import { RelatedConcept } from './related-concept.js';
6
+ import { DesignationRelationship, DESIGNATION_RELATIONSHIP_TYPES } from './designation-relationship.js';
6
7
 
7
8
  export class Designation extends GlossaristModel {
8
9
  static _registry = new Map();
@@ -38,7 +39,11 @@ export class Designation extends GlossaristModel {
38
39
  s => s instanceof ConceptSource ? s : new ConceptSource(s)
39
40
  );
40
41
  this.related = (data.related ?? []).map(
41
- r => r instanceof RelatedConcept ? r : new RelatedConcept(r)
42
+ r => (r instanceof DesignationRelationship || r instanceof RelatedConcept)
43
+ ? r
44
+ : DESIGNATION_RELATIONSHIP_TYPES.includes(r.type)
45
+ ? DesignationRelationship.fromJSON(r)
46
+ : RelatedConcept.fromJSON(r)
42
47
  );
43
48
  }
44
49
 
@@ -88,6 +88,7 @@ export class LocalizedConcept extends GlossaristModel {
88
88
  readonly definitions: DetailedDefinition[];
89
89
  readonly definition: DetailedDefinition[];
90
90
  readonly notes: DetailedDefinition[];
91
+ readonly annotations: DetailedDefinition[];
91
92
  readonly examples: DetailedDefinition[];
92
93
  readonly sources: ConceptSource[];
93
94
  readonly dates: ConceptDate[];
@@ -113,7 +114,7 @@ export class Designation extends GlossaristModel {
113
114
  readonly termType: string | null;
114
115
  readonly pronunciations: Pronunciation[];
115
116
  readonly sources: ConceptSource[];
116
- readonly related: RelatedConcept[];
117
+ readonly related: (RelatedConcept | DesignationRelationship)[];
117
118
  static register(type: string, cls: typeof Designation): void;
118
119
  static fromData(data: Record<string, unknown>): Designation;
119
120
  static fromJSON(data: Record<string, unknown>): Designation;
@@ -194,6 +195,7 @@ export namespace Citation {
194
195
  export class ConceptRef extends GlossaristModel {
195
196
  readonly source: string | null;
196
197
  readonly id: string | null;
198
+ readonly text: string | null;
197
199
  toString(): string;
198
200
  static fromJSON(data: Record<string, unknown>): ConceptRef;
199
201
  }
@@ -212,6 +214,14 @@ export class RelatedConcept extends GlossaristModel {
212
214
  readonly ref: ConceptRef | null;
213
215
  }
214
216
 
217
+ export const DESIGNATION_RELATIONSHIP_TYPES: readonly string[];
218
+ export class DesignationRelationship extends GlossaristModel {
219
+ readonly type: string | null;
220
+ readonly content: string | null;
221
+ readonly target: string | null;
222
+ static fromJSON(data: Record<string, unknown>): DesignationRelationship;
223
+ }
224
+
215
225
  export class ConceptReference extends GlossaristModel {
216
226
  readonly conceptId: string | null;
217
227
  readonly refType: string | null;
@@ -8,6 +8,7 @@ export { Citation } from './citation.js';
8
8
  export { ConceptRef } from './concept-ref.js';
9
9
  export { ConceptSource } from './concept-source.js';
10
10
  export { RelatedConcept, RELATIONSHIP_TYPES } from './related-concept.js';
11
+ export { DesignationRelationship, DESIGNATION_RELATIONSHIP_TYPES } from './designation-relationship.js';
11
12
  export { ConceptReference } from './concept-reference.js';
12
13
  export { ConceptDate, DATE_TYPES } from './concept-date.js';
13
14
  export { DetailedDefinition } from './detailed-definition.js';
@@ -6,6 +6,10 @@ import { ConceptDate } from './concept-date.js';
6
6
  import { NonVerbRep } from './non-verb-rep.js';
7
7
  import { RelatedConcept } from './related-concept.js';
8
8
 
9
+ function wrapAs(Cls) {
10
+ return item => item instanceof Cls ? item : new Cls(item);
11
+ }
12
+
9
13
  export class LocalizedConcept extends GlossaristModel {
10
14
  constructor(data = {}) {
11
15
  super();
@@ -30,6 +34,7 @@ export class LocalizedConcept extends GlossaristModel {
30
34
  this._rawDefinition = data.definition ?? [];
31
35
  this._rawSources = data.sources ?? [];
32
36
  this._rawNotes = data.notes ?? [];
37
+ this._rawAnnotations = data.annotations ?? [];
33
38
  this._rawExamples = data.examples ?? [];
34
39
  this._rawDates = data.dates ?? [];
35
40
  this._rawNonVerbal = data.non_verbal_rep ?? data.non_verb ?? [];
@@ -39,6 +44,7 @@ export class LocalizedConcept extends GlossaristModel {
39
44
  this._definitions = null;
40
45
  this._sources = null;
41
46
  this._notes = null;
47
+ this._annotations = null;
42
48
  this._examples = null;
43
49
  this._dates = null;
44
50
  this._nonVerbal = null;
@@ -46,19 +52,11 @@ export class LocalizedConcept extends GlossaristModel {
46
52
  }
47
53
 
48
54
  get terms() {
49
- if (this._terms === null) {
50
- this._terms = this._rawTerms.map(t => Designation.fromData(t));
51
- }
52
- return this._terms;
55
+ return this._lazy('_terms', '_rawTerms', t => Designation.fromData(t));
53
56
  }
54
57
 
55
58
  get definitions() {
56
- if (this._definitions === null) {
57
- this._definitions = this._rawDefinition.map(
58
- d => d instanceof DetailedDefinition ? d : new DetailedDefinition(d)
59
- );
60
- }
61
- return this._definitions;
59
+ return this._lazy('_definitions', '_rawDefinition', wrapAs(DetailedDefinition));
62
60
  }
63
61
 
64
62
  get definition() {
@@ -66,57 +64,31 @@ export class LocalizedConcept extends GlossaristModel {
66
64
  }
67
65
 
68
66
  get sources() {
69
- if (this._sources === null) {
70
- this._sources = this._rawSources.map(
71
- s => s instanceof ConceptSource ? s : new ConceptSource(s)
72
- );
73
- }
74
- return this._sources;
67
+ return this._lazy('_sources', '_rawSources', wrapAs(ConceptSource));
75
68
  }
76
69
 
77
70
  get notes() {
78
- if (this._notes === null) {
79
- this._notes = this._rawNotes.map(
80
- n => n instanceof DetailedDefinition ? n : new DetailedDefinition(n)
81
- );
82
- }
83
- return this._notes;
71
+ return this._lazy('_notes', '_rawNotes', wrapAs(DetailedDefinition));
72
+ }
73
+
74
+ get annotations() {
75
+ return this._lazy('_annotations', '_rawAnnotations', wrapAs(DetailedDefinition));
84
76
  }
85
77
 
86
78
  get examples() {
87
- if (this._examples === null) {
88
- this._examples = this._rawExamples.map(
89
- e => e instanceof DetailedDefinition ? e : new DetailedDefinition(e)
90
- );
91
- }
92
- return this._examples;
79
+ return this._lazy('_examples', '_rawExamples', wrapAs(DetailedDefinition));
93
80
  }
94
81
 
95
82
  get dates() {
96
- if (this._dates === null) {
97
- this._dates = this._rawDates.map(
98
- d => d instanceof ConceptDate ? d : new ConceptDate(d)
99
- );
100
- }
101
- return this._dates;
83
+ return this._lazy('_dates', '_rawDates', wrapAs(ConceptDate));
102
84
  }
103
85
 
104
86
  get nonVerbalRep() {
105
- if (this._nonVerbal === null) {
106
- this._nonVerbal = this._rawNonVerbal.map(
107
- n => n instanceof NonVerbRep ? n : new NonVerbRep(n)
108
- );
109
- }
110
- return this._nonVerbal;
87
+ return this._lazy('_nonVerbal', '_rawNonVerbal', wrapAs(NonVerbRep));
111
88
  }
112
89
 
113
90
  get related() {
114
- if (this._related === null) {
115
- this._related = this._rawRelated.map(
116
- r => r instanceof RelatedConcept ? r : new RelatedConcept(r)
117
- );
118
- }
119
- return this._related;
91
+ return this._lazy('_related', '_rawRelated', wrapAs(RelatedConcept));
120
92
  }
121
93
 
122
94
  get primaryDesignation() {
@@ -146,47 +118,31 @@ export class LocalizedConcept extends GlossaristModel {
146
118
  if (this.reviewDecision) obj.review_decision = this.reviewDecision;
147
119
  if (this.reviewDecisionNotes) obj.review_decision_notes = this.reviewDecisionNotes;
148
120
 
149
- const terms = this._terms ?? this._rawTerms;
150
- if (terms.length > 0) {
151
- obj.terms = terms.map(t => (typeof t.toJSON === 'function') ? t.toJSON() : t);
152
- }
121
+ this._serialize(obj, 'terms', '_terms', '_rawTerms');
122
+ this._serialize(obj, 'definition', '_definitions', '_rawDefinition');
123
+ this._serialize(obj, 'notes', '_notes', '_rawNotes');
124
+ this._serialize(obj, 'annotations', '_annotations', '_rawAnnotations');
125
+ this._serialize(obj, 'examples', '_examples', '_rawExamples');
126
+ this._serialize(obj, 'sources', '_sources', '_rawSources');
127
+ this._serialize(obj, 'dates', '_dates', '_rawDates');
128
+ this._serialize(obj, 'non_verbal_rep', '_nonVerbal', '_rawNonVerbal');
129
+ this._serialize(obj, 'related', '_related', '_rawRelated');
153
130
 
154
- const defs = this._definitions ?? (this._rawDefinition.length > 0 ? this._rawDefinition : []);
155
- if (defs.length > 0) {
156
- obj.definition = defs.map(d => (typeof d.toJSON === 'function') ? d.toJSON() : d);
157
- }
158
-
159
- const notes = this._notes ?? (this._rawNotes.length > 0 ? this._rawNotes : []);
160
- if (notes.length > 0) {
161
- obj.notes = notes.map(n => (typeof n.toJSON === 'function') ? n.toJSON() : n);
162
- }
163
-
164
- const examples = this._examples ?? (this._rawExamples.length > 0 ? this._rawExamples : []);
165
- if (examples.length > 0) {
166
- obj.examples = examples.map(e => (typeof e.toJSON === 'function') ? e.toJSON() : e);
167
- }
168
-
169
- const sources = this._sources ?? (this._rawSources.length > 0 ? this._rawSources : []);
170
- if (sources.length > 0) {
171
- obj.sources = sources.map(s => (typeof s.toJSON === 'function') ? s.toJSON() : s);
172
- }
173
-
174
- const dates = this._dates ?? (this._rawDates.length > 0 ? this._rawDates : []);
175
- if (dates.length > 0) {
176
- obj.dates = dates.map(d => (typeof d.toJSON === 'function') ? d.toJSON() : d);
177
- }
131
+ return obj;
132
+ }
178
133
 
179
- const nonVerbal = this._nonVerbal ?? (this._rawNonVerbal.length > 0 ? this._rawNonVerbal : []);
180
- if (nonVerbal.length > 0) {
181
- obj.non_verbal_rep = nonVerbal.map(n => (typeof n.toJSON === 'function') ? n.toJSON() : n);
134
+ _lazy(cacheKey, rawKey, wrapFn) {
135
+ if (this[cacheKey] === null) {
136
+ this[cacheKey] = this[rawKey].map(wrapFn);
182
137
  }
138
+ return this[cacheKey];
139
+ }
183
140
 
184
- const related = this._related ?? (this._rawRelated.length > 0 ? this._rawRelated : []);
185
- if (related.length > 0) {
186
- obj.related = related.map(r => (typeof r.toJSON === 'function') ? r.toJSON() : r);
141
+ _serialize(obj, jsonKey, cacheKey, rawKey) {
142
+ const items = this[cacheKey] ?? (this[rawKey].length > 0 ? this[rawKey] : []);
143
+ if (items.length > 0) {
144
+ obj[jsonKey] = items.map(i => (typeof i.toJSON === 'function') ? i.toJSON() : i);
187
145
  }
188
-
189
- return obj;
190
146
  }
191
147
 
192
148
  static fromJSON(data) {
@@ -31,8 +31,6 @@ export const RELATIONSHIP_TYPES = Object.freeze([
31
31
  'sequentially_related_concept', 'spatially_related_concept', 'temporally_related_concept',
32
32
  // Lexical (ISO 12620 / TBX)
33
33
  'homograph', 'false_friend',
34
- // Designation-level
35
- 'abbreviated_form_for', 'short_form_for',
36
34
  ]);
37
35
 
38
36
  export class RelatedConcept extends GlossaristModel {
@@ -4,6 +4,7 @@ export { ValidationResult } from './validation-result.js';
4
4
  export { ConceptValidator, LanguageCodeRule, DesignationTypeRule, EntryStatusRule } from './concept-validator.js';
5
5
  export { RegisterValidator } from './register-validator.js';
6
6
  export { GcrValidator } from './gcr-validator.js';
7
+ export { RelationshipTypeRule } from './relationship-type-rule.js';
7
8
  export {
8
9
  RefShapeRule,
9
10
  LocalityCompletenessRule,
@@ -17,6 +18,7 @@ export {
17
18
  import { ConceptValidator, LanguageCodeRule, DesignationTypeRule, EntryStatusRule } from './concept-validator.js';
18
19
  import { RegisterValidator } from './register-validator.js';
19
20
  import { GcrValidator } from './gcr-validator.js';
21
+ import { RelationshipTypeRule } from './relationship-type-rule.js';
20
22
  import {
21
23
  RefShapeRule,
22
24
  LocalityCompletenessRule,
@@ -37,7 +39,8 @@ const _default = new ConceptValidator()
37
39
  .addRule(new SchemaVersionRule())
38
40
  .addRule(new DomainRefRule())
39
41
  .addRule(new UuidFormatRule())
40
- .addRule(new SourceUrnFormatRule());
42
+ .addRule(new SourceUrnFormatRule())
43
+ .addRule(new RelationshipTypeRule());
41
44
 
42
45
  export function validateConcept(concept) {
43
46
  return _default.validate(concept);
@@ -0,0 +1,40 @@
1
+ import { ValidationRule } from './validation-rule.js';
2
+ import { RELATIONSHIP_TYPES } from '../models/related-concept.js';
3
+ import { DESIGNATION_RELATIONSHIP_TYPES } from '../models/designation-relationship.js';
4
+
5
+ const KNOWN_CONCEPT_TYPES = new Set(RELATIONSHIP_TYPES);
6
+ const KNOWN_DESIGNATION_TYPES = new Set(DESIGNATION_RELATIONSHIP_TYPES);
7
+
8
+ export class RelationshipTypeRule extends ValidationRule {
9
+ constructor() { super('relationship-type', 'warning'); }
10
+
11
+ validate(value, path) {
12
+ const errors = [];
13
+ this._checkRelated(value.related, `${path}related`, KNOWN_CONCEPT_TYPES, errors);
14
+
15
+ if (value.localizations) {
16
+ for (const [lang, lc] of Object.entries(value.localizations)) {
17
+ this._checkRelated(lc.related, `${path}localizations.${lang}.related`, KNOWN_CONCEPT_TYPES, errors);
18
+
19
+ if (lc.terms) {
20
+ for (let ti = 0; ti < lc.terms.length; ti++) {
21
+ this._checkRelated(lc.terms[ti]?.related,
22
+ `${path}localizations.${lang}.terms[${ti}].related`, KNOWN_DESIGNATION_TYPES, errors);
23
+ }
24
+ }
25
+ }
26
+ }
27
+ return errors;
28
+ }
29
+
30
+ _checkRelated(arr, basePath, knownTypes, errors) {
31
+ if (!arr) return;
32
+ for (let i = 0; i < arr.length; i++) {
33
+ const type = arr[i]?.type;
34
+ if (type && !knownTypes.has(type)) {
35
+ errors.push(...this.error(`${basePath}[${i}].type`,
36
+ `Unknown relationship type '${type}'`));
37
+ }
38
+ }
39
+ }
40
+ }