@glossarist/concept-browser 0.7.135 → 0.7.136

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/scripts/doctor.ts CHANGED
@@ -22,7 +22,9 @@ export const NODE_MIN_MAJOR = 18;
22
22
  /**
23
23
  * @typedef {'pass'|'fail'|'warn'} CheckStatus
24
24
  * @typedef {{ id: string, label: string, status: CheckStatus, detail?: string, hint?: string }} CheckResult
25
- * @typedef {{ cwd: string, pkgRoot: string, datasetsYml?: any, datasetsYmlError?: string }} DoctorContext
25
+ * @typedef {{ id: string, localPath?: string }} RegisteredDataset
26
+ * @typedef {{ registered: RegisteredDataset[], source: 'datasets.yml'|'site-config.yml'|null, error?: string }} DatasetRegistry
27
+ * @typedef {{ cwd: string, pkgRoot: string, datasetsYml?: any, datasetsYmlError?: string, registry: DatasetRegistry }} DoctorContext
26
28
  */
27
29
 
28
30
  /**
@@ -63,8 +65,12 @@ function nodeVersionCheck() {
63
65
  }
64
66
 
65
67
  function packageDepsCheck(ctx) {
66
- const nm = resolve(ctx.pkgRoot, 'node_modules');
67
- if (!existsSync(nm)) {
68
+ // When installed as a library, the CLI's deps may be hoisted into the
69
+ // consumer's node_modules or nested under the package's own — accept
70
+ // either. Checking only PKG_ROOT (the installed package dir) reported
71
+ // false "node_modules missing" in consumer deployments.
72
+ const roots = [resolve(ctx.cwd, 'node_modules'), resolve(ctx.pkgRoot, 'node_modules')];
73
+ if (!roots.some((nm) => existsSync(nm))) {
68
74
  return {
69
75
  id: 'package-deps',
70
76
  label: 'Dependencies installed',
@@ -74,7 +80,7 @@ function packageDepsCheck(ctx) {
74
80
  };
75
81
  }
76
82
  const required = ['vue', 'vue-router', 'pinia', 'vite', 'n3', 'glossarist', '@rdfjs/dataset'];
77
- const missing = required.filter((p) => !existsSync(join(nm, p)));
83
+ const missing = required.filter((p) => !roots.some((nm) => existsSync(join(nm, p))));
78
84
  if (missing.length) {
79
85
  return {
80
86
  id: 'package-deps',
@@ -87,67 +93,103 @@ function packageDepsCheck(ctx) {
87
93
  return { id: 'package-deps', label: 'Dependencies installed', status: 'pass' };
88
94
  }
89
95
 
90
- function datasetsYmlCheck(ctx) {
91
- const path = resolve(ctx.cwd, 'datasets.yml');
92
- if (!existsSync(path)) {
93
- return {
94
- id: 'datasets-yml',
95
- label: 'datasets.yml present',
96
- status: 'warn',
97
- detail: 'file not found at project root',
98
- hint: 'datasets.yml registers every dataset the browser serves',
99
- };
96
+ /**
97
+ * Dataset registrations come from either `datasets.yml` (source repos to
98
+ * fetch) or `site-config.yml` (deployment sites, entries carry
99
+ * `local_path` to in-repo YAML). Doctor only knew the former, so
100
+ * site-config deployments reported "no datasets registered".
101
+ * @returns {{ registered: Array<{id: string, localPath?: string}>, source: 'datasets.yml'|'site-config.yml'|null, error?: string }}
102
+ */
103
+ export function resolveDatasetRegistry(cwd, fileError = undefined) {
104
+ if (fileError) return { registered: [], source: 'datasets.yml', error: fileError };
105
+ const ymlPath = resolve(cwd, 'datasets.yml');
106
+ if (existsSync(ymlPath)) {
107
+ const list = normalizeRegistry(yaml.load(readFileSync(ymlPath, 'utf8')));
108
+ return { registered: list, source: 'datasets.yml' };
109
+ }
110
+ const sitePath = resolve(cwd, 'site-config.yml');
111
+ if (existsSync(sitePath)) {
112
+ try {
113
+ const list = normalizeRegistry(yaml.load(readFileSync(sitePath, 'utf8')));
114
+ return { registered: list, source: 'site-config.yml' };
115
+ } catch (err) {
116
+ return { registered: [], source: 'site-config.yml', error: err.message };
117
+ }
100
118
  }
101
- if (ctx.datasetsYmlError) {
119
+ return { registered: [], source: null };
120
+ }
121
+
122
+ function normalizeRegistry(doc) {
123
+ const datasets = doc?.datasets;
124
+ if (!Array.isArray(datasets)) return [];
125
+ return datasets
126
+ .filter((d) => d && typeof d === 'object' && d.id)
127
+ .map((d) => ({ id: String(d.id), localPath: d.local_path ?? d.localPath }));
128
+ }
129
+
130
+ function datasetsRegisteredCheck(ctx) {
131
+ if (ctx.registry.error) {
102
132
  return {
103
- id: 'datasets-yml',
104
- label: 'datasets.yml parses',
133
+ id: 'datasets-registered',
134
+ label: `${ctx.registry.source} parses`,
105
135
  status: 'fail',
106
- detail: ctx.datasetsYmlError,
136
+ detail: ctx.registry.error,
107
137
  hint: 'fix the YAML syntax error',
108
138
  };
109
139
  }
110
- const datasets = ctx.datasetsYml?.datasets;
111
- if (!Array.isArray(datasets) || datasets.length === 0) {
140
+ if (!ctx.registry.source) {
112
141
  return {
113
- id: 'datasets-yml',
114
- label: 'datasets.yml lists datasets',
142
+ id: 'datasets-registered',
143
+ label: 'Dataset registry present',
115
144
  status: 'warn',
116
- detail: '`datasets` key is empty or missing',
145
+ detail: 'neither datasets.yml nor site-config.yml found at project root',
146
+ hint: 'datasets.yml / site-config.yml register every dataset the browser serves',
147
+ };
148
+ }
149
+ if (ctx.registry.registered.length === 0) {
150
+ return {
151
+ id: 'datasets-registered',
152
+ label: 'Dataset registry lists datasets',
153
+ status: 'warn',
154
+ detail: `\`datasets\` key is empty or missing in ${ctx.registry.source}`,
117
155
  hint: 'add at least one dataset entry; see docs/adding-a-dataset.md',
118
156
  };
119
157
  }
120
158
  return {
121
- id: 'datasets-yml',
122
- label: `datasets.yml lists ${datasets.length} dataset(s)`,
159
+ id: 'datasets-registered',
160
+ label: `${ctx.registry.source} lists ${ctx.registry.registered.length} dataset(s)`,
123
161
  status: 'pass',
124
162
  };
125
163
  }
126
164
 
127
165
  function datasetsFetchedCheck(ctx) {
128
- const datasets = ctx.datasetsYml?.datasets ?? [];
129
- if (!datasets.length) {
166
+ const datasets = ctx.registry.registered;
167
+ if (!ctx.registry.source || !datasets.length) {
130
168
  return { id: 'datasets-fetched', label: 'Source datasets fetched', status: 'warn', detail: 'no datasets registered' };
131
169
  }
132
- const dotDatasets = resolve(ctx.cwd, '.datasets');
170
+ // site-config deployments keep sources in-repo at local_path; the
171
+ // datasets.yml flow fetches source repos into .datasets/{id}/concepts.
133
172
  const missing = datasets
134
- .filter((d) => !existsSync(join(dotDatasets, d.id, 'concepts')))
173
+ .filter((d) => {
174
+ if (d.localPath) return !existsSync(resolve(ctx.cwd, d.localPath));
175
+ return !existsSync(join(resolve(ctx.cwd, '.datasets'), d.id, 'concepts'));
176
+ })
135
177
  .map((d) => d.id);
136
178
  if (missing.length) {
137
179
  return {
138
180
  id: 'datasets-fetched',
139
181
  label: 'Source datasets fetched',
140
182
  status: 'fail',
141
- detail: `.datasets/ missing: ${missing.join(', ')}`,
142
- hint: 'run `npm run fetch-datasets`',
183
+ detail: `dataset sources missing: ${missing.join(', ')}`,
184
+ hint: 'run `npm run fetch-datasets` (or check local_path entries in site-config.yml)',
143
185
  };
144
186
  }
145
187
  return { id: 'datasets-fetched', label: 'Source datasets fetched', status: 'pass' };
146
188
  }
147
189
 
148
190
  function datasetsGeneratedCheck(ctx) {
149
- const datasets = ctx.datasetsYml?.datasets ?? [];
150
- if (!datasets.length) {
191
+ const datasets = ctx.registry.registered;
192
+ if (!ctx.registry.source || !datasets.length) {
151
193
  return { id: 'datasets-generated', label: 'Generated data present', status: 'warn', detail: 'no datasets registered' };
152
194
  }
153
195
  const publicData = resolve(ctx.cwd, 'public', 'data');
@@ -256,7 +298,7 @@ function jsonldContextCheck(ctx) {
256
298
  export const CHECKS = [
257
299
  safe('node-version', nodeVersionCheck),
258
300
  safe('package-deps', packageDepsCheck),
259
- safe('datasets-yml', datasetsYmlCheck),
301
+ safe('datasets-registered', datasetsRegisteredCheck),
260
302
  safe('datasets-fetched', datasetsFetchedCheck),
261
303
  safe('datasets-generated', datasetsGeneratedCheck),
262
304
  safe('public-datasets-json', publicDatasetsJsonCheck),
@@ -280,7 +322,13 @@ export async function runDoctor(cwd = process.cwd()) {
280
322
  }
281
323
  }
282
324
  /** @type {DoctorContext} */
283
- const ctx = { cwd, pkgRoot: PKG_ROOT, datasetsYml, datasetsYmlError };
325
+ const ctx = {
326
+ cwd,
327
+ pkgRoot: PKG_ROOT,
328
+ datasetsYml,
329
+ datasetsYmlError,
330
+ registry: resolveDatasetRegistry(cwd, datasetsYmlError),
331
+ };
284
332
 
285
333
  const results = [];
286
334
  for (const check of CHECKS) {
@@ -2,7 +2,16 @@
2
2
  import fs2 from "fs";
3
3
  import path3 from "path";
4
4
  import yaml4 from "js-yaml";
5
- import { naturalSort, Register, parseMentions, InvalidMentionError } from "glossarist";
5
+ import {
6
+ naturalSort,
7
+ Register,
8
+ parseMentions,
9
+ InvalidMentionError,
10
+ parseConceptYaml,
11
+ conceptToTurtle,
12
+ conceptsToTbx,
13
+ emitOutputSet
14
+ } from "glossarist";
6
15
 
7
16
  // scripts/load-site-config.ts
8
17
  import { readFileSync, existsSync } from "fs";
@@ -546,6 +555,9 @@ function normalizeBibliography(raw) {
546
555
  function buildConceptUri(uriBase, registerId, conceptId) {
547
556
  return `${uriBase}/dataset/${registerId}/concept/${conceptId}`;
548
557
  }
558
+ function buildConceptUriPrefix(uriBase, registerId) {
559
+ return `${uriBase}/dataset/${registerId}/concept/`;
560
+ }
549
561
 
550
562
  // scripts/lib/harmonize-concept.ts
551
563
  import fs from "fs";
@@ -782,301 +794,6 @@ function refsToJsonLd(refs, refMaps2) {
782
794
  }).filter((r) => r["@id"]);
783
795
  }
784
796
 
785
- // scripts/lib/turtle-escape.ts
786
- var STRING_ESCAPES = {
787
- "\\": "\\\\",
788
- '"': '\\"',
789
- "\n": "\\n",
790
- "\r": "\\r",
791
- " ": "\\t"
792
- };
793
- function ttlLit(s) {
794
- if (s == null) return '""';
795
- const escaped = String(s).replace(/[\\\"\n\r\t]/g, (ch) => STRING_ESCAPES[ch] ?? ch);
796
- return `"${escaped}"`;
797
- }
798
-
799
- // scripts/lib/concept-formats.ts
800
- function escapeTurtle(s) {
801
- return ttlLit(s).slice(1, -1);
802
- }
803
- function escapeXml(s) {
804
- const str = Array.isArray(s) ? s.join(", ") : String(s ?? "");
805
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
806
- }
807
- function conceptJsonToTurtle(concept) {
808
- const uri = concept["@id"] || "";
809
- const id = concept["gl:identifier"] || "";
810
- const lines = [
811
- "@prefix skos: <http://www.w3.org/2004/02/skos/core#> .",
812
- "@prefix dcterms: <http://purl.org/dc/terms/> .",
813
- ""
814
- ];
815
- const props = [" a skos:Concept"];
816
- props.push(` skos:notation "${escapeTurtle(id)}"`);
817
- for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
818
- if (lc["gl:designation"]) {
819
- for (const d of lc["gl:designation"]) {
820
- const term = d["gl:term"];
821
- if (!term) continue;
822
- const pred = d["gl:normativeStatus"] === "preferred" ? "skos:prefLabel" : "skos:altLabel";
823
- props.push(` ${pred} "${escapeTurtle(term)}"@${lang}`);
824
- }
825
- }
826
- if (lc["gl:definition"]) {
827
- for (const d of lc["gl:definition"]) {
828
- if (d["gl:content"]) props.push(` skos:definition "${escapeTurtle(d["gl:content"])}"@${lang}`);
829
- }
830
- }
831
- if (lc["gl:notes"]) {
832
- for (const d of lc["gl:notes"]) {
833
- if (d["gl:content"]) props.push(` skos:scopeNote "${escapeTurtle(d["gl:content"])}"@${lang}`);
834
- }
835
- }
836
- }
837
- lines.push(`<${uri}>`);
838
- lines.push(props.join(" ;\n"));
839
- lines.push(" .");
840
- return lines.join("\n");
841
- }
842
- var SKOS_JSON_LD_CONTEXT = {
843
- skos: "http://www.w3.org/2004/02/skos/core#",
844
- dcterms: "http://purl.org/dc/terms/",
845
- "@language": { "@container": "@language" }
846
- };
847
- function buildSkosJsonLdObject(concept) {
848
- const uri = concept["@id"] || "";
849
- const id = concept["gl:identifier"] || "";
850
- const doc = {
851
- "@id": uri,
852
- "@type": "skos:Concept",
853
- "skos:notation": id
854
- };
855
- const prefLabels = {};
856
- const altLabels = {};
857
- const definitions = {};
858
- const scopeNotes = {};
859
- for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
860
- const descs = lc["gl:designation"] || [];
861
- const pref = descs.find((d) => d["gl:normativeStatus"] === "preferred" && d["gl:term"]);
862
- const alt = descs.find((d) => d["gl:normativeStatus"] !== "preferred" && d["gl:term"]);
863
- if (pref) prefLabels[lang] = pref["gl:term"];
864
- if (alt) altLabels[lang] = alt["gl:term"];
865
- const def = (lc["gl:definition"] || [])[0];
866
- if (def?.["gl:content"]) definitions[lang] = def["gl:content"];
867
- const note = (lc["gl:notes"] || [])[0];
868
- if (note?.["gl:content"]) scopeNotes[lang] = note["gl:content"];
869
- }
870
- if (Object.keys(prefLabels).length) doc["skos:prefLabel"] = prefLabels;
871
- if (Object.keys(altLabels).length) doc["skos:altLabel"] = altLabels;
872
- if (Object.keys(definitions).length) doc["skos:definition"] = definitions;
873
- if (Object.keys(scopeNotes).length) doc["skos:scopeNote"] = scopeNotes;
874
- return doc;
875
- }
876
- function conceptJsonToSkosJsonLd(concept) {
877
- return JSON.stringify({
878
- "@context": SKOS_JSON_LD_CONTEXT,
879
- ...buildSkosJsonLdObject(concept)
880
- });
881
- }
882
- function conceptsToSkosJsonLdGraph(concepts) {
883
- return JSON.stringify(
884
- {
885
- "@context": SKOS_JSON_LD_CONTEXT,
886
- "@graph": concepts.map(buildSkosJsonLdObject)
887
- },
888
- null,
889
- 2
890
- );
891
- }
892
- var CSV_COLUMNS = [
893
- "termid",
894
- "uri",
895
- "status",
896
- "section",
897
- "language",
898
- "term",
899
- "alt_terms",
900
- "definition",
901
- "notes",
902
- "examples",
903
- "sources",
904
- "source_links"
905
- ];
906
- function csvEscape(value) {
907
- if (/[",\r\n]/.test(value)) {
908
- return `"${value.replace(/"/g, '""')}"`;
909
- }
910
- return value;
911
- }
912
- function joinLines(items) {
913
- return items.filter(Boolean).join("\n");
914
- }
915
- function localizedLanguages(localized, languageOrder) {
916
- const present = Object.keys(localized);
917
- if (!languageOrder?.length) return present;
918
- const ordered = languageOrder.filter((lang) => present.includes(lang));
919
- return ordered.concat(present.filter((lang) => !ordered.includes(lang)));
920
- }
921
- function sourceRefString(src) {
922
- const ref = src?.["gl:origin"]?.["gl:ref"];
923
- if (!ref) return "";
924
- return [ref["gl:source"], ref["gl:id"]].filter(Boolean).join(": ");
925
- }
926
- function sourceLinkString(src) {
927
- return src?.["gl:origin"]?.["gl:link"] || "";
928
- }
929
- function conceptsToCsv(concepts, opts = {}) {
930
- const rows = [CSV_COLUMNS.join(",")];
931
- for (const concept of concepts) {
932
- const localized = concept["gl:localizedConcept"] || {};
933
- const domains = (concept["gl:domain"] || []).map((d) => d["gl:conceptId"]).filter(Boolean).join("; ");
934
- const managedSources = concept["gl:source"] || [];
935
- const shared = {
936
- termid: concept["gl:identifier"] || "",
937
- uri: concept["@id"] || "",
938
- status: concept["gl:status"] || "",
939
- section: domains
940
- };
941
- const languages = localizedLanguages(localized, opts.languageOrder);
942
- if (languages.length === 0) {
943
- rows.push([shared.termid, shared.uri, shared.status, shared.section, "", "", "", "", "", "", "", ""].map(csvEscape).join(","));
944
- continue;
945
- }
946
- for (const lang of languages) {
947
- const lc = localized[lang] || {};
948
- const designations = (lc["gl:designation"] || []).filter((d) => d["gl:term"]);
949
- const preferred = designations.find((d) => d["gl:normativeStatus"] === "preferred") || designations[0];
950
- const altTerms = designations.filter((d) => d !== preferred).map((d) => d["gl:term"]);
951
- const localizedSources = lc["gl:source"]?.length ? lc["gl:source"] : managedSources;
952
- rows.push([
953
- shared.termid,
954
- shared.uri,
955
- shared.status,
956
- shared.section,
957
- lang,
958
- preferred?.["gl:term"] || "",
959
- altTerms.join("; "),
960
- joinLines((lc["gl:definition"] || []).map((d) => d["gl:content"])),
961
- joinLines((lc["gl:notes"] || []).map((d) => d["gl:content"])),
962
- joinLines((lc["gl:examples"] || []).map((d) => d["gl:content"])),
963
- localizedSources.map(sourceRefString).filter(Boolean).join("; "),
964
- localizedSources.map(sourceLinkString).filter(Boolean).join("; ")
965
- ].map(csvEscape).join(","));
966
- }
967
- }
968
- return "\uFEFF" + rows.join("\r\n") + "\r\n";
969
- }
970
- function conceptJsonToTbx(concept) {
971
- const id = concept["gl:identifier"] || "";
972
- const uri = concept["@id"] || "";
973
- const localized = concept["gl:localizedConcept"] || {};
974
- const langSections = [];
975
- for (const [lang, lc] of Object.entries(localized)) {
976
- const descs = lc["gl:designation"] || [];
977
- const definitions = (lc["gl:definition"] || []).filter((d) => d["gl:content"]);
978
- const notes = (lc["gl:notes"] || []).filter((d) => d["gl:content"]);
979
- const examples = (lc["gl:examples"] || []).filter((d) => d["gl:content"]);
980
- const sources = lc["gl:source"] || [];
981
- const entryStatus = lc["gl:entryStatus"] || "";
982
- if (!descs.length && !definitions.length) continue;
983
- const termEntries = [];
984
- for (const d of descs) {
985
- const term = d["gl:term"];
986
- if (!term) continue;
987
- const status = d["gl:normativeStatus"] || "";
988
- const type = d["@type"] || "";
989
- let gramGrp = "";
990
- if (d["gl:grammarInfo"] && d["gl:grammarInfo"].length > 0) {
991
- const gi = d["gl:grammarInfo"][0];
992
- if (gi["gl:gender"]) gramGrp = `
993
- <grammaticalGender>${escapeXml(gi["gl:gender"])}</grammaticalGender>`;
994
- if (gi["gl:number"]) gramGrp += `
995
- <grammaticalNumber>${escapeXml(gi["gl:number"])}</grammaticalNumber>`;
996
- for (const pos of ["noun", "verb", "adj", "adverb", "preposition", "participle"]) {
997
- if (gi[`gl:${pos}`]) gramGrp += `
998
- <partOfSpeech>${pos}</partOfSpeech>`;
999
- }
1000
- }
1001
- let posBlock = "";
1002
- if (type.includes("Abbreviation")) posBlock = "\n <partOfSpeech>abbreviation</partOfSpeech>";
1003
- if (type.includes("Symbol")) posBlock = "\n <partOfSpeech>symbol</partOfSpeech>";
1004
- termEntries.push(` <termEntry>
1005
- <langSet xml:lang="${lang}">
1006
- <tig>
1007
- <term>${escapeXml(term)}</term>${gramGrp}${posBlock}
1008
- </tig>
1009
- </langSet>
1010
- </termEntry>`);
1011
- }
1012
- let defBlock = "";
1013
- if (definitions.length) {
1014
- const defParts = definitions.map((d) => ` <p>${escapeXml(d["gl:content"])}</p>`).join("\n");
1015
- defBlock = `
1016
- <descrip type="definition">
1017
- ${defParts}
1018
- </descrip>`;
1019
- }
1020
- let noteBlock = "";
1021
- for (let i = 0; i < notes.length; i++) {
1022
- noteBlock += `
1023
- <note type="note">${escapeXml(notes[i]["gl:content"])}</note>`;
1024
- }
1025
- for (let i = 0; i < examples.length; i++) {
1026
- noteBlock += `
1027
- <note type="example">${escapeXml(examples[i]["gl:content"])}</note>`;
1028
- }
1029
- let sourceBlock = "";
1030
- for (const src of sources) {
1031
- const origin = src["gl:origin"] || {};
1032
- const parts = [];
1033
- const ref = origin["gl:ref"];
1034
- if (ref) {
1035
- const refParts = [];
1036
- if (ref["gl:source"]) refParts.push(ref["gl:source"]);
1037
- if (ref["gl:id"]) refParts.push(ref["gl:id"]);
1038
- parts.push(refParts.join(" ") || "");
1039
- }
1040
- if (origin["gl:locality"]) {
1041
- const loc = origin["gl:locality"];
1042
- if (loc["gl:referenceFrom"]) parts.push(loc["gl:localityType"] ? `${loc["gl:localityType"]} ${loc["gl:referenceFrom"]}` : loc["gl:referenceFrom"]);
1043
- }
1044
- if (parts.filter(Boolean).length) {
1045
- sourceBlock += `
1046
- <ref>${escapeXml(parts.filter(Boolean).join(", "))}</ref>`;
1047
- }
1048
- }
1049
- let statusBlock = "";
1050
- if (entryStatus) {
1051
- statusBlock += `
1052
- <descrip type="entryStatus">${escapeXml(entryStatus)}</descrip>`;
1053
- }
1054
- const termEntriesBlock = termEntries.length ? "\n" + termEntries.join("\n") : "";
1055
- langSections.push({ lang, termEntries, blocks: [defBlock, noteBlock, sourceBlock, statusBlock].filter((b) => b).join("") });
1056
- }
1057
- if (!langSections.length) return "";
1058
- const bodyEntries = langSections.map((ls) => {
1059
- return ` <languageSection xml:lang="${ls.lang}">${ls.blocks}
1060
- </languageSection>`;
1061
- }).join("\n");
1062
- return `<?xml version="1.0" encoding="UTF-8"?>
1063
- <tbx style="dca" type="TBX-Basic" xml:lang="en" xmlns="urn:iso:std:iso:30042:ed-2">
1064
- <tbxHeader>
1065
- <fileDesc>
1066
- <source>${escapeXml(uri)}</source>
1067
- </fileDesc>
1068
- </tbxHeader>
1069
- <text>
1070
- <body>
1071
- <conceptEntry id="${escapeXml(id)}">
1072
- ${bodyEntries}
1073
- </conceptEntry>
1074
- </body>
1075
- </text>
1076
- </tbx>
1077
- `;
1078
- }
1079
-
1080
797
  // scripts/generate-data.ts
1081
798
  var __dirname3 = path3.dirname(new URL(import.meta.url).pathname);
1082
799
  var ROOT = process.cwd();
@@ -1512,7 +1229,7 @@ async function processDataset(dir, register, opts) {
1512
1229
  for (const f of fs2.readdirSync(conceptsDir)) fs2.unlinkSync(path3.join(conceptsDir, f));
1513
1230
  }
1514
1231
  const concepts = [];
1515
- const jsonlds = [];
1232
+ const conceptInstances = [];
1516
1233
  const langTermCounts = {};
1517
1234
  const langDefCounts = {};
1518
1235
  const availableFormats = ["ttl", "jsonld", "yaml", "tbx", "csv"];
@@ -1617,14 +1334,28 @@ async function processDataset(dir, register, opts) {
1617
1334
  const termid = String(conceptYaml.termid);
1618
1335
  const jsonld = yamlToJsonLd(conceptYaml, register, dsRefMaps);
1619
1336
  writeJson(path3.join(conceptsDir, `${termid}.json`), jsonld);
1620
- jsonlds.push(jsonld);
1621
- const ttlContent = conceptJsonToTurtle(jsonld);
1622
- fs2.writeFileSync(path3.join(conceptsDir, `${termid}.ttl`), ttlContent);
1623
- const skosJsonLd = conceptJsonToSkosJsonLd(jsonld);
1624
- fs2.writeFileSync(path3.join(conceptsDir, `${termid}.jsonld`), skosJsonLd);
1625
- const tbxContent = conceptJsonToTbx(jsonld);
1626
- if (tbxContent) {
1627
- fs2.writeFileSync(path3.join(conceptsDir, `${termid}.tbx`), tbxContent);
1337
+ let conceptInstance = null;
1338
+ try {
1339
+ conceptInstance = parseConceptYaml(fs2.readFileSync(path3.join(dir, file), "utf8"), termid);
1340
+ } catch (e) {
1341
+ console.warn(` ${termid}: canonical parse failed (${e.message}); skipping glossarist exports`);
1342
+ }
1343
+ if (conceptInstance) {
1344
+ conceptInstances.push(conceptInstance);
1345
+ fs2.writeFileSync(
1346
+ path3.join(conceptsDir, `${termid}.ttl`),
1347
+ conceptToTurtle(conceptInstance, { uriBase: refMaps.uriBase, registerId: register })
1348
+ );
1349
+ const conceptJsonLd = await emitOutputSet([conceptInstance], {
1350
+ registerId: register,
1351
+ uriBase: refMaps.uriBase,
1352
+ formats: ["jsonld"]
1353
+ });
1354
+ fs2.writeFileSync(path3.join(conceptsDir, `${termid}.jsonld`), conceptJsonLd.jsonld);
1355
+ const tbxContent = conceptsToTbx([conceptInstance], { registerId: register });
1356
+ if (tbxContent) {
1357
+ fs2.writeFileSync(path3.join(conceptsDir, `${termid}.tbx`), tbxContent);
1358
+ }
1628
1359
  }
1629
1360
  fs2.copyFileSync(path3.join(dir, file), path3.join(conceptsDir, `${termid}.yaml`));
1630
1361
  concepts.push({
@@ -1640,8 +1371,13 @@ async function processDataset(dir, register, opts) {
1640
1371
  console.warn(` Skipping ${file}: ${e.message}`);
1641
1372
  }
1642
1373
  }
1643
- fs2.writeFileSync(path3.join(DATA, register, `${register}.csv`), conceptsToCsv(jsonlds, { languageOrder: opts.languageOrder || opts.languages }));
1644
- fs2.writeFileSync(path3.join(DATA, register, `${register}.jsonld`), conceptsToSkosJsonLdGraph(jsonlds));
1374
+ const aggregate = await emitOutputSet(conceptInstances, {
1375
+ registerId: register,
1376
+ uriBase: refMaps.uriBase,
1377
+ languageOrder: opts.languageOrder || opts.languages
1378
+ });
1379
+ fs2.writeFileSync(path3.join(DATA, register, `${register}.csv`), aggregate.csv);
1380
+ fs2.writeFileSync(path3.join(DATA, register, `${register}.jsonld`), aggregate.jsonld);
1645
1381
  const CHUNK_SIZE = 500;
1646
1382
  const chunks = [];
1647
1383
  for (let i = 0; i < concepts.length; i += CHUNK_SIZE) {
@@ -1684,7 +1420,7 @@ async function processDataset(dir, register, opts) {
1684
1420
  fs2.writeFileSync(
1685
1421
  path3.join(DATA, register, "graph-nodes.json"),
1686
1422
  JSON.stringify({
1687
- uriPrefix: `${refMaps.uriBase}/${register}/concept/`,
1423
+ uriPrefix: buildConceptUriPrefix(refMaps.uriBase, register),
1688
1424
  registerId: register,
1689
1425
  nodes: graphNodeEntries
1690
1426
  })