@glossarist/concept-browser 0.7.114 → 0.7.116

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.
Files changed (103) hide show
  1. package/README.md +45 -2
  2. package/cli/index.mjs +10 -13
  3. package/cli/index.ts +9 -11
  4. package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
  5. package/package.json +18 -12
  6. package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
  7. package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
  8. package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
  9. package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
  10. package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
  11. package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
  12. package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
  13. package/scripts/bridge-to-astro.js +132 -0
  14. package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
  15. package/scripts/build-edges.js +242 -336
  16. package/scripts/build-edges.ts +572 -0
  17. package/scripts/build-scripts.mjs +22 -0
  18. package/scripts/check-syntax.js +81 -0
  19. package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
  20. package/scripts/doctor.js +278 -0
  21. package/scripts/extract-source-refs.js +16 -24
  22. package/scripts/extract-source-refs.ts +32 -0
  23. package/scripts/fetch-datasets.js +278 -0
  24. package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
  25. package/scripts/generate-404.js +10 -11
  26. package/scripts/generate-404.ts +15 -0
  27. package/scripts/generate-data.js +2220 -0
  28. package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
  29. package/scripts/generate-ontology-data.js +139 -0
  30. package/scripts/generate-ontology-schema.js +472 -0
  31. package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
  32. package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
  33. package/scripts/lib/concept-formats.ts +206 -0
  34. package/scripts/lib/concept-uri.ts +14 -0
  35. package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
  36. package/scripts/lib/harmonize-concept.ts +86 -0
  37. package/scripts/lib/jsonld-emitter.ts +169 -0
  38. package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
  39. package/scripts/lib/yaml-types.ts +262 -0
  40. package/scripts/load-site-config.js +83 -0
  41. package/scripts/migrate-v1-to-v3.js +113 -0
  42. package/scripts/normalize-yaml.js +96 -0
  43. package/scripts/process-about-pages.js +241 -0
  44. package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
  45. package/scripts/smoke.js +211 -0
  46. package/scripts/{smoke.mjs → smoke.ts} +4 -3
  47. package/scripts/sync-concept-model.js +63 -0
  48. package/scripts/validate-shacl.js +152 -0
  49. package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
  50. package/scripts/validate-yaml.js +132 -0
  51. package/scripts/validate-yaml.ts +144 -0
  52. package/src/__tests__/extract-source-refs.test.ts +2 -3
  53. package/src/__tests__/perf/serialization-perf.test.ts +5 -5
  54. package/src/__tests__/published-package-contract.test.ts +84 -0
  55. package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
  56. package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
  57. package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
  58. package/src/__tests__/scripts/build-cache.test.ts +1 -1
  59. package/src/__tests__/scripts/build-integration.test.ts +19 -19
  60. package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
  61. package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
  62. package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
  63. package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
  64. package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
  65. package/src/__tests__/scripts/version-turtle.test.ts +1 -1
  66. package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
  67. package/src/__tests__/scripts-syntax.test.ts +4 -4
  68. package/src/__tests__/use-rdf-document.test.ts +2 -3
  69. package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
  70. package/src/__tests__/validate-shacl.test.ts +3 -3
  71. package/src/adapters/wire/index.ts +69 -0
  72. package/src/data/ontology-schema.json +1336 -183
  73. package/src/utils/content-renderer.ts +7 -3
  74. package/src/utils/url-safety.ts +1 -5
  75. package/tsconfig.json +2 -2
  76. package/vite.config.ts +1 -1
  77. package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
  78. package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
  79. package/src/types/agents-version-turtle.d.ts +0 -27
  80. package/src/types/bibliography-turtle.d.ts +0 -12
  81. package/src/types/build-activity-turtle.d.ts +0 -16
  82. package/src/types/build-cache.d.ts +0 -20
  83. package/src/types/dataset-turtle.d.ts +0 -32
  84. package/src/types/normalize-yaml.d.ts +0 -16
  85. package/src/types/turtle-escape.d.ts +0 -6
  86. package/src/types/vocab-turtle.d.ts +0 -13
  87. /package/scripts/{doctor.mjs → doctor.ts} +0 -0
  88. /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
  89. /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
  90. /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
  91. /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
  92. /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
  93. /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
  94. /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
  95. /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
  96. /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
  97. /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
  98. /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
  99. /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
  100. /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
  101. /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
  102. /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
  103. /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
@@ -2,46 +2,59 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import yaml from 'js-yaml';
4
4
  import { naturalSort, Register, parseMention } from 'glossarist';
5
- import { loadSiteConfig } from './load-site-config.mjs';
6
- import { getGroups } from './lib/concept-groups.mjs';
7
- import { consumeDatasetEntities } from './lib/build/non-verbal-consumer.mjs';
8
- import { copyImageAssets } from './lib/build/image-assets.mjs';
9
- import { buildDatasetTurtle } from './lib/dataset-turtle.mjs';
10
- import { buildActivityTurtle } from './lib/build-activity-turtle.mjs';
11
- import { buildVocabularyTurtle } from './lib/vocab-turtle.mjs';
12
- import { buildAgentsTurtle } from './lib/agents-turtle.mjs';
13
- import { buildVersionHistoryTurtle } from './lib/version-turtle.mjs';
14
- import { buildBibliographyTurtle } from './lib/bibliography-turtle.mjs';
15
- import { ttlLit } from './lib/turtle-escape.mjs';
16
- import { firstNonEmpty } from './lib/first-non-empty.mjs';
17
- import { normalizeBibliography } from './lib/bibliography.mjs';
5
+ import { loadSiteConfig } from './load-site-config';
6
+ import { getGroups } from './lib/concept-groups';
7
+ import { consumeDatasetEntities } from './lib/build/non-verbal-consumer';
8
+ import { copyImageAssets } from './lib/build/image-assets';
9
+ import { buildDatasetTurtle } from './lib/dataset-turtle';
10
+ import { buildActivityTurtle } from './lib/build-activity-turtle';
11
+ import { buildVocabularyTurtle } from './lib/vocab-turtle';
12
+ import { buildAgentsTurtle } from './lib/agents-turtle';
13
+ import { buildVersionHistoryTurtle } from './lib/version-turtle';
14
+ import { buildBibliographyTurtle } from './lib/bibliography-turtle';
15
+ import { ttlLit } from './lib/turtle-escape';
16
+ import { firstNonEmpty } from './lib/first-non-empty';
17
+ import { normalizeBibliography } from './lib/bibliography';
18
+ import type {
19
+ HarmonizedConcept,
20
+ YamlManagedConceptDoc,
21
+ YamlManifest,
22
+ YamlBibliographyEntry,
23
+ YamlNewsFrontmatter,
24
+ YamlContentPage,
25
+ } from './lib/yaml-types';
26
+ import { buildConceptUri } from './lib/concept-uri';
27
+ import {
28
+ loadConceptFile,
29
+ readYaml,
30
+ stripHtml,
31
+ splitMultiplicity,
32
+ LEGACY_MULTIPLICITY_MAP,
33
+ LEGACY_CERTAINTY_FROM_AXIS,
34
+ } from './lib/harmonize-concept';
35
+ import {
36
+ termToDesignation,
37
+ defsToJsonLd,
38
+ refToJsonLd,
39
+ localityToJsonLd,
40
+ sourcesToJsonLd,
41
+ refsToJsonLd,
42
+ citationToJsonLd,
43
+ } from './lib/jsonld-emitter';
44
+ import {
45
+ escapeTurtle,
46
+ escapeXml,
47
+ conceptJsonToTurtle,
48
+ conceptJsonToSkosJsonLd,
49
+ conceptJsonToTbx,
50
+ } from './lib/concept-formats';
18
51
 
19
52
  // MECE partitive multiplicity: 2 independent axes (ISO 704:2022).
20
53
  // presence × count
21
54
  // Legacy 5-value `multiplicity` and 2-value `certainty` map into the
22
55
  // 2-axis model — keep emitting them for one release cycle so consumers
23
56
  // that still read the legacy fields keep working.
24
- const LEGACY_MULTIPLICITY_MAP = {
25
- compulsory: { presence: 'required', count: 'exactly_one' },
26
- optional: { presence: 'optional', count: 'exactly_one' },
27
- compulsory_multiple: { presence: 'required', count: 'multiple' },
28
- optional_multiple: { presence: 'optional', count: 'multiple' },
29
- compulsory_at_least_one: { presence: 'required', count: 'at_least_one' },
30
- };
31
- const LEGACY_CERTAINTY_FROM_AXIS = {
32
- required: 'confirmed',
33
- optional: 'possible',
34
- };
35
-
36
- function splitMultiplicity(multiplicity) {
37
- return multiplicity in LEGACY_MULTIPLICITY_MAP
38
- ? LEGACY_MULTIPLICITY_MAP[multiplicity]
39
- : { presence: 'required', count: 'exactly_one' };
40
- }
41
-
42
- function buildConceptUri(uriBase, registerId, conceptId) {
43
- return `${uriBase}/${registerId}/concept/${conceptId}`;
44
- }
57
+ // (LEGACY_MULTIPLICITY_MAP and LEGACY_CERTAINTY_FROM_AXIS imported from harmonize-concept.ts)
45
58
 
46
59
 
47
60
  const __dirname = path.dirname(new URL(import.meta.url).pathname);
@@ -55,7 +68,7 @@ const DATA = path.join(PUBLIC, 'data');
55
68
  * No staging, no copy. fetch-datasets.mjs verifies the path is safe.
56
69
  * - Otherwise fall back to the standard .datasets/<id>/ staging dir.
57
70
  */
58
- function datasetDir(ds) {
71
+ function datasetDir(ds: Record<string, any>): string {
59
72
  return ds.localPath
60
73
  ? path.resolve(ROOT, ds.localPath)
61
74
  : path.join(ROOT, '.datasets', ds.id);
@@ -67,57 +80,6 @@ const DS_PALETTE = [
67
80
  '#0891b2', '#65a30d',
68
81
  ];
69
82
 
70
- function readYaml(filePath) {
71
- return yaml.load(fs.readFileSync(filePath, 'utf8'));
72
- }
73
-
74
- /** Strip HTML tags and normalize whitespace for plain-text display. */
75
- function stripHtml(s) {
76
- return s.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
77
- }
78
-
79
- function loadConceptFile(filePath) {
80
- const content = fs.readFileSync(filePath, 'utf8');
81
- const docs = yaml.loadAll(content, null, { schema: yaml.DEFAULT_SCHEMA });
82
-
83
- if (docs.length === 1 && docs[0].termid !== undefined) {
84
- return docs[0];
85
- }
86
-
87
- if (docs.length >= 1 && docs[0].data && docs[0].data.identifier !== undefined) {
88
- const mc = docs[0];
89
- const result = { termid: String(mc.data.identifier) };
90
-
91
- // Managed concept-level fields
92
- if (mc.related) result._related = mc.related;
93
- if (mc.partitive_relations) result._partitiveRelations = mc.partitive_relations;
94
- if (mc.generic_relations) result._genericRelations = mc.generic_relations;
95
- if (mc.data.domains) result._domains = mc.data.domains;
96
- if (mc.dates) result._dates = mc.dates;
97
- if (mc.sources) result._sources = mc.sources;
98
- if (mc.status) result._status = mc.status;
99
- if (mc.schema_version) result._schemaVersion = mc.schema_version;
100
- if (mc.date_accepted) result._dateAccepted = mc.date_accepted;
101
-
102
- for (const doc of docs.slice(1)) {
103
- if (!doc) continue;
104
- const lang = doc.data?.language_code || doc.language_code;
105
- if (!lang) continue;
106
- const lcData = { ...(doc.data || {}) };
107
- delete lcData.language_code;
108
- // Merge top-level fields (terms, definition, notes, etc.) into lcData
109
- for (const key of ['terms', 'definition', 'notes', 'annotations', 'examples', 'sources', 'dates', 'domain', 'references', 'entry_status', 'classification', 'review_type', 'review_date', 'review_decision_date', 'review_decision_event', 'review_status', 'review_decision', 'review_decision_notes', 'lineage_source_similarity', 'release', 'script', 'system']) {
110
- if (doc[key] !== undefined && lcData[key] === undefined) {
111
- lcData[key] = doc[key];
112
- }
113
- }
114
- result[lang] = lcData;
115
- }
116
- return result;
117
- }
118
-
119
- return docs[0];
120
- }
121
83
 
122
84
  function writeJson(filePath, data) {
123
85
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -203,167 +165,13 @@ async function writeBuildActivity(conceptCount, datasetRegisters, baseUri) {
203
165
  console.log(`Emitted build activity record: data/activity/${runId}.ttl`);
204
166
  }
205
167
 
206
- function termToDesignation(term) {
207
- const typeMap = {
208
- expression: 'gl:Expression',
209
- abbreviation: 'gl:Abbreviation',
210
- symbol: 'gl:Symbol',
211
- letter_symbol: 'gl:LetterSymbol',
212
- 'graphical symbol': 'gl:GraphicalSymbol',
213
- };
214
- const doc = {
215
- '@type': typeMap[term.type] || 'gl:Designation',
216
- 'gl:normativeStatus': term.normative_status || 'preferred',
217
- 'gl:term': term.designation,
218
- };
219
-
220
- if (term.grammar_info && term.grammar_info.length > 0) {
221
- doc['gl:grammarInfo'] = term.grammar_info.map(gi => {
222
- const g = {};
223
- if (gi.gender) g['gl:gender'] = gi.gender;
224
- if (gi.number) g['gl:number'] = gi.number;
225
- for (const pos of ['noun', 'verb', 'adj', 'adverb', 'preposition', 'participle']) {
226
- if (gi[pos]) g[`gl:${pos}`] = gi[pos];
227
- }
228
- return g;
229
- });
230
- }
231
-
232
- if (term.international !== undefined) doc['gl:international'] = term.international;
233
- if (term.absent !== undefined) doc['gl:absent'] = term.absent;
234
- if (term.geographical_area) doc['gl:geographicalArea'] = term.geographical_area;
235
- if (term.term_type) doc['gl:termType'] = term.term_type;
236
- if (term.prefix) doc['gl:prefix'] = term.prefix;
237
- if (term.usage_info) doc['gl:usageInfo'] = term.usage_info;
238
- if (term.field_of_application) doc['gl:fieldOfApplication'] = term.field_of_application;
239
-
240
- if (term.acronym !== undefined) doc['gl:acronym'] = term.acronym;
241
- if (term.initialism !== undefined) doc['gl:initialism'] = term.initialism;
242
- if (term.truncation !== undefined) doc['gl:truncation'] = term.truncation;
243
-
244
- if (term.text) doc['gl:text'] = term.text;
245
- if (term.image) doc['gl:image'] = term.image;
246
-
247
- if (term.related && term.related.length > 0) {
248
- doc['gl:related'] = term.related.map(r => {
249
- const rel = {};
250
- if (r.type) rel['gl:relationshipType'] = r.type;
251
- if (r.target) {
252
- rel['gl:target'] = r.target;
253
- } else if (r.ref) {
254
- const ref = { '@type': 'gl:ConceptRef' };
255
- if (r.ref.source) ref['gl:source'] = r.ref.source;
256
- if (r.ref.id) ref['gl:id'] = r.ref.id;
257
- if (r.ref.text) ref['gl:text'] = r.ref.text;
258
- rel['gl:ref'] = ref;
259
- }
260
- return rel;
261
- });
262
- }
263
-
264
- return doc;
265
- }
266
-
267
- function defsToJsonLd(defs) {
268
- if (!defs || !Array.isArray(defs)) return [];
269
- return defs
270
- .map(d => ({
271
- '@type': 'gl:DetailedDefinition',
272
- 'gl:content': d.content || '',
273
- }))
274
- .filter(d => d['gl:content']);
275
- }
276
-
277
- function refToJsonLd(ref, typeName = 'gl:Ref') {
278
- if (!ref) return undefined;
279
- const refObj = { '@type': typeName };
280
- if (typeof ref === 'string') {
281
- refObj['gl:source'] = ref;
282
- } else {
283
- if (ref.source) refObj['gl:source'] = ref.source;
284
- if (ref.id) refObj['gl:id'] = ref.id;
285
- if (ref.version) refObj['gl:version'] = ref.version;
286
- if (ref.text) refObj['gl:text'] = ref.text;
287
- }
288
- return refObj;
289
- }
290
-
291
- function localityToJsonLd(loc) {
292
- if (!loc) return undefined;
293
- const locObj = {};
294
- if (loc.type) locObj['gl:localityType'] = loc.type;
295
- if (loc.reference_from) locObj['gl:referenceFrom'] = loc.reference_from;
296
- if (loc.referenceFrom) locObj['gl:referenceFrom'] = loc.referenceFrom;
297
- if (loc.reference_to) locObj['gl:referenceTo'] = loc.reference_to;
298
- if (loc.referenceTo) locObj['gl:referenceTo'] = loc.referenceTo;
299
- return Object.keys(locObj).length > 0 ? locObj : undefined;
300
- }
301
-
302
- function sourcesToJsonLd(sources) {
303
- if (!sources || !Array.isArray(sources)) return [];
304
- return sources.map(s => {
305
- const doc = { '@type': 'gl:ConceptSource' };
306
- if (s.id) doc['gl:id'] = s.id;
307
- if (s.type) doc['gl:sourceType'] = s.type;
308
- if (s.status) doc['gl:sourceStatus'] = s.status;
309
- if (s.modification) doc['gl:modification'] = s.modification;
310
- if (s.origin) {
311
- const origin = { '@type': 'gl:Citation' };
312
- const ref = refToJsonLd(s.origin.ref);
313
- if (ref) origin['gl:ref'] = ref;
314
- const loc = localityToJsonLd(s.origin.locality);
315
- if (loc) origin['gl:locality'] = loc;
316
- if (s.origin.link) origin['gl:link'] = s.origin.link;
317
- doc['gl:origin'] = origin;
318
- }
319
- if (s.sourced_from && s.sourced_from.length) {
320
- doc['gl:sourcedFrom'] = s.sourced_from.map(sf => {
321
- const cite = { '@type': 'gl:Citation' };
322
- const ref = refToJsonLd(sf.ref);
323
- if (ref) cite['gl:ref'] = ref;
324
- const loc = localityToJsonLd(sf.locality);
325
- if (loc) cite['gl:locality'] = loc;
326
- if (sf.link) cite['gl:link'] = sf.link;
327
- return cite;
328
- });
329
- }
330
- return doc;
331
- });
332
- }
333
-
334
- function refsToJsonLd(refs, refMaps) {
335
- if (!refs || !Array.isArray(refs)) return [];
336
- return refs.map(r => {
337
- if (r.id) {
338
- const ref = { '@id': r.id, 'gl:term': r.term };
339
- if (r.sourceId) ref['gl:sourceId'] = r.sourceId;
340
- if (r.citation) ref['gl:citation'] = citationToJsonLd(r.citation);
341
- return ref;
342
- }
343
- if (r.term && refMaps) {
344
- const uri = resolveRefUri(r.term, refMaps);
345
- if (uri) return { '@id': uri, 'gl:term': r.term };
346
- }
347
- return { '@id': r.id || r.term, 'gl:term': r.term };
348
- }).filter(r => r['@id']);
349
- }
350
-
351
- function citationToJsonLd(citation) {
352
- const obj = {};
353
- const ref = refToJsonLd(citation.ref);
354
- if (ref) obj['gl:ref'] = ref;
355
- const loc = localityToJsonLd(citation.locality);
356
- if (loc) obj['gl:locality'] = loc;
357
- if (citation.link) obj['gl:link'] = citation.link;
358
- return obj;
359
- }
360
168
 
361
- function buildPatternIndex(datasets, registerCache) {
362
- const entries = [];
169
+ function buildPatternIndex(datasets: Record<string, any>[], registerCache: Record<string, any>) {
170
+ const entries: { prefix: string; datasetId: string }[] = [];
363
171
 
364
172
  for (const ds of datasets) {
365
- const reg = registerCache[ds.id] || null;
366
- const patterns = new Set();
173
+ const reg: Record<string, any> | null = registerCache[ds.id] || null;
174
+ const patterns = new Set<string>();
367
175
 
368
176
  // Site-config patterns (primary)
369
177
  if (ds.uri) patterns.add(ds.uri);
@@ -371,12 +179,12 @@ function buildPatternIndex(datasets, registerCache) {
371
179
 
372
180
  // Register.yaml patterns (supplementary)
373
181
  if (reg) {
374
- if (reg.urn && reg.urn.endsWith('*')) patterns.add(reg.urn);
182
+ if (reg.urn && typeof reg.urn === 'string' && reg.urn.endsWith('*')) patterns.add(reg.urn);
375
183
  for (const alias of reg.urnAliases || []) patterns.add(alias);
376
184
  }
377
185
 
378
186
  for (const pattern of patterns) {
379
- if (!pattern.endsWith('*')) continue;
187
+ if (typeof pattern !== 'string' || !pattern.endsWith('*')) continue;
380
188
  const prefix = pattern.slice(0, -1);
381
189
  if (prefix) entries.push({ prefix, datasetId: ds.id });
382
190
  }
@@ -834,201 +642,6 @@ function getPrimaryDesignation(conceptYaml) {
834
642
  return descs;
835
643
  }
836
644
 
837
- function escapeTurtle(s) {
838
- return ttlLit(s).slice(1, -1);
839
- }
840
-
841
- function conceptJsonToTurtle(concept) {
842
- const uri = concept['@id'] || '';
843
- const id = concept['gl:identifier'] || '';
844
- const lines = [
845
- '@prefix skos: <http://www.w3.org/2004/02/skos/core#> .',
846
- '@prefix dcterms: <http://purl.org/dc/terms/> .',
847
- '',
848
- ];
849
-
850
- const props = [' a skos:Concept'];
851
- props.push(` skos:notation "${escapeTurtle(id)}"`);
852
-
853
- for (const [lang, lc] of Object.entries(concept['gl:localizedConcept'] || {})) {
854
- if (lc['gl:designation']) {
855
- for (const d of lc['gl:designation']) {
856
- const term = d['gl:term'];
857
- if (!term) continue;
858
- const pred = d['gl:normativeStatus'] === 'preferred' ? 'skos:prefLabel' : 'skos:altLabel';
859
- props.push(` ${pred} "${escapeTurtle(term)}"@${lang}`);
860
- }
861
- }
862
- if (lc['gl:definition']) {
863
- for (const d of lc['gl:definition']) {
864
- if (d['gl:content']) props.push(` skos:definition "${escapeTurtle(d['gl:content'])}"@${lang}`);
865
- }
866
- }
867
- if (lc['gl:notes']) {
868
- for (const d of lc['gl:notes']) {
869
- if (d['gl:content']) props.push(` skos:scopeNote "${escapeTurtle(d['gl:content'])}"@${lang}`);
870
- }
871
- }
872
- }
873
-
874
- lines.push(`<${uri}>`);
875
- lines.push(props.join(' ;\n'));
876
- lines.push(' .');
877
- return lines.join('\n');
878
- }
879
-
880
- function conceptJsonToSkosJsonLd(concept) {
881
- const uri = concept['@id'] || '';
882
- const id = concept['gl:identifier'] || '';
883
-
884
- const doc = {
885
- '@context': {
886
- skos: 'http://www.w3.org/2004/02/skos/core#',
887
- dcterms: 'http://purl.org/dc/terms/',
888
- '@language': { '@container': '@language' },
889
- },
890
- '@id': uri,
891
- '@type': 'skos:Concept',
892
- 'skos:notation': id,
893
- };
894
-
895
- const prefLabels = {}, altLabels = {}, definitions = {}, scopeNotes = {};
896
- for (const [lang, lc] of Object.entries(concept['gl:localizedConcept'] || {})) {
897
- const descs = lc['gl:designation'] || [];
898
- const pref = descs.find(d => d['gl:normativeStatus'] === 'preferred' && d['gl:term']);
899
- const alt = descs.find(d => d['gl:normativeStatus'] !== 'preferred' && d['gl:term']);
900
- if (pref) prefLabels[lang] = pref['gl:term'];
901
- if (alt) altLabels[lang] = alt['gl:term'];
902
- const def = (lc['gl:definition'] || [])[0];
903
- if (def?.['gl:content']) definitions[lang] = def['gl:content'];
904
- const note = (lc['gl:notes'] || [])[0];
905
- if (note?.['gl:content']) scopeNotes[lang] = note['gl:content'];
906
- }
907
-
908
- if (Object.keys(prefLabels).length) doc['skos:prefLabel'] = prefLabels;
909
- if (Object.keys(altLabels).length) doc['skos:altLabel'] = altLabels;
910
- if (Object.keys(definitions).length) doc['skos:definition'] = definitions;
911
- if (Object.keys(scopeNotes).length) doc['skos:scopeNote'] = scopeNotes;
912
-
913
- return JSON.stringify(doc);
914
- }
915
-
916
- function escapeXml(s) {
917
- const str = Array.isArray(s) ? s.join(', ') : String(s ?? '');
918
- return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
919
- }
920
-
921
- function conceptJsonToTbx(concept) {
922
- const id = concept['gl:identifier'] || '';
923
- const uri = concept['@id'] || '';
924
- const localized = concept['gl:localizedConcept'] || {};
925
-
926
- const langSections = [];
927
- for (const [lang, lc] of Object.entries(localized)) {
928
- const descs = lc['gl:designation'] || [];
929
- const definitions = (lc['gl:definition'] || []).filter(d => d['gl:content']);
930
- const notes = (lc['gl:notes'] || []).filter(d => d['gl:content']);
931
- const examples = (lc['gl:examples'] || []).filter(d => d['gl:content']);
932
- const sources = lc['gl:source'] || [];
933
- const entryStatus = lc['gl:entryStatus'] || '';
934
-
935
- if (!descs.length && !definitions.length) continue;
936
-
937
- const termEntries = [];
938
- for (const d of descs) {
939
- const term = d['gl:term'];
940
- if (!term) continue;
941
- const status = d['gl:normativeStatus'] || '';
942
- const type = d['@type'] || '';
943
- let gramGrp = '';
944
- if (d['gl:grammarInfo'] && d['gl:grammarInfo'].length > 0) {
945
- const gi = d['gl:grammarInfo'][0];
946
- if (gi['gl:gender']) gramGrp = `\n <grammaticalGender>${escapeXml(gi['gl:gender'])}</grammaticalGender>`;
947
- if (gi['gl:number']) gramGrp += `\n <grammaticalNumber>${escapeXml(gi['gl:number'])}</grammaticalNumber>`;
948
- for (const pos of ['noun', 'verb', 'adj', 'adverb', 'preposition', 'participle']) {
949
- if (gi[`gl:${pos}`]) gramGrp += `\n <partOfSpeech>${pos}</partOfSpeech>`;
950
- }
951
- }
952
- let posBlock = '';
953
- if (type.includes('Abbreviation')) posBlock = '\n <partOfSpeech>abbreviation</partOfSpeech>';
954
- if (type.includes('Symbol')) posBlock = '\n <partOfSpeech>symbol</partOfSpeech>';
955
-
956
- termEntries.push(` <termEntry>
957
- <langSet xml:lang="${lang}">
958
- <tig>
959
- <term>${escapeXml(term)}</term>${gramGrp}${posBlock}
960
- </tig>
961
- </langSet>
962
- </termEntry>`);
963
- }
964
-
965
- let defBlock = '';
966
- if (definitions.length) {
967
- const defParts = definitions.map(d => ` <p>${escapeXml(d['gl:content'])}</p>`).join('\n');
968
- defBlock = `\n <descrip type="definition">\n${defParts}\n </descrip>`;
969
- }
970
-
971
- let noteBlock = '';
972
- for (let i = 0; i < notes.length; i++) {
973
- noteBlock += `\n <note type="note">${escapeXml(notes[i]['gl:content'])}</note>`;
974
- }
975
- for (let i = 0; i < examples.length; i++) {
976
- noteBlock += `\n <note type="example">${escapeXml(examples[i]['gl:content'])}</note>`;
977
- }
978
-
979
- let sourceBlock = '';
980
- for (const src of sources) {
981
- const origin = src['gl:origin'] || {};
982
- const parts = [];
983
- const ref = origin['gl:ref'];
984
- if (ref) {
985
- const refParts = [];
986
- if (ref['gl:source']) refParts.push(ref['gl:source']);
987
- if (ref['gl:id']) refParts.push(ref['gl:id']);
988
- parts.push(refParts.join(' ') || '');
989
- }
990
- if (origin['gl:locality']) {
991
- const loc = origin['gl:locality'];
992
- if (loc['gl:referenceFrom']) parts.push(loc['gl:localityType'] ? `${loc['gl:localityType']} ${loc['gl:referenceFrom']}` : loc['gl:referenceFrom']);
993
- }
994
- if (parts.filter(Boolean).length) {
995
- sourceBlock += `\n <ref>${escapeXml(parts.filter(Boolean).join(', '))}</ref>`;
996
- }
997
- }
998
-
999
- let statusBlock = '';
1000
- if (entryStatus) {
1001
- statusBlock += `\n <descrip type="entryStatus">${escapeXml(entryStatus)}</descrip>`;
1002
- }
1003
-
1004
- const termEntriesBlock = termEntries.length ? '\n' + termEntries.join('\n') : '';
1005
- langSections.push({ lang, termEntries, blocks: [defBlock, noteBlock, sourceBlock, statusBlock].filter(b => b).join('') });
1006
- }
1007
-
1008
- if (!langSections.length) return '';
1009
-
1010
- const bodyEntries = langSections.map(ls => {
1011
- return ` <languageSection xml:lang="${ls.lang}">${ls.blocks}\n </languageSection>`;
1012
- }).join('\n');
1013
-
1014
- return `<?xml version="1.0" encoding="UTF-8"?>
1015
- <tbx style="dca" type="TBX-Basic" xml:lang="en" xmlns="urn:iso:std:iso:30042:ed-2">
1016
- <tbxHeader>
1017
- <fileDesc>
1018
- <source>${escapeXml(uri)}</source>
1019
- </fileDesc>
1020
- </tbxHeader>
1021
- <text>
1022
- <body>
1023
- <conceptEntry id="${escapeXml(id)}">
1024
- ${bodyEntries}
1025
- </conceptEntry>
1026
- </body>
1027
- </text>
1028
- </tbx>
1029
- `;
1030
- }
1031
-
1032
645
  async function processDataset(dir, register, opts) {
1033
646
  const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml')).sort((a, b) => naturalSort(a.replace('.yaml', ''), b.replace('.yaml', '')));
1034
647
 
@@ -1052,9 +665,9 @@ async function processDataset(dir, register, opts) {
1052
665
  if (!conceptYaml?.termid) continue;
1053
666
  const termid = String(conceptYaml.termid);
1054
667
  for (const lang of Object.keys(conceptYaml)) {
1055
- const lc = conceptYaml[lang];
668
+ const lc = conceptYaml[lang] as Record<string, any>;
1056
669
  if (!lc || typeof lc !== 'object' || !Array.isArray(lc.terms)) continue;
1057
- for (const term of lc.terms) {
670
+ for (const term of lc.terms as Record<string, any>[]) {
1058
671
  const designation = term.designation;
1059
672
  if (typeof designation === 'string' && designation && !designationLookup.has(designation.toLowerCase())) {
1060
673
  designationLookup.set(designation.toLowerCase(), termid);
@@ -1171,7 +784,7 @@ async function processDataset(dir, register, opts) {
1171
784
  id: termid,
1172
785
  designations: getPrimaryDesignation(conceptYaml),
1173
786
  groups: getGroups(conceptYaml),
1174
- status: conceptYaml.eng?.entry_status || 'valid',
787
+ status: (conceptYaml.eng as Record<string, any>)?.entry_status || 'valid',
1175
788
  });
1176
789
 
1177
790
  for (const processor of STATS_PROCESSORS) {
@@ -1207,20 +820,20 @@ async function processDataset(dir, register, opts) {
1207
820
  // Strip HTML from index summary for text display
1208
821
  const plainSummary = summary.map(c => {
1209
822
  const designations = {};
1210
- for (const [lang, term] of Object.entries(c.designations)) {
1211
- if (term) designations[lang] = stripHtml(term);
823
+ for (const [lang, term] of Object.entries(c.designations) as [string, unknown][]) {
824
+ if (term) designations[lang] = stripHtml(String(term));
1212
825
  }
1213
826
  return {
1214
827
  ...c,
1215
828
  designations,
1216
- eng: stripHtml(c.eng),
829
+ eng: stripHtml(String(c.eng)),
1217
830
  };
1218
831
  });
1219
832
 
1220
833
  const graphNodeEntries = concepts.map(c => {
1221
834
  const cleanDesignations = {};
1222
- for (const [l, t] of Object.entries(c.designations)) {
1223
- if (t) cleanDesignations[l] = stripHtml(t);
835
+ for (const [l, t] of Object.entries(c.designations) as [string, unknown][]) {
836
+ if (t) cleanDesignations[l] = stripHtml(String(t));
1224
837
  }
1225
838
  return [c.id, cleanDesignations, c.status];
1226
839
  });
@@ -1292,7 +905,7 @@ async function processDataset(dir, register, opts) {
1292
905
  conceptIds: s.conceptIds,
1293
906
  }))
1294
907
  .sort((a, b) => b.conceptCount - a.conceptCount);
1295
- const totalRelationships = Object.values(stats.relTypeCounts).reduce((a, b) => a + b, 0);
908
+ const totalRelationships = Object.values(stats.relTypeCounts).reduce((a: number, b: number) => a + b, 0);
1296
909
 
1297
910
  writeJson(path.join(DATA, register, 'stats.json'), {
1298
911
  sourceCount: sourceStats.length,
@@ -1302,7 +915,7 @@ async function processDataset(dir, register, opts) {
1302
915
  partitiveRelations: stats.partitiveRelations,
1303
916
  });
1304
917
 
1305
- const manifest = {
918
+ const manifest: Record<string, any> = {
1306
919
  id: register,
1307
920
  datasetUri: opts.datasetUri,
1308
921
  uriAliases: opts.uriAliases,
@@ -1571,7 +1184,7 @@ function processNewsPage(config, page) {
1571
1184
 
1572
1185
  for (const file of postFiles) {
1573
1186
  const content = fs.readFileSync(path.join(newsDir, file), 'utf8');
1574
- const frontmatter = {};
1187
+ const frontmatter: Record<string, string> = {};
1575
1188
  const bodyLines = [];
1576
1189
 
1577
1190
  let inFm = false;
@@ -1614,20 +1227,20 @@ function processNewsPage(config, page) {
1614
1227
 
1615
1228
  // --- Markdown-lite renderer (isomorphic, same logic as src/utils/markdown-lite.ts) ---
1616
1229
 
1617
- function renderMarkdown(input) {
1618
- const INLINE_PATTERNS = [
1230
+ function renderMarkdown(input: string): string {
1231
+ const INLINE_PATTERNS: [RegExp, (m: string[]) => string][] = [
1619
1232
  [/\*\*(.+?)\*\*/g, m => `<strong>${m[1]}</strong>`],
1620
1233
  [/(?<!\*)\*([^*]+?)\*(?!\*)/g, m => `<em>${m[1]}</em>`],
1621
1234
  [/`([^`]+?)`/g, m => `<code>${m[1]}</code>`],
1622
1235
  [/\[([^\]]+)\]\(([^)]+)\)/g, m => `<a href="${m[2]}" target="_blank">${m[1]}</a>`],
1623
1236
  ];
1624
- function renderInline(text) {
1237
+ function renderInline(text: string): string {
1625
1238
  for (const [re, fn] of INLINE_PATTERNS) {
1626
- text = text.replace(re, (...args) => fn(args));
1239
+ text = text.replace(re, (...args: string[]) => fn(args));
1627
1240
  }
1628
1241
  return text;
1629
1242
  }
1630
- function escapeHtml(s) {
1243
+ function escapeHtml(s: string): string {
1631
1244
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1632
1245
  }
1633
1246
 
@@ -1684,7 +1297,7 @@ function renderMarkdown(input) {
1684
1297
  return blocks.join('\n');
1685
1298
  }
1686
1299
 
1687
- function processContentPage(config, page) {
1300
+ function processContentPage(config: Record<string, any>, page: Record<string, any>) {
1688
1301
  if (!page.source) {
1689
1302
  console.warn(` Skipping content page '${page.route}': no source file`);
1690
1303
  return;
@@ -1711,7 +1324,7 @@ function processContentPage(config, page) {
1711
1324
 
1712
1325
  // Generate localized versions
1713
1326
  if (page.translations) {
1714
- for (const [lang, tr] of Object.entries(page.translations)) {
1327
+ for (const [lang, tr] of Object.entries(page.translations) as [string, Record<string, any>][]) {
1715
1328
  const { source, title: trTitle } = tr;
1716
1329
  if (!source) continue;
1717
1330
  const trSrcPath = path.resolve(ROOT, source);
@@ -1873,7 +1486,7 @@ writeJson(path.join(PUBLIC, 'site-config.json'), {
1873
1486
  });
1874
1487
  console.log('Generated site-config.json');
1875
1488
 
1876
- const total = Object.values(counts).reduce((s, n) => s + n, 0);
1489
+ const total = Object.values(counts).reduce((s: number, n: number) => s + n, 0);
1877
1490
  console.log(`\nDone! Generated data for ${total} concepts across ${registry.length} datasets.`);
1878
1491
  for (const [id, count] of Object.entries(counts)) {
1879
1492
  console.log(` ${id}: ${count} concepts`);