glossarist 0.3.7 → 0.4.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/package.json +1 -1
- package/src/concept-parser.js +7 -1
- package/src/concept-reader.js +1 -1
- package/src/dataset-asset.js +3 -0
- package/src/entity-directory.js +43 -0
- package/src/gcr-reader.js +53 -5
- package/src/gcr-writer.js +3 -1
- package/src/index.d.ts +40 -7
- package/src/index.js +19 -2
- package/src/managed-concept-collection.js +11 -2
- package/src/models/base.js +14 -0
- package/src/models/bibliography-data.js +43 -0
- package/src/models/bibliography-entry.js +26 -0
- package/src/models/concept.js +27 -5
- package/src/models/designation.js +2 -14
- package/src/models/figure.js +71 -0
- package/src/models/formula.js +21 -0
- package/src/models/index.js +12 -1
- package/src/models/localized-concept.js +0 -14
- package/src/models/localized-string.js +19 -0
- package/src/models/non-verb-rep.js +15 -13
- package/src/models/non-verbal-entity.js +39 -0
- package/src/models/non-verbal-reference.js +35 -0
- package/src/models/non-verbal-references.js +16 -0
- package/src/models/registrable.js +25 -0
- package/src/models/shared-non-verbal-entity.js +28 -0
- package/src/models/table.js +21 -0
- package/src/reference-mention.js +19 -3
- package/src/reference-resolver.js +135 -184
- package/src/render-classification.js +19 -50
- package/src/validators/asset-index.js +66 -0
- package/src/validators/concept-validator.js +18 -33
- package/src/validators/index.js +5 -1
- package/src/validators/v3-rules.js +179 -43
|
@@ -9,18 +9,13 @@ const VALID_ENTRY_STATUSES = new Set([
|
|
|
9
9
|
'valid', 'draft', 'retired', 'notValid', 'superseded', 'withdrawn',
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
|
-
const _langs = (c) =>
|
|
13
|
-
c.languages ?? (c.localizations ? Object.keys(c.localizations) : []);
|
|
14
|
-
|
|
15
|
-
const _loc = (c, lang) =>
|
|
16
|
-
typeof c.localization === 'function' ? c.localization(lang) : c.localizations?.[lang];
|
|
17
|
-
|
|
18
12
|
export class LanguageCodeRule extends ValidationRule {
|
|
19
13
|
constructor() { super('language-code'); }
|
|
20
14
|
validate(concept, path, result) {
|
|
21
|
-
for (const lang of
|
|
15
|
+
for (const lang of concept.languages) {
|
|
22
16
|
if (!/^[a-z]{3}$/.test(lang)) {
|
|
23
|
-
|
|
17
|
+
this.addIssue(result,
|
|
18
|
+
`${path}localizations.${lang}`,
|
|
24
19
|
`Invalid language code '${lang}': expected ISO 639-3 (3 lowercase letters)`);
|
|
25
20
|
}
|
|
26
21
|
}
|
|
@@ -30,15 +25,14 @@ export class LanguageCodeRule extends ValidationRule {
|
|
|
30
25
|
export class DesignationTypeRule extends ValidationRule {
|
|
31
26
|
constructor() { super('designation-type'); }
|
|
32
27
|
validate(concept, path, result) {
|
|
33
|
-
for (const lang of
|
|
34
|
-
const lc =
|
|
28
|
+
for (const lang of concept.languages) {
|
|
29
|
+
const lc = concept.localization(lang);
|
|
35
30
|
if (!lc) continue;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const t = terms[i];
|
|
39
|
-
const type = t.type ?? (typeof t.toJSON === 'function' ? t.toJSON().type : undefined);
|
|
31
|
+
for (let i = 0; i < lc.terms.length; i++) {
|
|
32
|
+
const type = lc.terms[i].type;
|
|
40
33
|
if (type && !VALID_DESIGNATION_TYPES.has(type)) {
|
|
41
|
-
|
|
34
|
+
this.addIssue(result,
|
|
35
|
+
`${path}localizations.${lang}.terms[${i}].type`,
|
|
42
36
|
`Unknown designation type '${type}'`);
|
|
43
37
|
}
|
|
44
38
|
}
|
|
@@ -49,13 +43,13 @@ export class DesignationTypeRule extends ValidationRule {
|
|
|
49
43
|
export class EntryStatusRule extends ValidationRule {
|
|
50
44
|
constructor() { super('entry-status'); }
|
|
51
45
|
validate(concept, path, result) {
|
|
52
|
-
for (const lang of
|
|
53
|
-
const lc =
|
|
46
|
+
for (const lang of concept.languages) {
|
|
47
|
+
const lc = concept.localization(lang);
|
|
54
48
|
if (!lc) continue;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`Unknown entry status '${
|
|
49
|
+
if (lc.entryStatus && !VALID_ENTRY_STATUSES.has(lc.entryStatus)) {
|
|
50
|
+
this.addIssue(result,
|
|
51
|
+
`${path}localizations.${lang}.entry_status`,
|
|
52
|
+
`Unknown entry status '${lc.entryStatus}'`);
|
|
59
53
|
}
|
|
60
54
|
}
|
|
61
55
|
}
|
|
@@ -71,30 +65,21 @@ export class ConceptValidator {
|
|
|
71
65
|
|
|
72
66
|
validate(concept) {
|
|
73
67
|
const result = new ValidationResult();
|
|
74
|
-
const hasModelApi = typeof concept.localization === 'function';
|
|
75
68
|
|
|
76
69
|
if (!concept.id) {
|
|
77
70
|
result.addError('id', 'Concept must have an id');
|
|
78
71
|
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
if (langs.length === 0) {
|
|
73
|
+
if (concept.languages.length === 0) {
|
|
82
74
|
result.addWarning('localizations', 'Concept must have at least one localization');
|
|
83
|
-
} else
|
|
84
|
-
for (const lang of
|
|
75
|
+
} else {
|
|
76
|
+
for (const lang of concept.languages) {
|
|
85
77
|
const lc = concept.localization(lang);
|
|
86
78
|
if (!lc || lc.terms.length === 0) {
|
|
87
79
|
result.addWarning(`localizations.${lang}.terms`,
|
|
88
80
|
`Localization '${lang}' must have at least one term`);
|
|
89
81
|
}
|
|
90
82
|
}
|
|
91
|
-
} else {
|
|
92
|
-
for (const [lang, lc] of Object.entries(concept.localizations ?? {})) {
|
|
93
|
-
if (!lc.terms || lc.terms.length === 0) {
|
|
94
|
-
result.addWarning(`localizations.${lang}.terms`,
|
|
95
|
-
`Localization '${lang}' must have at least one term`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
83
|
}
|
|
99
84
|
|
|
100
85
|
for (const rule of this._rules) {
|
package/src/validators/index.js
CHANGED
|
@@ -14,6 +14,8 @@ export {
|
|
|
14
14
|
UuidFormatRule,
|
|
15
15
|
SourceUrnFormatRule,
|
|
16
16
|
CiteRefIntegrityRule,
|
|
17
|
+
NonVerbalRefIntegrityRule,
|
|
18
|
+
OrphanedImagesRule,
|
|
17
19
|
} from './v3-rules.js';
|
|
18
20
|
|
|
19
21
|
import { ConceptValidator, LanguageCodeRule, DesignationTypeRule, EntryStatusRule } from './concept-validator.js';
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
UuidFormatRule,
|
|
30
32
|
SourceUrnFormatRule,
|
|
31
33
|
CiteRefIntegrityRule,
|
|
34
|
+
NonVerbalRefIntegrityRule,
|
|
32
35
|
} from './v3-rules.js';
|
|
33
36
|
|
|
34
37
|
const _default = new ConceptValidator()
|
|
@@ -43,7 +46,8 @@ const _default = new ConceptValidator()
|
|
|
43
46
|
.addRule(new UuidFormatRule())
|
|
44
47
|
.addRule(new SourceUrnFormatRule())
|
|
45
48
|
.addRule(new RelationshipTypeRule())
|
|
46
|
-
.addRule(new CiteRefIntegrityRule())
|
|
49
|
+
.addRule(new CiteRefIntegrityRule())
|
|
50
|
+
.addRule(new NonVerbalRefIntegrityRule());
|
|
47
51
|
|
|
48
52
|
export function validateConcept(concept) {
|
|
49
53
|
return _default.validate(concept);
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { ValidationRule } from './validation-rule.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
concept.languages ?? (concept.localizations ? Object.keys(concept.localizations) : []);
|
|
5
|
-
|
|
6
|
-
const _loc = (concept, lang) =>
|
|
7
|
-
typeof concept.localization === 'function' ? concept.localization(lang) : concept.localizations?.[lang];
|
|
2
|
+
import { parseMention } from '../reference-mention.js';
|
|
3
|
+
import { GraphicalSymbol } from '../models/designation.js';
|
|
8
4
|
|
|
9
5
|
const _eachLocalization = (concept, fn) => {
|
|
10
|
-
for (const lang of
|
|
11
|
-
const lc =
|
|
6
|
+
for (const lang of concept.languages) {
|
|
7
|
+
const lc = concept.localization(lang);
|
|
12
8
|
if (lc) fn(lang, lc);
|
|
13
9
|
}
|
|
14
10
|
};
|
|
@@ -19,7 +15,7 @@ export class RefShapeRule extends ValidationRule {
|
|
|
19
15
|
validate(concept, path, result) {
|
|
20
16
|
let sourceIdx = 0;
|
|
21
17
|
_eachLocalization(concept, (lang, lc) => {
|
|
22
|
-
const sources = lc.sources
|
|
18
|
+
const sources = lc.sources;
|
|
23
19
|
for (let i = 0; i < sources.length; i++) {
|
|
24
20
|
sourceIdx++;
|
|
25
21
|
const origin = sources[i].origin;
|
|
@@ -38,7 +34,7 @@ export class RefShapeRule extends ValidationRule {
|
|
|
38
34
|
}
|
|
39
35
|
});
|
|
40
36
|
|
|
41
|
-
const related = concept.relatedConcepts
|
|
37
|
+
const related = concept.relatedConcepts;
|
|
42
38
|
for (let i = 0; i < related.length; i++) {
|
|
43
39
|
const ref = related[i].ref;
|
|
44
40
|
if (!ref) continue;
|
|
@@ -56,7 +52,7 @@ export class LocalityCompletenessRule extends ValidationRule {
|
|
|
56
52
|
|
|
57
53
|
validate(concept, path, result) {
|
|
58
54
|
_eachLocalization(concept, (lang, lc) => {
|
|
59
|
-
const sources = lc.sources
|
|
55
|
+
const sources = lc.sources;
|
|
60
56
|
for (let i = 0; i < sources.length; i++) {
|
|
61
57
|
const origin = sources[i].origin;
|
|
62
58
|
if (!origin || !origin.locality) continue;
|
|
@@ -81,14 +77,14 @@ export class LocalizationConsistencyRule extends ValidationRule {
|
|
|
81
77
|
constructor() { super('localization-consistency'); }
|
|
82
78
|
|
|
83
79
|
validate(concept, path, result) {
|
|
84
|
-
const langs =
|
|
85
|
-
const data = concept.raw?.data ||
|
|
80
|
+
const langs = concept.languages;
|
|
81
|
+
const data = concept.raw?.data || {};
|
|
86
82
|
const declaredLangs = data.localized_concepts
|
|
87
83
|
? Object.keys(data.localized_concepts)
|
|
88
84
|
: langs;
|
|
89
85
|
|
|
90
86
|
for (const lang of declaredLangs) {
|
|
91
|
-
if (!concept.hasLocalization
|
|
87
|
+
if (!concept.hasLocalization(lang)) {
|
|
92
88
|
this.addIssue(result,
|
|
93
89
|
`${path}localizations.${lang}`,
|
|
94
90
|
`localized_concepts map has '${lang}' but no localization loaded`);
|
|
@@ -101,12 +97,10 @@ export class SchemaVersionRule extends ValidationRule {
|
|
|
101
97
|
constructor() { super('schema-version', 'warning'); }
|
|
102
98
|
|
|
103
99
|
validate(concept, path, result) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (version && String(version) !== '3') {
|
|
100
|
+
if (concept.schemaVersion && String(concept.schemaVersion) !== '3') {
|
|
107
101
|
this.addIssue(result,
|
|
108
102
|
`${path}schema_version`,
|
|
109
|
-
`schema_version is '${
|
|
103
|
+
`schema_version is '${concept.schemaVersion}', expected '3'`);
|
|
110
104
|
}
|
|
111
105
|
}
|
|
112
106
|
}
|
|
@@ -115,11 +109,8 @@ export class DomainRefRule extends ValidationRule {
|
|
|
115
109
|
constructor() { super('domain-ref', 'warning'); }
|
|
116
110
|
|
|
117
111
|
validate(concept, path, result) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < domains.length; i++) {
|
|
121
|
-
const domain = domains[i];
|
|
122
|
-
const json = typeof domain.toJSON === 'function' ? domain.toJSON() : domain;
|
|
112
|
+
for (let i = 0; i < concept.domains.length; i++) {
|
|
113
|
+
const json = concept.domains[i].toJSON();
|
|
123
114
|
if (!json.concept_id && !json.urn) {
|
|
124
115
|
this.addIssue(result,
|
|
125
116
|
`${path}domains[${i}]`,
|
|
@@ -134,7 +125,7 @@ export class UuidFormatRule extends ValidationRule {
|
|
|
134
125
|
|
|
135
126
|
validate(concept, path, result) {
|
|
136
127
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
137
|
-
const id = concept.id
|
|
128
|
+
const id = concept.id;
|
|
138
129
|
|
|
139
130
|
if (id && !UUID_RE.test(String(id))) {
|
|
140
131
|
if (String(id).includes('-') && String(id).length > 20) {
|
|
@@ -153,7 +144,7 @@ export class SourceUrnFormatRule extends ValidationRule {
|
|
|
153
144
|
const URN_RE = /^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]+$/i;
|
|
154
145
|
|
|
155
146
|
_eachLocalization(concept, (lang, lc) => {
|
|
156
|
-
const sources = lc.sources
|
|
147
|
+
const sources = lc.sources;
|
|
157
148
|
for (let i = 0; i < sources.length; i++) {
|
|
158
149
|
const source = lc.sources[i].origin?.ref?.source;
|
|
159
150
|
if (!source || !source.startsWith('urn:')) continue;
|
|
@@ -180,23 +171,23 @@ function _findCiteMentions(concept) {
|
|
|
180
171
|
}
|
|
181
172
|
};
|
|
182
173
|
|
|
183
|
-
for (const lang of
|
|
184
|
-
const lc =
|
|
174
|
+
for (const lang of concept.languages) {
|
|
175
|
+
const lc = concept.localization(lang);
|
|
185
176
|
if (!lc) continue;
|
|
186
177
|
|
|
187
|
-
for (let i = 0;
|
|
178
|
+
for (let i = 0; lc.definitions[i]; i++) {
|
|
188
179
|
walkText(lc.definitions[i]?.content, `localizations.${lang}.definitions[${i}].content`);
|
|
189
180
|
}
|
|
190
|
-
for (let i = 0;
|
|
181
|
+
for (let i = 0; lc.notes[i]; i++) {
|
|
191
182
|
const content = typeof lc.notes[i] === 'object'
|
|
192
183
|
? (lc.notes[i]?.content ?? '')
|
|
193
184
|
: String(lc.notes[i] ?? '');
|
|
194
185
|
walkText(content, `localizations.${lang}.notes[${i}].content`);
|
|
195
186
|
}
|
|
196
|
-
for (let i = 0;
|
|
187
|
+
for (let i = 0; lc.examples[i]; i++) {
|
|
197
188
|
walkText(lc.examples[i]?.content, `localizations.${lang}.examples[${i}].content`);
|
|
198
189
|
}
|
|
199
|
-
for (let i = 0;
|
|
190
|
+
for (let i = 0; lc.annotations[i]; i++) {
|
|
200
191
|
walkText(lc.annotations[i]?.content, `localizations.${lang}.annotations[${i}].content`);
|
|
201
192
|
}
|
|
202
193
|
}
|
|
@@ -212,13 +203,13 @@ function _findDuplicateSourceIds(concept) {
|
|
|
212
203
|
seen.get(source.id).push(source);
|
|
213
204
|
};
|
|
214
205
|
|
|
215
|
-
for (const source of
|
|
216
|
-
for (const lang of
|
|
217
|
-
const lc =
|
|
206
|
+
for (const source of concept.sources) record(source);
|
|
207
|
+
for (const lang of concept.languages) {
|
|
208
|
+
const lc = concept.localization(lang);
|
|
218
209
|
if (!lc) continue;
|
|
219
|
-
for (const source of (lc.sources
|
|
220
|
-
for (const designation of
|
|
221
|
-
for (const source of
|
|
210
|
+
for (const source of (lc.sources)) record(source);
|
|
211
|
+
for (const designation of lc.terms) {
|
|
212
|
+
for (const source of designation.sources) record(source);
|
|
222
213
|
}
|
|
223
214
|
}
|
|
224
215
|
|
|
@@ -231,17 +222,17 @@ function _findDuplicateSourceIds(concept) {
|
|
|
231
222
|
|
|
232
223
|
function _collectSourceIds(concept) {
|
|
233
224
|
const ids = new Set();
|
|
234
|
-
for (const source of
|
|
225
|
+
for (const source of concept.sources) {
|
|
235
226
|
if (source?.id != null) ids.add(source.id);
|
|
236
227
|
}
|
|
237
|
-
for (const lang of
|
|
238
|
-
const lc =
|
|
228
|
+
for (const lang of concept.languages) {
|
|
229
|
+
const lc = concept.localization(lang);
|
|
239
230
|
if (!lc) continue;
|
|
240
|
-
for (const source of (lc.sources
|
|
231
|
+
for (const source of (lc.sources)) {
|
|
241
232
|
if (source?.id != null) ids.add(source.id);
|
|
242
233
|
}
|
|
243
|
-
for (const designation of
|
|
244
|
-
for (const source of
|
|
234
|
+
for (const designation of lc.terms) {
|
|
235
|
+
for (const source of designation.sources) {
|
|
245
236
|
if (source?.id != null) ids.add(source.id);
|
|
246
237
|
}
|
|
247
238
|
}
|
|
@@ -278,3 +269,148 @@ export class CiteRefIntegrityRule extends ValidationRule {
|
|
|
278
269
|
}
|
|
279
270
|
}
|
|
280
271
|
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
// ── NonVerbalRefIntegrityRule ────────────────────────────────────────
|
|
275
|
+
// Uses parseMention for classification (no regex duplication).
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
const NVR_ARRAYS = Object.freeze([
|
|
279
|
+
{ name: 'figures', entityType: 'figure' },
|
|
280
|
+
{ name: 'tables', entityType: 'table' },
|
|
281
|
+
{ name: 'formulas', entityType: 'formula' },
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
function _findNvrMentions(concept) {
|
|
285
|
+
const mentions = [];
|
|
286
|
+
const walkText = (text, source) => {
|
|
287
|
+
if (typeof text !== 'string' || text.length === 0) return;
|
|
288
|
+
const re = /\{\{([^{}]*?)\}\}/g;
|
|
289
|
+
let m;
|
|
290
|
+
while ((m = re.exec(text)) !== null) {
|
|
291
|
+
const parsed = parseMention(m[1]);
|
|
292
|
+
if (parsed.kind === 'fig-ref' ||
|
|
293
|
+
parsed.kind === 'table-ref' ||
|
|
294
|
+
parsed.kind === 'formula-ref') {
|
|
295
|
+
mentions.push({ key: parsed.key, source });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
for (const lang of concept.languages) {
|
|
301
|
+
const lc = concept.localization(lang);
|
|
302
|
+
if (!lc) continue;
|
|
303
|
+
for (let i = 0; i < lc.definitions.length; i++) {
|
|
304
|
+
walkText(lc.definitions[i]?.content,
|
|
305
|
+
`localizations.${lang}.definitions[${i}].content`);
|
|
306
|
+
}
|
|
307
|
+
for (let i = 0; i < lc.notes.length; i++) {
|
|
308
|
+
walkText(lc.notes[i]?.content,
|
|
309
|
+
`localizations.${lang}.notes[${i}].content`);
|
|
310
|
+
}
|
|
311
|
+
for (let i = 0; i < lc.examples.length; i++) {
|
|
312
|
+
walkText(lc.examples[i]?.content,
|
|
313
|
+
`localizations.${lang}.examples[${i}].content`);
|
|
314
|
+
}
|
|
315
|
+
for (let i = 0; i < lc.annotations.length; i++) {
|
|
316
|
+
walkText(lc.annotations[i]?.content,
|
|
317
|
+
`localizations.${lang}.annotations[${i}].content`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return mentions;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export class NonVerbalRefIntegrityRule extends ValidationRule {
|
|
324
|
+
constructor() {
|
|
325
|
+
super('nvr-integrity', 'warning');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
validate(concept, path, result) {
|
|
329
|
+
for (const { name } of NVR_ARRAYS) {
|
|
330
|
+
const counts = new Map();
|
|
331
|
+
for (const ref of concept[name]) {
|
|
332
|
+
if (ref.entityId == null) continue;
|
|
333
|
+
counts.set(ref.entityId, (counts.get(ref.entityId) ?? 0) + 1);
|
|
334
|
+
}
|
|
335
|
+
for (const [id, count] of counts) {
|
|
336
|
+
if (count > 1) {
|
|
337
|
+
this.addIssue(result, `${path}${name}`,
|
|
338
|
+
`duplicate ${name} reference id "${id}" appears ${count} times`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const mentions = _findNvrMentions(concept);
|
|
344
|
+
if (mentions.length === 0) return;
|
|
345
|
+
|
|
346
|
+
const knownIds = new Set();
|
|
347
|
+
for (const { name } of NVR_ARRAYS) {
|
|
348
|
+
for (const ref of concept[name]) {
|
|
349
|
+
if (ref.entityId != null) knownIds.add(ref.entityId);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const { key, source } of mentions) {
|
|
354
|
+
if (!knownIds.has(key)) {
|
|
355
|
+
this.addIssue(result, source,
|
|
356
|
+
`inline NVR mention "${key}" does not resolve to any figures/tables/formulas entry`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── OrphanedImagesRule ───────────────────────────────────────────────
|
|
363
|
+
// Collection-scope rule: needs AssetIndex + all concepts. Called
|
|
364
|
+
// directly by GcrValidator (not in concept validator chain).
|
|
365
|
+
|
|
366
|
+
export class OrphanedImagesRule {
|
|
367
|
+
constructor() {
|
|
368
|
+
this.name = 'orphaned-images';
|
|
369
|
+
this.severity = 'warning';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
check(context) {
|
|
373
|
+
const { assetIndex, concepts, resolver } = context;
|
|
374
|
+
if (!assetIndex || assetIndex.size === 0) return [];
|
|
375
|
+
|
|
376
|
+
const referenced = new Set();
|
|
377
|
+
|
|
378
|
+
for (const concept of concepts) {
|
|
379
|
+
if (resolver) {
|
|
380
|
+
for (const ref of resolver.extractReferences(concept)) {
|
|
381
|
+
if (ref.target && ref.target.includes('images/')) {
|
|
382
|
+
referenced.add(ref.target.replace(/^\//, ''));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
for (const lang of concept.languages) {
|
|
388
|
+
const lc = concept.localization(lang);
|
|
389
|
+
if (!lc) continue;
|
|
390
|
+
|
|
391
|
+
for (const nvr of lc.nonVerbalRep) {
|
|
392
|
+
for (const img of nvr.images) {
|
|
393
|
+
if (img.src) referenced.add(img.src.replace(/^\//, ''));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const term of lc.terms) {
|
|
397
|
+
if (term instanceof GraphicalSymbol && term.image) {
|
|
398
|
+
referenced.add(term.image.replace(/^\//, ''));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const issues = [];
|
|
405
|
+
for (const imgPath of assetIndex.paths) {
|
|
406
|
+
if (!referenced.has(imgPath)) {
|
|
407
|
+
issues.push({
|
|
408
|
+
path: imgPath,
|
|
409
|
+
severity: 'warning',
|
|
410
|
+
message: `orphaned image: ${imgPath} (not referenced by any concept)`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return issues;
|
|
415
|
+
}
|
|
416
|
+
}
|