glossarist 0.2.1 → 0.3.0
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/README.md +4 -2
- package/package.json +1 -1
- package/src/concept-parser.js +27 -0
- package/src/concept-serializer.js +16 -0
- package/src/gcr-reader.js +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/models/citation.js +48 -29
- package/src/models/concept-date.js +1 -1
- package/src/models/concept-ref.js +25 -0
- package/src/models/concept-reference.js +36 -0
- package/src/models/concept.js +23 -2
- package/src/models/designation.js +69 -14
- package/src/models/grammar-info.js +40 -0
- package/src/models/index.d.ts +117 -22
- package/src/models/index.js +7 -2
- package/src/models/locality.js +22 -0
- package/src/models/localized-concept.js +113 -10
- package/src/models/non-verb-rep.js +8 -8
- package/src/models/pronunciation.js +26 -0
- package/src/models/related-concept.js +25 -6
- package/src/reference-resolver.js +11 -1
- package/src/validators/index.js +26 -1
- package/src/validators/v3-rules.js +216 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { ValidationRule } from './validation-rule.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GLS-305: Enforces Citation#ref is a proper Citation.Ref object
|
|
5
|
+
* and RelatedConcept#ref has at least source or id.
|
|
6
|
+
*/
|
|
7
|
+
export class RefShapeRule extends ValidationRule {
|
|
8
|
+
constructor() { super('ref-shape'); }
|
|
9
|
+
|
|
10
|
+
validate(value, path) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
|
|
13
|
+
// Check sources in localizations
|
|
14
|
+
const localizations = value.localizations || {};
|
|
15
|
+
let sourceIdx = 0;
|
|
16
|
+
for (const [lang, lc] of Object.entries(localizations)) {
|
|
17
|
+
const sources = lc.sources || [];
|
|
18
|
+
for (let i = 0; i < sources.length; i++) {
|
|
19
|
+
sourceIdx++;
|
|
20
|
+
const origin = sources[i].origin;
|
|
21
|
+
if (!origin) continue;
|
|
22
|
+
|
|
23
|
+
const ref = origin.ref;
|
|
24
|
+
if (!ref) {
|
|
25
|
+
errors.push(...this.error(
|
|
26
|
+
`${path}localizations.${lang}.sources[${i}].origin.ref`,
|
|
27
|
+
`source ${sourceIdx} origin has nil ref (expected Citation.Ref hash)`,
|
|
28
|
+
));
|
|
29
|
+
} else if (!ref.source && !ref.id) {
|
|
30
|
+
errors.push(...this.error(
|
|
31
|
+
`${path}localizations.${lang}.sources[${i}].origin.ref`,
|
|
32
|
+
`source ${sourceIdx} origin.ref has neither source nor id`,
|
|
33
|
+
));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check related concepts
|
|
39
|
+
const related = value.related || [];
|
|
40
|
+
for (let i = 0; i < related.length; i++) {
|
|
41
|
+
const ref = related[i].ref;
|
|
42
|
+
if (!ref) continue;
|
|
43
|
+
if (!ref.source && !ref.id) {
|
|
44
|
+
errors.push(...this.error(
|
|
45
|
+
`${path}related[${i}].ref`,
|
|
46
|
+
`related concept ${i + 1} has empty ref (no source or id)`,
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* GLS-308: Locality must have type and reference_from when present.
|
|
57
|
+
*/
|
|
58
|
+
export class LocalityCompletenessRule extends ValidationRule {
|
|
59
|
+
constructor() { super('locality-completeness', 'warning'); }
|
|
60
|
+
|
|
61
|
+
validate(value, path) {
|
|
62
|
+
const errors = [];
|
|
63
|
+
const localizations = value.localizations || {};
|
|
64
|
+
|
|
65
|
+
for (const [lang, lc] of Object.entries(localizations)) {
|
|
66
|
+
const sources = lc.sources || [];
|
|
67
|
+
for (let i = 0; i < sources.length; i++) {
|
|
68
|
+
const origin = sources[i].origin;
|
|
69
|
+
if (!origin || !origin.locality) continue;
|
|
70
|
+
|
|
71
|
+
const loc = origin.locality;
|
|
72
|
+
if (!loc.type) {
|
|
73
|
+
errors.push(...this.error(
|
|
74
|
+
`${path}localizations.${lang}.sources[${i}].origin.locality.type`,
|
|
75
|
+
`source locality has no type`,
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
if (!loc.reference_from) {
|
|
79
|
+
errors.push(...this.error(
|
|
80
|
+
`${path}localizations.${lang}.sources[${i}].origin.locality.reference_from`,
|
|
81
|
+
`source locality has no reference_from`,
|
|
82
|
+
));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* GLS-017: Localization map consistency.
|
|
93
|
+
*/
|
|
94
|
+
export class LocalizationConsistencyRule extends ValidationRule {
|
|
95
|
+
constructor() { super('localization-consistency'); }
|
|
96
|
+
|
|
97
|
+
validate(value, path) {
|
|
98
|
+
const errors = [];
|
|
99
|
+
const localizations = value.localizations || {};
|
|
100
|
+
const data = value.raw?.data || value;
|
|
101
|
+
|
|
102
|
+
const declaredLangs = data.localized_concepts
|
|
103
|
+
? Object.keys(data.localized_concepts)
|
|
104
|
+
: Object.keys(localizations);
|
|
105
|
+
|
|
106
|
+
for (const lang of declaredLangs) {
|
|
107
|
+
if (!localizations[lang]) {
|
|
108
|
+
errors.push(...this.error(
|
|
109
|
+
`${path}localizations.${lang}`,
|
|
110
|
+
`localized_concepts map has '${lang}' but no localization loaded`,
|
|
111
|
+
));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return errors;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* GLS-010: Schema version should be 3.
|
|
121
|
+
*/
|
|
122
|
+
export class SchemaVersionRule extends ValidationRule {
|
|
123
|
+
constructor() { super('schema-version', 'warning'); }
|
|
124
|
+
|
|
125
|
+
validate(value, path) {
|
|
126
|
+
const errors = [];
|
|
127
|
+
const version = value.schemaVersion || value.schema_version;
|
|
128
|
+
|
|
129
|
+
if (version && String(version) !== '3') {
|
|
130
|
+
errors.push(...this.error(
|
|
131
|
+
`${path}schema_version`,
|
|
132
|
+
`schema_version is '${version}', expected '3'`,
|
|
133
|
+
));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* GLS-309: Domain references need concept_id or urn.
|
|
142
|
+
*/
|
|
143
|
+
export class DomainRefRule extends ValidationRule {
|
|
144
|
+
constructor() { super('domain-ref', 'warning'); }
|
|
145
|
+
|
|
146
|
+
validate(value, path) {
|
|
147
|
+
const errors = [];
|
|
148
|
+
const domains = value.domains || [];
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < domains.length; i++) {
|
|
151
|
+
const domain = domains[i];
|
|
152
|
+
if (!domain.concept_id && !domain.urn) {
|
|
153
|
+
errors.push(...this.error(
|
|
154
|
+
`${path}domains[${i}]`,
|
|
155
|
+
`domain ${i + 1} has neither concept_id nor urn`,
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return errors;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* GLS-016: UUID format validation.
|
|
166
|
+
*/
|
|
167
|
+
export class UuidFormatRule extends ValidationRule {
|
|
168
|
+
constructor() { super('uuid-format'); }
|
|
169
|
+
|
|
170
|
+
validate(value, path) {
|
|
171
|
+
const errors = [];
|
|
172
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
173
|
+
const id = value.id || value.uuid;
|
|
174
|
+
|
|
175
|
+
if (id && !UUID_RE.test(String(id))) {
|
|
176
|
+
// Only flag if it looks like it's supposed to be a UUID
|
|
177
|
+
if (String(id).includes('-') && String(id).length > 20) {
|
|
178
|
+
errors.push(...this.error(
|
|
179
|
+
`${path}id`,
|
|
180
|
+
`concept ID '${id}' is not valid UUID format`,
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return errors;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* GLS-310: URN format validation for sources.
|
|
191
|
+
*/
|
|
192
|
+
export class SourceUrnFormatRule extends ValidationRule {
|
|
193
|
+
constructor() { super('source-urn-format', 'warning'); }
|
|
194
|
+
|
|
195
|
+
validate(value, path) {
|
|
196
|
+
const errors = [];
|
|
197
|
+
const URN_RE = /^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]+$/i;
|
|
198
|
+
|
|
199
|
+
const localizations = value.localizations || {};
|
|
200
|
+
for (const [lang, lc] of Object.entries(localizations)) {
|
|
201
|
+
const sources = lc.sources || [];
|
|
202
|
+
for (let i = 0; i < sources.length; i++) {
|
|
203
|
+
const source = sources[i].origin?.ref?.source;
|
|
204
|
+
if (!source || !source.startsWith('urn:')) continue;
|
|
205
|
+
if (!URN_RE.test(source)) {
|
|
206
|
+
errors.push(...this.error(
|
|
207
|
+
`${path}localizations.${lang}.sources[${i}].origin.ref.source`,
|
|
208
|
+
`malformed URN '${source}'`,
|
|
209
|
+
));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return errors;
|
|
215
|
+
}
|
|
216
|
+
}
|