@glossarist/concept-browser 0.7.115 → 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.
- package/cli/index.mjs +12 -15
- package/package.json +5 -1
- package/scripts/bridge-to-astro.js +132 -0
- package/scripts/build-edges.js +480 -0
- package/scripts/build-scripts.mjs +22 -0
- package/scripts/check-syntax.js +81 -0
- package/scripts/doctor.js +278 -0
- package/scripts/extract-source-refs.js +24 -0
- package/scripts/fetch-datasets.js +278 -0
- package/scripts/generate-404.js +14 -0
- package/scripts/generate-data.js +2220 -0
- package/scripts/generate-ontology-data.js +139 -0
- package/scripts/generate-ontology-schema.js +472 -0
- package/scripts/load-site-config.js +83 -0
- package/scripts/migrate-v1-to-v3.js +113 -0
- package/scripts/normalize-yaml.js +96 -0
- package/scripts/process-about-pages.js +241 -0
- package/scripts/smoke.js +211 -0
- package/scripts/sync-concept-model.js +63 -0
- package/scripts/validate-shacl.js +152 -0
- package/scripts/validate-yaml.js +132 -0
- package/src/__tests__/published-package-contract.test.ts +84 -0
- package/src/__tests__/scripts-syntax.test.ts +2 -2
- package/src/data/ontology-schema.json +1336 -183
|
@@ -0,0 +1,2220 @@
|
|
|
1
|
+
// scripts/generate-data.ts
|
|
2
|
+
import fs2 from "fs";
|
|
3
|
+
import path3 from "path";
|
|
4
|
+
import yaml4 from "js-yaml";
|
|
5
|
+
import { naturalSort, Register, parseMention } from "glossarist";
|
|
6
|
+
|
|
7
|
+
// scripts/load-site-config.ts
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { resolve, dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import yaml from "js-yaml";
|
|
12
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
var projectRoot = resolve(__dirname, "..");
|
|
14
|
+
var SNAKE_TO_CAMEL = {
|
|
15
|
+
// Site-level
|
|
16
|
+
uri_base: "uriBase",
|
|
17
|
+
base_path: "basePath",
|
|
18
|
+
ui_languages: "uiLanguages",
|
|
19
|
+
dataset_groups: "datasetGroups",
|
|
20
|
+
dataset_translations: "datasetTranslations",
|
|
21
|
+
primary_color: "primaryColor",
|
|
22
|
+
dark_color: "darkColor",
|
|
23
|
+
owner_name: "ownerName",
|
|
24
|
+
owner_url: "ownerUrl",
|
|
25
|
+
powered_by: "poweredBy",
|
|
26
|
+
footer_nav: "footerNav",
|
|
27
|
+
footer_logo: "footerLogo",
|
|
28
|
+
news_dir: "newsDir",
|
|
29
|
+
// Dataset-level
|
|
30
|
+
gcr_package: "gcrPackage",
|
|
31
|
+
source_repo: "sourceRepo",
|
|
32
|
+
local_path: "localPath",
|
|
33
|
+
local_light: "localLight",
|
|
34
|
+
local_dark: "localDark",
|
|
35
|
+
uri_aliases: "uriAliases",
|
|
36
|
+
language_order: "languageOrder",
|
|
37
|
+
ref_aliases: "refAliases",
|
|
38
|
+
urn_aliases: "urnAliases",
|
|
39
|
+
existing_site_url: "existingSiteUrl",
|
|
40
|
+
edition_status: "editionStatus",
|
|
41
|
+
dataset_uri: "datasetUri",
|
|
42
|
+
schema_version: "schemaVersion"
|
|
43
|
+
};
|
|
44
|
+
function normalizeKey(key) {
|
|
45
|
+
if (typeof key !== "string") return key;
|
|
46
|
+
if (Object.prototype.hasOwnProperty.call(SNAKE_TO_CAMEL, key)) {
|
|
47
|
+
return SNAKE_TO_CAMEL[key];
|
|
48
|
+
}
|
|
49
|
+
if (key.includes("_") && /^[a-z][a-z0-9_]*$/.test(key)) {
|
|
50
|
+
return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
51
|
+
}
|
|
52
|
+
return key;
|
|
53
|
+
}
|
|
54
|
+
function normalizeObject(obj) {
|
|
55
|
+
if (Array.isArray(obj)) return obj.map(normalizeObject);
|
|
56
|
+
if (obj && typeof obj === "object") {
|
|
57
|
+
const out = {};
|
|
58
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
59
|
+
out[normalizeKey(k)] = normalizeObject(v);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
return obj;
|
|
64
|
+
}
|
|
65
|
+
function findConfigFile(args = []) {
|
|
66
|
+
if (process.env.SITE_CONFIG) {
|
|
67
|
+
return resolve(process.env.SITE_CONFIG);
|
|
68
|
+
}
|
|
69
|
+
for (const dir of [process.cwd(), projectRoot]) {
|
|
70
|
+
const p = resolve(dir, "site-config.yml");
|
|
71
|
+
if (existsSync(p)) return p;
|
|
72
|
+
}
|
|
73
|
+
throw new Error("No site config found. Set SITE_CONFIG or create site-config.yml");
|
|
74
|
+
}
|
|
75
|
+
function loadSiteConfig(args = []) {
|
|
76
|
+
const configPath = findConfigFile(args);
|
|
77
|
+
const raw = yaml.load(readFileSync(configPath, "utf-8"));
|
|
78
|
+
const config2 = normalizeObject(raw);
|
|
79
|
+
return { config: config2, configPath };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// scripts/lib/concept-groups.ts
|
|
83
|
+
function getGroups(conceptYaml) {
|
|
84
|
+
if (conceptYaml.eng && conceptYaml.eng.groups) return conceptYaml.eng.groups;
|
|
85
|
+
if (conceptYaml._domains) {
|
|
86
|
+
const sectionIds = conceptYaml._domains.filter((d) => d.ref_type === "section" && d.concept_id).map((d) => d.concept_id.replace(/^section-/, ""));
|
|
87
|
+
if (sectionIds.length) return sectionIds;
|
|
88
|
+
}
|
|
89
|
+
const termid = String(conceptYaml.termid);
|
|
90
|
+
if (/^\d{3}-/.test(termid)) return [termid.substring(0, 3)];
|
|
91
|
+
if (/^\d+\.\d+\.\d+/.test(termid)) {
|
|
92
|
+
const parts = termid.split(".");
|
|
93
|
+
return [`${parts[0]}.${parts[1]}.${parts[2]}`];
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// scripts/lib/build/non-verbal-consumer.ts
|
|
99
|
+
import { readFile, writeFile, mkdir, readdir, stat } from "node:fs/promises";
|
|
100
|
+
import path from "node:path";
|
|
101
|
+
import yaml2 from "js-yaml";
|
|
102
|
+
var KIND_META = {
|
|
103
|
+
figure: { dir: "figures", type: "Figure" },
|
|
104
|
+
table: { dir: "tables", type: "Table" },
|
|
105
|
+
formula: { dir: "formulas", type: "Formula" }
|
|
106
|
+
};
|
|
107
|
+
function yamlEntityToJsonLd(yaml5, kind) {
|
|
108
|
+
const meta = KIND_META[kind];
|
|
109
|
+
const doc = {
|
|
110
|
+
"@type": `gl:${meta.type}`,
|
|
111
|
+
"gl:id": yaml5.id
|
|
112
|
+
};
|
|
113
|
+
if (yaml5.identifier) doc["gl:identifier"] = yaml5.identifier;
|
|
114
|
+
if (yaml5.caption) doc["gl:caption"] = yaml5.caption;
|
|
115
|
+
if (yaml5.alt) doc["gl:altText"] = yaml5.alt;
|
|
116
|
+
if (yaml5.description) doc["gl:description"] = yaml5.description;
|
|
117
|
+
if (Array.isArray(yaml5.sources) && yaml5.sources.length) {
|
|
118
|
+
doc["gl:source"] = yaml5.sources.map((s) => yamlSourceToJsonLd(s));
|
|
119
|
+
}
|
|
120
|
+
if (kind === "figure") {
|
|
121
|
+
if (Array.isArray(yaml5.images)) {
|
|
122
|
+
doc["gl:image"] = yaml5.images.map((img) => ({
|
|
123
|
+
"gl:src": img.src,
|
|
124
|
+
"gl:format": img.format,
|
|
125
|
+
...img.role ? { "gl:role": img.role } : {},
|
|
126
|
+
...img.width != null ? { "gl:width": img.width } : {},
|
|
127
|
+
...img.height != null ? { "gl:height": img.height } : {},
|
|
128
|
+
...img.scale != null ? { "gl:scale": img.scale } : {}
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(yaml5.subfigures) && yaml5.subfigures.length) {
|
|
132
|
+
doc["gl:subfigure"] = yaml5.subfigures.map((sub) => yamlEntityToJsonLd(sub, "figure"));
|
|
133
|
+
}
|
|
134
|
+
} else if (kind === "table") {
|
|
135
|
+
if (yaml5.content) {
|
|
136
|
+
const c = { ...yaml5.content };
|
|
137
|
+
if (c.type) {
|
|
138
|
+
c["gl:type"] = c.type;
|
|
139
|
+
delete c.type;
|
|
140
|
+
}
|
|
141
|
+
doc["gl:content"] = c;
|
|
142
|
+
}
|
|
143
|
+
if (yaml5.format) doc["gl:format"] = yaml5.format;
|
|
144
|
+
} else if (kind === "formula") {
|
|
145
|
+
if (yaml5.expression) doc["gl:expression"] = yaml5.expression;
|
|
146
|
+
if (yaml5.notation) doc["gl:notation"] = yaml5.notation;
|
|
147
|
+
}
|
|
148
|
+
return doc;
|
|
149
|
+
}
|
|
150
|
+
function yamlSourceToJsonLd(s) {
|
|
151
|
+
const out = {};
|
|
152
|
+
if (s.id) out["gl:id"] = s.id;
|
|
153
|
+
if (s.type) out["gl:sourceType"] = s.type;
|
|
154
|
+
if (s.status) out["gl:sourceStatus"] = s.status;
|
|
155
|
+
if (s.modification) out["gl:modification"] = s.modification;
|
|
156
|
+
if (s.origin) {
|
|
157
|
+
const o = {};
|
|
158
|
+
if (s.origin.ref) {
|
|
159
|
+
o["gl:ref"] = typeof s.origin.ref === "string" ? s.origin.ref : {
|
|
160
|
+
...s.origin.ref.source ? { "gl:source": s.origin.ref.source } : {},
|
|
161
|
+
...s.origin.ref.id ? { "gl:id": s.origin.ref.id } : {},
|
|
162
|
+
...s.origin.ref.version ? { "gl:version": s.origin.ref.version } : {},
|
|
163
|
+
...s.origin.ref.text ? { "gl:text": s.origin.ref.text } : {}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (s.origin.locality) {
|
|
167
|
+
const l = s.origin.locality;
|
|
168
|
+
o["gl:locality"] = {
|
|
169
|
+
...l.type ? { "gl:localityType": l.type } : {},
|
|
170
|
+
...l.reference_from ? { "gl:referenceFrom": l.reference_from } : {},
|
|
171
|
+
...l.reference_to ? { "gl:referenceTo": l.reference_to } : {}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (s.origin.link) o["gl:link"] = s.origin.link;
|
|
175
|
+
if (Object.keys(o).length) out["gl:origin"] = o;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
async function readEntitiesForKind(sourceDir, kind) {
|
|
180
|
+
const meta = KIND_META[kind];
|
|
181
|
+
const dir = path.join(sourceDir, meta.dir);
|
|
182
|
+
let files;
|
|
183
|
+
try {
|
|
184
|
+
files = await readdir(dir);
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const out = [];
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
const full = path.join(dir, file);
|
|
191
|
+
const st = await stat(full);
|
|
192
|
+
if (!st.isFile()) continue;
|
|
193
|
+
const ext = path.extname(file).toLowerCase();
|
|
194
|
+
const id = path.basename(file, ext);
|
|
195
|
+
if (ext === ".json") {
|
|
196
|
+
try {
|
|
197
|
+
const doc = JSON.parse(await readFile(full, "utf8"));
|
|
198
|
+
out.push({ id, doc });
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.warn(` Warning: failed to parse ${full}: ${e.message}`);
|
|
201
|
+
}
|
|
202
|
+
} else if (ext === ".yaml" || ext === ".yml") {
|
|
203
|
+
try {
|
|
204
|
+
const raw = yaml2.load(await readFile(full, "utf8"));
|
|
205
|
+
if (!raw || typeof raw !== "object") continue;
|
|
206
|
+
out.push({ id, doc: yamlEntityToJsonLd(raw, kind) });
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.warn(` Warning: failed to parse ${full}: ${e.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
async function consumeDatasetEntities(sourceRoot, destRoot) {
|
|
215
|
+
const counts2 = { figures: 0, tables: 0, formulas: 0 };
|
|
216
|
+
const warnings = [];
|
|
217
|
+
for (
|
|
218
|
+
const kind of
|
|
219
|
+
/** @type {NonVerbalKind[]} */
|
|
220
|
+
["figure", "table", "formula"]
|
|
221
|
+
) {
|
|
222
|
+
const meta = KIND_META[kind];
|
|
223
|
+
const entities = await readEntitiesForKind(sourceRoot, kind);
|
|
224
|
+
if (entities.length === 0) continue;
|
|
225
|
+
const outDir = path.join(destRoot, meta.dir);
|
|
226
|
+
await mkdir(outDir, { recursive: true });
|
|
227
|
+
const index = {};
|
|
228
|
+
for (const { id, doc } of entities) {
|
|
229
|
+
const docId = doc["gl:id"] ?? doc["gloss:id"] ?? id;
|
|
230
|
+
if (docId !== id) {
|
|
231
|
+
warnings.push(`${meta.dir}/${id}: gl:id (${docId}) does not match filename`);
|
|
232
|
+
}
|
|
233
|
+
const dest = path.join(outDir, `${id}.json`);
|
|
234
|
+
await writeFile(dest, JSON.stringify(doc, null, 2));
|
|
235
|
+
index[id] = `${meta.dir}/${id}.json`;
|
|
236
|
+
if (!doc[`gl:${kind === "figure" ? "altText" : ""}`] && kind === "figure") {
|
|
237
|
+
warnings.push(`${meta.dir}/${id}: missing gl:altText (accessibility)`);
|
|
238
|
+
}
|
|
239
|
+
if (!doc["gl:caption"]) {
|
|
240
|
+
warnings.push(`${meta.dir}/${id}: missing gl:caption`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const indexDest = path.join(destRoot, `${meta.dir}-index.json`);
|
|
244
|
+
await writeFile(indexDest, JSON.stringify(index, null, 2));
|
|
245
|
+
counts2[meta.dir] = entities.length;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
figures: counts2.figures,
|
|
249
|
+
tables: counts2.tables,
|
|
250
|
+
formulas: counts2.formulas,
|
|
251
|
+
warnings
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// scripts/lib/build/image-assets.ts
|
|
256
|
+
import { createHash } from "node:crypto";
|
|
257
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, readdir as readdir2, stat as stat2, copyFile } from "node:fs/promises";
|
|
258
|
+
import path2 from "node:path";
|
|
259
|
+
var SUPPORTED_FORMATS = /* @__PURE__ */ new Set(["svg", "png", "jpg", "jpeg", "gif", "webp", "avif"]);
|
|
260
|
+
var MAGIC_BYTES = {
|
|
261
|
+
png: [137, 80, 78, 71, 13, 10, 26, 10],
|
|
262
|
+
jpg: [255, 216, 255],
|
|
263
|
+
gif: [71, 73, 70, 56],
|
|
264
|
+
webp: [82, 73, 70, 70]
|
|
265
|
+
// "RIFF" — full match needs bytes 8-11 = "WEBP"
|
|
266
|
+
};
|
|
267
|
+
var FILENAME_RE = /^[a-z0-9._-]+$/i;
|
|
268
|
+
function detectFormat(buf, ext) {
|
|
269
|
+
const e = ext.toLowerCase();
|
|
270
|
+
if (e === "svg") return buf.includes("<svg") || buf.includes("<SVG") ? "svg" : null;
|
|
271
|
+
if (e === "png" || e === "jpg" || e === "jpeg" || e === "gif" || e === "webp") {
|
|
272
|
+
const png = MAGIC_BYTES.png;
|
|
273
|
+
if (e === "png" && buf.length >= png.length && png.every((b, i) => buf[i] === b)) return "png";
|
|
274
|
+
const jpg = MAGIC_BYTES.jpg;
|
|
275
|
+
if ((e === "jpg" || e === "jpeg") && buf.length >= jpg.length && jpg.every((b, i) => buf[i] === b)) return "jpg";
|
|
276
|
+
const gif = MAGIC_BYTES.gif;
|
|
277
|
+
if (e === "gif" && buf.length >= gif.length && gif.every((b, i) => buf[i] === b)) return "gif";
|
|
278
|
+
if (e === "webp" && buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP") return "webp";
|
|
279
|
+
}
|
|
280
|
+
if (SUPPORTED_FORMATS.has(e)) return e;
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
function readIntrinsicDimensions(buf, format) {
|
|
284
|
+
if (format === "png" && buf.length >= 24) {
|
|
285
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
286
|
+
}
|
|
287
|
+
if (format === "jpg" || format === "jpeg") {
|
|
288
|
+
return readJpegDimensions(buf);
|
|
289
|
+
}
|
|
290
|
+
return {};
|
|
291
|
+
}
|
|
292
|
+
function readJpegDimensions(buf) {
|
|
293
|
+
let i = 2;
|
|
294
|
+
while (i < buf.length - 9) {
|
|
295
|
+
if (buf[i] !== 255) {
|
|
296
|
+
i++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const marker = buf[i + 1];
|
|
300
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
301
|
+
const height = buf.readUInt16BE(i + 5);
|
|
302
|
+
const width = buf.readUInt16BE(i + 7);
|
|
303
|
+
return { width, height };
|
|
304
|
+
}
|
|
305
|
+
const segLen = buf.readUInt16BE(i + 2);
|
|
306
|
+
i += 2 + segLen;
|
|
307
|
+
}
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
function sanitizeImageFilename(name) {
|
|
311
|
+
const lowered = name.toLowerCase();
|
|
312
|
+
const sanitized = lowered.replace(/[^a-z0-9._-]/g, "-").replace(/-+/g, "-");
|
|
313
|
+
return sanitized.replace(/^-+|-+$/g, "");
|
|
314
|
+
}
|
|
315
|
+
function sha256(buf) {
|
|
316
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
317
|
+
}
|
|
318
|
+
async function copyImageAssets(srcDir, destDir) {
|
|
319
|
+
await mkdir2(destDir, { recursive: true });
|
|
320
|
+
const entries = await readdir2(srcDir, { withFileTypes: true });
|
|
321
|
+
const upstreamManifestPath = path2.join(srcDir, "manifest.json");
|
|
322
|
+
let upstream = null;
|
|
323
|
+
try {
|
|
324
|
+
const st = await stat2(upstreamManifestPath);
|
|
325
|
+
if (st.isFile()) {
|
|
326
|
+
upstream = JSON.parse(await readFile2(upstreamManifestPath, "utf8"));
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
const manifest = {};
|
|
331
|
+
const skipped = [];
|
|
332
|
+
let count = 0;
|
|
333
|
+
for (const entry of entries) {
|
|
334
|
+
if (!entry.isFile()) continue;
|
|
335
|
+
if (entry.name === "manifest.json") continue;
|
|
336
|
+
const safeName = sanitizeImageFilename(entry.name);
|
|
337
|
+
if (!FILENAME_RE.test(safeName)) {
|
|
338
|
+
skipped.push(entry.name);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const ext = path2.extname(safeName).slice(1);
|
|
342
|
+
if (!ext || !SUPPORTED_FORMATS.has(ext)) {
|
|
343
|
+
skipped.push(entry.name);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const src = path2.join(srcDir, entry.name);
|
|
347
|
+
const dest = path2.join(destDir, safeName);
|
|
348
|
+
const buf = await readFile2(src);
|
|
349
|
+
const format = detectFormat(buf, ext);
|
|
350
|
+
if (!format) {
|
|
351
|
+
skipped.push(`${entry.name} (format mismatch)`);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
await copyFile(src, dest);
|
|
355
|
+
let entry_record;
|
|
356
|
+
if (upstream && upstream[safeName]) {
|
|
357
|
+
entry_record = { ...upstream[safeName], src: safeName, format };
|
|
358
|
+
} else {
|
|
359
|
+
const dims = readIntrinsicDimensions(buf, format);
|
|
360
|
+
entry_record = {
|
|
361
|
+
src: safeName,
|
|
362
|
+
format,
|
|
363
|
+
sha256: sha256(buf),
|
|
364
|
+
bytes: buf.length,
|
|
365
|
+
...dims
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
manifest[safeName] = entry_record;
|
|
369
|
+
count++;
|
|
370
|
+
}
|
|
371
|
+
await mkdir2(destDir, { recursive: true });
|
|
372
|
+
await writeFile2(
|
|
373
|
+
path2.join(destDir, "..", "images-manifest.json"),
|
|
374
|
+
JSON.stringify(manifest, null, 2)
|
|
375
|
+
);
|
|
376
|
+
return { count, manifest, skipped };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// scripts/lib/dataset-turtle.ts
|
|
380
|
+
import {
|
|
381
|
+
datasetToQuads,
|
|
382
|
+
collectQuads,
|
|
383
|
+
writeTurtle,
|
|
384
|
+
PREFIXES
|
|
385
|
+
} from "glossarist/rdf";
|
|
386
|
+
function writerPrefixes() {
|
|
387
|
+
const out = {};
|
|
388
|
+
for (const k of ["dcat", "skos", "dcterms", "prov", "rdf", "rdfs", "xsd"]) {
|
|
389
|
+
if (PREFIXES[k]) out[k] = PREFIXES[k];
|
|
390
|
+
}
|
|
391
|
+
out.dcat ??= "http://www.w3.org/ns/dcat#";
|
|
392
|
+
out.prov ??= "http://www.w3.org/ns/prov#";
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
async function buildDatasetTurtle(input) {
|
|
396
|
+
const quads = collectQuads(datasetToQuads(input));
|
|
397
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// scripts/lib/build-activity-turtle.ts
|
|
401
|
+
import {
|
|
402
|
+
buildActivityToQuads,
|
|
403
|
+
collectQuads as collectQuads2,
|
|
404
|
+
writeTurtle as writeTurtle2
|
|
405
|
+
} from "glossarist/rdf";
|
|
406
|
+
function writerPrefixes2() {
|
|
407
|
+
return {
|
|
408
|
+
prov: "http://www.w3.org/ns/prov#",
|
|
409
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
410
|
+
foaf: "http://xmlns.com/foaf/0.1/",
|
|
411
|
+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
412
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
413
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
414
|
+
gloss: "https://www.glossarist.org/ontologies/"
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
async function buildActivityTurtle(input) {
|
|
418
|
+
const quads = collectQuads2(buildActivityToQuads(input));
|
|
419
|
+
return writeTurtle2(quads, { prefixes: writerPrefixes2() });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// scripts/lib/vocab-turtle.ts
|
|
423
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
424
|
+
import { join, dirname as dirname2 } from "node:path";
|
|
425
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
426
|
+
import {
|
|
427
|
+
vocabularyToQuads,
|
|
428
|
+
collectQuads as collectQuads3,
|
|
429
|
+
writeTurtle as writeTurtle3,
|
|
430
|
+
PREFIXES as PREFIXES2
|
|
431
|
+
} from "glossarist/rdf";
|
|
432
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
433
|
+
var VOCAB_PATH = join(__dirname2, "..", "..", "data", "glossarist-vocab.json");
|
|
434
|
+
function loadVocab(path4 = VOCAB_PATH) {
|
|
435
|
+
return JSON.parse(readFileSync2(path4, "utf8"));
|
|
436
|
+
}
|
|
437
|
+
function writerPrefixes3() {
|
|
438
|
+
const out = {};
|
|
439
|
+
for (const [k, v] of Object.entries(PREFIXES2)) {
|
|
440
|
+
out[k] = v;
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
async function buildVocabularyTurtle() {
|
|
445
|
+
const vocab = loadVocab();
|
|
446
|
+
const quads = collectQuads3(vocabularyToQuads(vocab.schemes));
|
|
447
|
+
return writeTurtle3(quads, { prefixes: writerPrefixes3() });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// scripts/lib/agents-turtle.ts
|
|
451
|
+
import {
|
|
452
|
+
agentsFromContributors,
|
|
453
|
+
agentsToQuads,
|
|
454
|
+
collectQuads as collectQuads4,
|
|
455
|
+
writeTurtle as writeTurtle4
|
|
456
|
+
} from "glossarist/rdf";
|
|
457
|
+
function writerPrefixes4() {
|
|
458
|
+
return {
|
|
459
|
+
foaf: "http://xmlns.com/foaf/0.1/",
|
|
460
|
+
prov: "http://www.w3.org/ns/prov#",
|
|
461
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
462
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
async function buildAgentsTurtle(contributors2, agentBase, orgBase) {
|
|
466
|
+
if (!agentBase) throw new Error("buildAgentsTurtle requires agentBase");
|
|
467
|
+
if (!orgBase) throw new Error("buildAgentsTurtle requires orgBase");
|
|
468
|
+
const agents = agentsFromContributors(contributors2 ?? [], agentBase);
|
|
469
|
+
const quads = collectQuads4(agentsToQuads(agents, { orgBase }));
|
|
470
|
+
return writeTurtle4(quads, { prefixes: writerPrefixes4() });
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// scripts/lib/version-turtle.ts
|
|
474
|
+
import {
|
|
475
|
+
versionToQuads,
|
|
476
|
+
versionHistoryToQuads,
|
|
477
|
+
collectQuads as collectQuads5,
|
|
478
|
+
writeTurtle as writeTurtle5
|
|
479
|
+
} from "glossarist/rdf";
|
|
480
|
+
function writerPrefixes5() {
|
|
481
|
+
return {
|
|
482
|
+
prov: "http://www.w3.org/ns/prov#",
|
|
483
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
484
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
485
|
+
xsd: "http://www.w3.org/2001/XMLSchema#"
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
async function buildVersionHistoryTurtle(input) {
|
|
489
|
+
const quads = collectQuads5(versionHistoryToQuads(input));
|
|
490
|
+
return writeTurtle5(quads, { prefixes: writerPrefixes5() });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// scripts/lib/bibliography-turtle.ts
|
|
494
|
+
import {
|
|
495
|
+
bibliographyToQuads,
|
|
496
|
+
normalizeBibliographyData,
|
|
497
|
+
collectQuads as collectQuads6,
|
|
498
|
+
writeTurtle as writeTurtle6
|
|
499
|
+
} from "glossarist/rdf";
|
|
500
|
+
function writerPrefixes6() {
|
|
501
|
+
return {
|
|
502
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
503
|
+
foaf: "http://xmlns.com/foaf/0.1/",
|
|
504
|
+
gloss: "https://www.glossarist.org/ontologies/"
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async function buildBibliographyTurtle(register, bibliographyJson, baseUri) {
|
|
508
|
+
if (!baseUri) throw new Error("buildBibliographyTurtle requires baseUri");
|
|
509
|
+
const entries = normalizeBibliographyData(bibliographyJson);
|
|
510
|
+
const quads = collectQuads6(bibliographyToQuads({ registerId: register, entries, baseUri }));
|
|
511
|
+
return writeTurtle6(quads, { prefixes: writerPrefixes6() });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// scripts/lib/first-non-empty.ts
|
|
515
|
+
function firstNonEmpty(...vals) {
|
|
516
|
+
for (const v of vals) {
|
|
517
|
+
if (v == null) continue;
|
|
518
|
+
if (Array.isArray(v) && v.length === 0) continue;
|
|
519
|
+
if (typeof v === "string" && v === "") continue;
|
|
520
|
+
return v;
|
|
521
|
+
}
|
|
522
|
+
return void 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// scripts/lib/bibliography.ts
|
|
526
|
+
function normalizeBibliography(raw) {
|
|
527
|
+
if (Array.isArray(raw)) return { bibliography: raw };
|
|
528
|
+
if (raw && Array.isArray(raw.bibliography)) return raw;
|
|
529
|
+
return { bibliography: [] };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// scripts/lib/concept-uri.ts
|
|
533
|
+
function buildConceptUri(uriBase, registerId, conceptId) {
|
|
534
|
+
return `${uriBase}/${registerId}/concept/${conceptId}`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// scripts/lib/harmonize-concept.ts
|
|
538
|
+
import fs from "fs";
|
|
539
|
+
import yaml3 from "js-yaml";
|
|
540
|
+
var LEGACY_MULTIPLICITY_MAP = {
|
|
541
|
+
compulsory: { presence: "required", count: "exactly_one" },
|
|
542
|
+
optional: { presence: "optional", count: "exactly_one" },
|
|
543
|
+
compulsory_multiple: { presence: "required", count: "multiple" },
|
|
544
|
+
optional_multiple: { presence: "optional", count: "multiple" },
|
|
545
|
+
compulsory_at_least_one: { presence: "required", count: "at_least_one" }
|
|
546
|
+
};
|
|
547
|
+
var LEGACY_CERTAINTY_FROM_AXIS = {
|
|
548
|
+
required: "confirmed",
|
|
549
|
+
optional: "possible"
|
|
550
|
+
};
|
|
551
|
+
function splitMultiplicity(multiplicity) {
|
|
552
|
+
return multiplicity in LEGACY_MULTIPLICITY_MAP ? LEGACY_MULTIPLICITY_MAP[multiplicity] : { presence: "required", count: "exactly_one" };
|
|
553
|
+
}
|
|
554
|
+
function loadConceptFile(filePath) {
|
|
555
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
556
|
+
const docs = yaml3.loadAll(content, null, { schema: yaml3.DEFAULT_SCHEMA });
|
|
557
|
+
if (docs.length === 1 && docs[0].termid !== void 0) {
|
|
558
|
+
return docs[0];
|
|
559
|
+
}
|
|
560
|
+
if (docs.length >= 1 && docs[0].data && docs[0].data.identifier !== void 0) {
|
|
561
|
+
const mc = docs[0];
|
|
562
|
+
const result = { termid: String(mc.data.identifier) };
|
|
563
|
+
if (mc.related) result._related = mc.related;
|
|
564
|
+
if (mc.partitive_relations) result._partitiveRelations = mc.partitive_relations;
|
|
565
|
+
if (mc.generic_relations) result._genericRelations = mc.generic_relations;
|
|
566
|
+
if (mc.data.domains) result._domains = mc.data.domains;
|
|
567
|
+
if (mc.dates) result._dates = mc.dates;
|
|
568
|
+
if (mc.sources) result._sources = mc.sources;
|
|
569
|
+
if (mc.status) result._status = mc.status;
|
|
570
|
+
if (mc.schema_version) result._schemaVersion = mc.schema_version;
|
|
571
|
+
if (mc.date_accepted) result._dateAccepted = mc.date_accepted;
|
|
572
|
+
const MERGE_KEYS = [
|
|
573
|
+
"terms",
|
|
574
|
+
"definition",
|
|
575
|
+
"notes",
|
|
576
|
+
"annotations",
|
|
577
|
+
"examples",
|
|
578
|
+
"sources",
|
|
579
|
+
"dates",
|
|
580
|
+
"domain",
|
|
581
|
+
"references",
|
|
582
|
+
"entry_status",
|
|
583
|
+
"classification",
|
|
584
|
+
"review_type",
|
|
585
|
+
"review_date",
|
|
586
|
+
"review_decision_date",
|
|
587
|
+
"review_decision_event",
|
|
588
|
+
"review_status",
|
|
589
|
+
"review_decision",
|
|
590
|
+
"review_decision_notes",
|
|
591
|
+
"lineage_source_similarity",
|
|
592
|
+
"release",
|
|
593
|
+
"script",
|
|
594
|
+
"system"
|
|
595
|
+
];
|
|
596
|
+
for (const doc of docs.slice(1)) {
|
|
597
|
+
if (!doc) continue;
|
|
598
|
+
const lang = doc.data?.language_code || doc.language_code;
|
|
599
|
+
if (!lang) continue;
|
|
600
|
+
const lcData = { ...doc.data || {} };
|
|
601
|
+
delete lcData.language_code;
|
|
602
|
+
for (const key of MERGE_KEYS) {
|
|
603
|
+
if (doc[key] !== void 0 && lcData[key] === void 0) {
|
|
604
|
+
lcData[key] = doc[key];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
result[lang] = lcData;
|
|
608
|
+
}
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
return docs[0];
|
|
612
|
+
}
|
|
613
|
+
function readYaml(filePath) {
|
|
614
|
+
return yaml3.load(fs.readFileSync(filePath, "utf8"));
|
|
615
|
+
}
|
|
616
|
+
function stripHtml(s) {
|
|
617
|
+
return s.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// scripts/lib/jsonld-emitter.ts
|
|
621
|
+
function resolveRefUri(term, refMaps2) {
|
|
622
|
+
if (!refMaps2) return void 0;
|
|
623
|
+
const lc = term.toLowerCase();
|
|
624
|
+
if (refMaps2.urnMap?.[lc]) return refMaps2.urnMap[lc];
|
|
625
|
+
if (refMaps2.designationMap?.[lc]) return refMaps2.designationMap[lc];
|
|
626
|
+
if (refMaps2.numericMap?.[term]) return refMaps2.numericMap[term];
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
function termToDesignation(term) {
|
|
630
|
+
const typeMap = {
|
|
631
|
+
expression: "gl:Expression",
|
|
632
|
+
abbreviation: "gl:Abbreviation",
|
|
633
|
+
symbol: "gl:Symbol",
|
|
634
|
+
letter_symbol: "gl:LetterSymbol",
|
|
635
|
+
"graphical symbol": "gl:GraphicalSymbol"
|
|
636
|
+
};
|
|
637
|
+
const doc = {
|
|
638
|
+
"@type": typeMap[term.type] || "gl:Designation",
|
|
639
|
+
"gl:normativeStatus": term.normative_status || "preferred",
|
|
640
|
+
"gl:term": term.designation
|
|
641
|
+
};
|
|
642
|
+
if (term.grammar_info && term.grammar_info.length > 0) {
|
|
643
|
+
doc["gl:grammarInfo"] = term.grammar_info.map((gi) => {
|
|
644
|
+
const g = {};
|
|
645
|
+
if (gi.gender) g["gl:gender"] = gi.gender;
|
|
646
|
+
if (gi.number) g["gl:number"] = gi.number;
|
|
647
|
+
if (gi.partOfSpeech) g["gl:partOfSpeech"] = gi.partOfSpeech;
|
|
648
|
+
for (const pos of ["noun", "verb", "adj", "adverb", "preposition", "participle"]) {
|
|
649
|
+
if (gi[pos]) g[`gl:${pos}`] = gi[pos];
|
|
650
|
+
}
|
|
651
|
+
return g;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (term.international !== void 0) doc["gl:international"] = term.international;
|
|
655
|
+
if (term.absent !== void 0) doc["gl:absent"] = term.absent;
|
|
656
|
+
if (term.geographical_area) doc["gl:geographicalArea"] = term.geographical_area;
|
|
657
|
+
if (term.term_type) doc["gl:termType"] = term.term_type;
|
|
658
|
+
if (term.prefix) doc["gl:prefix"] = term.prefix;
|
|
659
|
+
if (term.usage_info) doc["gl:usageInfo"] = term.usage_info;
|
|
660
|
+
if (term.field_of_application) doc["gl:fieldOfApplication"] = term.field_of_application;
|
|
661
|
+
if (term.acronym !== void 0) doc["gl:acronym"] = term.acronym;
|
|
662
|
+
if (term.initialism !== void 0) doc["gl:initialism"] = term.initialism;
|
|
663
|
+
if (term.truncation !== void 0) doc["gl:truncation"] = term.truncation;
|
|
664
|
+
if (term.text) doc["gl:text"] = term.text;
|
|
665
|
+
if (term.image) doc["gl:image"] = term.image;
|
|
666
|
+
if (term.related && term.related.length > 0) {
|
|
667
|
+
doc["gl:related"] = term.related.map((r) => {
|
|
668
|
+
const rel = {};
|
|
669
|
+
if (r.type) rel["gl:relationshipType"] = r.type;
|
|
670
|
+
if (r.target) {
|
|
671
|
+
rel["gl:target"] = r.target;
|
|
672
|
+
} else if (r.ref) {
|
|
673
|
+
const ref = { "@type": "gl:ConceptRef" };
|
|
674
|
+
if (r.ref.source) ref["gl:source"] = r.ref.source;
|
|
675
|
+
if (r.ref.id) ref["gl:id"] = r.ref.id;
|
|
676
|
+
if (r.ref.text) ref["gl:text"] = r.ref.text;
|
|
677
|
+
rel["gl:ref"] = ref;
|
|
678
|
+
}
|
|
679
|
+
return rel;
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return doc;
|
|
683
|
+
}
|
|
684
|
+
function defsToJsonLd(defs) {
|
|
685
|
+
if (!defs || !Array.isArray(defs)) return [];
|
|
686
|
+
return defs.map((d) => ({ "@type": "gl:DetailedDefinition", "gl:content": d.content || "" })).filter((d) => d["gl:content"]);
|
|
687
|
+
}
|
|
688
|
+
function refToJsonLd(ref, typeName = "gl:Ref") {
|
|
689
|
+
if (!ref) return void 0;
|
|
690
|
+
const refObj = { "@type": typeName };
|
|
691
|
+
if (typeof ref === "string") {
|
|
692
|
+
refObj["gl:source"] = ref;
|
|
693
|
+
} else {
|
|
694
|
+
if (ref.source) refObj["gl:source"] = ref.source;
|
|
695
|
+
if (ref.id) refObj["gl:id"] = ref.id;
|
|
696
|
+
if (ref.version) refObj["gl:version"] = ref.version;
|
|
697
|
+
if (ref.text) refObj["gl:text"] = ref.text;
|
|
698
|
+
}
|
|
699
|
+
return refObj;
|
|
700
|
+
}
|
|
701
|
+
function localityToJsonLd(loc) {
|
|
702
|
+
if (!loc) return void 0;
|
|
703
|
+
const locObj = {};
|
|
704
|
+
if (loc.type) locObj["gl:localityType"] = loc.type;
|
|
705
|
+
if (loc.reference_from) locObj["gl:referenceFrom"] = loc.reference_from;
|
|
706
|
+
if (loc.referenceFrom) locObj["gl:referenceFrom"] = loc.referenceFrom;
|
|
707
|
+
if (loc.reference_to) locObj["gl:referenceTo"] = loc.reference_to;
|
|
708
|
+
if (loc.referenceTo) locObj["gl:referenceTo"] = loc.referenceTo;
|
|
709
|
+
return Object.keys(locObj).length > 0 ? locObj : void 0;
|
|
710
|
+
}
|
|
711
|
+
function citationToJsonLd(citation) {
|
|
712
|
+
const obj = {};
|
|
713
|
+
const ref = refToJsonLd(citation.ref);
|
|
714
|
+
if (ref) obj["gl:ref"] = ref;
|
|
715
|
+
const loc = localityToJsonLd(citation.locality);
|
|
716
|
+
if (loc) obj["gl:locality"] = loc;
|
|
717
|
+
if (citation.link) obj["gl:link"] = citation.link;
|
|
718
|
+
return obj;
|
|
719
|
+
}
|
|
720
|
+
function sourcesToJsonLd(sources) {
|
|
721
|
+
if (!sources || !Array.isArray(sources)) return [];
|
|
722
|
+
return sources.map((s) => {
|
|
723
|
+
const doc = { "@type": "gl:ConceptSource" };
|
|
724
|
+
if (s.id) doc["gl:id"] = s.id;
|
|
725
|
+
if (s.type) doc["gl:sourceType"] = s.type;
|
|
726
|
+
if (s.status) doc["gl:sourceStatus"] = s.status;
|
|
727
|
+
if (s.modification) doc["gl:modification"] = s.modification;
|
|
728
|
+
if (s.origin) {
|
|
729
|
+
const origin = { "@type": "gl:Citation" };
|
|
730
|
+
const ref = refToJsonLd(s.origin.ref);
|
|
731
|
+
if (ref) origin["gl:ref"] = ref;
|
|
732
|
+
const loc = localityToJsonLd(s.origin.locality);
|
|
733
|
+
if (loc) origin["gl:locality"] = loc;
|
|
734
|
+
if (s.origin.link) origin["gl:link"] = s.origin.link;
|
|
735
|
+
doc["gl:origin"] = origin;
|
|
736
|
+
}
|
|
737
|
+
if (s.sourced_from && s.sourced_from.length) {
|
|
738
|
+
doc["gl:sourcedFrom"] = s.sourced_from.map((sf) => {
|
|
739
|
+
const cite = { "@type": "gl:Citation" };
|
|
740
|
+
const ref = refToJsonLd(sf.ref);
|
|
741
|
+
if (ref) cite["gl:ref"] = ref;
|
|
742
|
+
const loc = localityToJsonLd(sf.locality);
|
|
743
|
+
if (loc) cite["gl:locality"] = loc;
|
|
744
|
+
if (sf.link) cite["gl:link"] = sf.link;
|
|
745
|
+
return cite;
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return doc;
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
function refsToJsonLd(refs, refMaps2) {
|
|
752
|
+
if (!refs || !Array.isArray(refs)) return [];
|
|
753
|
+
return refs.map((r) => {
|
|
754
|
+
if (r.id) {
|
|
755
|
+
const ref = { "@id": r.id, "gl:term": r.term };
|
|
756
|
+
if (r.sourceId) ref["gl:sourceId"] = r.sourceId;
|
|
757
|
+
if (r.citation) ref["gl:citation"] = citationToJsonLd(r.citation);
|
|
758
|
+
return ref;
|
|
759
|
+
}
|
|
760
|
+
if (r.term && refMaps2) {
|
|
761
|
+
const uri = resolveRefUri(r.term, refMaps2);
|
|
762
|
+
if (uri) return { "@id": uri, "gl:term": r.term };
|
|
763
|
+
}
|
|
764
|
+
return { "@id": r.id || r.term, "gl:term": r.term };
|
|
765
|
+
}).filter((r) => r["@id"]);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// scripts/lib/turtle-escape.ts
|
|
769
|
+
var STRING_ESCAPES = {
|
|
770
|
+
"\\": "\\\\",
|
|
771
|
+
'"': '\\"',
|
|
772
|
+
"\n": "\\n",
|
|
773
|
+
"\r": "\\r",
|
|
774
|
+
" ": "\\t"
|
|
775
|
+
};
|
|
776
|
+
function ttlLit(s) {
|
|
777
|
+
if (s == null) return '""';
|
|
778
|
+
const escaped = String(s).replace(/[\\\"\n\r\t]/g, (ch) => STRING_ESCAPES[ch] ?? ch);
|
|
779
|
+
return `"${escaped}"`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// scripts/lib/concept-formats.ts
|
|
783
|
+
function escapeTurtle(s) {
|
|
784
|
+
return ttlLit(s).slice(1, -1);
|
|
785
|
+
}
|
|
786
|
+
function escapeXml(s) {
|
|
787
|
+
const str = Array.isArray(s) ? s.join(", ") : String(s ?? "");
|
|
788
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
789
|
+
}
|
|
790
|
+
function conceptJsonToTurtle(concept) {
|
|
791
|
+
const uri = concept["@id"] || "";
|
|
792
|
+
const id = concept["gl:identifier"] || "";
|
|
793
|
+
const lines = [
|
|
794
|
+
"@prefix skos: <http://www.w3.org/2004/02/skos/core#> .",
|
|
795
|
+
"@prefix dcterms: <http://purl.org/dc/terms/> .",
|
|
796
|
+
""
|
|
797
|
+
];
|
|
798
|
+
const props = [" a skos:Concept"];
|
|
799
|
+
props.push(` skos:notation "${escapeTurtle(id)}"`);
|
|
800
|
+
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
801
|
+
if (lc["gl:designation"]) {
|
|
802
|
+
for (const d of lc["gl:designation"]) {
|
|
803
|
+
const term = d["gl:term"];
|
|
804
|
+
if (!term) continue;
|
|
805
|
+
const pred = d["gl:normativeStatus"] === "preferred" ? "skos:prefLabel" : "skos:altLabel";
|
|
806
|
+
props.push(` ${pred} "${escapeTurtle(term)}"@${lang}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (lc["gl:definition"]) {
|
|
810
|
+
for (const d of lc["gl:definition"]) {
|
|
811
|
+
if (d["gl:content"]) props.push(` skos:definition "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (lc["gl:notes"]) {
|
|
815
|
+
for (const d of lc["gl:notes"]) {
|
|
816
|
+
if (d["gl:content"]) props.push(` skos:scopeNote "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
lines.push(`<${uri}>`);
|
|
821
|
+
lines.push(props.join(" ;\n"));
|
|
822
|
+
lines.push(" .");
|
|
823
|
+
return lines.join("\n");
|
|
824
|
+
}
|
|
825
|
+
function conceptJsonToSkosJsonLd(concept) {
|
|
826
|
+
const uri = concept["@id"] || "";
|
|
827
|
+
const id = concept["gl:identifier"] || "";
|
|
828
|
+
const doc = {
|
|
829
|
+
"@context": {
|
|
830
|
+
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
831
|
+
dcterms: "http://purl.org/dc/terms/",
|
|
832
|
+
"@language": { "@container": "@language" }
|
|
833
|
+
},
|
|
834
|
+
"@id": uri,
|
|
835
|
+
"@type": "skos:Concept",
|
|
836
|
+
"skos:notation": id
|
|
837
|
+
};
|
|
838
|
+
const prefLabels = {};
|
|
839
|
+
const altLabels = {};
|
|
840
|
+
const definitions = {};
|
|
841
|
+
const scopeNotes = {};
|
|
842
|
+
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
843
|
+
const descs = lc["gl:designation"] || [];
|
|
844
|
+
const pref = descs.find((d) => d["gl:normativeStatus"] === "preferred" && d["gl:term"]);
|
|
845
|
+
const alt = descs.find((d) => d["gl:normativeStatus"] !== "preferred" && d["gl:term"]);
|
|
846
|
+
if (pref) prefLabels[lang] = pref["gl:term"];
|
|
847
|
+
if (alt) altLabels[lang] = alt["gl:term"];
|
|
848
|
+
const def = (lc["gl:definition"] || [])[0];
|
|
849
|
+
if (def?.["gl:content"]) definitions[lang] = def["gl:content"];
|
|
850
|
+
const note = (lc["gl:notes"] || [])[0];
|
|
851
|
+
if (note?.["gl:content"]) scopeNotes[lang] = note["gl:content"];
|
|
852
|
+
}
|
|
853
|
+
if (Object.keys(prefLabels).length) doc["skos:prefLabel"] = prefLabels;
|
|
854
|
+
if (Object.keys(altLabels).length) doc["skos:altLabel"] = altLabels;
|
|
855
|
+
if (Object.keys(definitions).length) doc["skos:definition"] = definitions;
|
|
856
|
+
if (Object.keys(scopeNotes).length) doc["skos:scopeNote"] = scopeNotes;
|
|
857
|
+
return JSON.stringify(doc);
|
|
858
|
+
}
|
|
859
|
+
function conceptJsonToTbx(concept) {
|
|
860
|
+
const id = concept["gl:identifier"] || "";
|
|
861
|
+
const uri = concept["@id"] || "";
|
|
862
|
+
const localized = concept["gl:localizedConcept"] || {};
|
|
863
|
+
const langSections = [];
|
|
864
|
+
for (const [lang, lc] of Object.entries(localized)) {
|
|
865
|
+
const descs = lc["gl:designation"] || [];
|
|
866
|
+
const definitions = (lc["gl:definition"] || []).filter((d) => d["gl:content"]);
|
|
867
|
+
const notes = (lc["gl:notes"] || []).filter((d) => d["gl:content"]);
|
|
868
|
+
const examples = (lc["gl:examples"] || []).filter((d) => d["gl:content"]);
|
|
869
|
+
const sources = lc["gl:source"] || [];
|
|
870
|
+
const entryStatus = lc["gl:entryStatus"] || "";
|
|
871
|
+
if (!descs.length && !definitions.length) continue;
|
|
872
|
+
const termEntries = [];
|
|
873
|
+
for (const d of descs) {
|
|
874
|
+
const term = d["gl:term"];
|
|
875
|
+
if (!term) continue;
|
|
876
|
+
const status = d["gl:normativeStatus"] || "";
|
|
877
|
+
const type = d["@type"] || "";
|
|
878
|
+
let gramGrp = "";
|
|
879
|
+
if (d["gl:grammarInfo"] && d["gl:grammarInfo"].length > 0) {
|
|
880
|
+
const gi = d["gl:grammarInfo"][0];
|
|
881
|
+
if (gi["gl:gender"]) gramGrp = `
|
|
882
|
+
<grammaticalGender>${escapeXml(gi["gl:gender"])}</grammaticalGender>`;
|
|
883
|
+
if (gi["gl:number"]) gramGrp += `
|
|
884
|
+
<grammaticalNumber>${escapeXml(gi["gl:number"])}</grammaticalNumber>`;
|
|
885
|
+
for (const pos of ["noun", "verb", "adj", "adverb", "preposition", "participle"]) {
|
|
886
|
+
if (gi[`gl:${pos}`]) gramGrp += `
|
|
887
|
+
<partOfSpeech>${pos}</partOfSpeech>`;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
let posBlock = "";
|
|
891
|
+
if (type.includes("Abbreviation")) posBlock = "\n <partOfSpeech>abbreviation</partOfSpeech>";
|
|
892
|
+
if (type.includes("Symbol")) posBlock = "\n <partOfSpeech>symbol</partOfSpeech>";
|
|
893
|
+
termEntries.push(` <termEntry>
|
|
894
|
+
<langSet xml:lang="${lang}">
|
|
895
|
+
<tig>
|
|
896
|
+
<term>${escapeXml(term)}</term>${gramGrp}${posBlock}
|
|
897
|
+
</tig>
|
|
898
|
+
</langSet>
|
|
899
|
+
</termEntry>`);
|
|
900
|
+
}
|
|
901
|
+
let defBlock = "";
|
|
902
|
+
if (definitions.length) {
|
|
903
|
+
const defParts = definitions.map((d) => ` <p>${escapeXml(d["gl:content"])}</p>`).join("\n");
|
|
904
|
+
defBlock = `
|
|
905
|
+
<descrip type="definition">
|
|
906
|
+
${defParts}
|
|
907
|
+
</descrip>`;
|
|
908
|
+
}
|
|
909
|
+
let noteBlock = "";
|
|
910
|
+
for (let i = 0; i < notes.length; i++) {
|
|
911
|
+
noteBlock += `
|
|
912
|
+
<note type="note">${escapeXml(notes[i]["gl:content"])}</note>`;
|
|
913
|
+
}
|
|
914
|
+
for (let i = 0; i < examples.length; i++) {
|
|
915
|
+
noteBlock += `
|
|
916
|
+
<note type="example">${escapeXml(examples[i]["gl:content"])}</note>`;
|
|
917
|
+
}
|
|
918
|
+
let sourceBlock = "";
|
|
919
|
+
for (const src of sources) {
|
|
920
|
+
const origin = src["gl:origin"] || {};
|
|
921
|
+
const parts = [];
|
|
922
|
+
const ref = origin["gl:ref"];
|
|
923
|
+
if (ref) {
|
|
924
|
+
const refParts = [];
|
|
925
|
+
if (ref["gl:source"]) refParts.push(ref["gl:source"]);
|
|
926
|
+
if (ref["gl:id"]) refParts.push(ref["gl:id"]);
|
|
927
|
+
parts.push(refParts.join(" ") || "");
|
|
928
|
+
}
|
|
929
|
+
if (origin["gl:locality"]) {
|
|
930
|
+
const loc = origin["gl:locality"];
|
|
931
|
+
if (loc["gl:referenceFrom"]) parts.push(loc["gl:localityType"] ? `${loc["gl:localityType"]} ${loc["gl:referenceFrom"]}` : loc["gl:referenceFrom"]);
|
|
932
|
+
}
|
|
933
|
+
if (parts.filter(Boolean).length) {
|
|
934
|
+
sourceBlock += `
|
|
935
|
+
<ref>${escapeXml(parts.filter(Boolean).join(", "))}</ref>`;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
let statusBlock = "";
|
|
939
|
+
if (entryStatus) {
|
|
940
|
+
statusBlock += `
|
|
941
|
+
<descrip type="entryStatus">${escapeXml(entryStatus)}</descrip>`;
|
|
942
|
+
}
|
|
943
|
+
const termEntriesBlock = termEntries.length ? "\n" + termEntries.join("\n") : "";
|
|
944
|
+
langSections.push({ lang, termEntries, blocks: [defBlock, noteBlock, sourceBlock, statusBlock].filter((b) => b).join("") });
|
|
945
|
+
}
|
|
946
|
+
if (!langSections.length) return "";
|
|
947
|
+
const bodyEntries = langSections.map((ls) => {
|
|
948
|
+
return ` <languageSection xml:lang="${ls.lang}">${ls.blocks}
|
|
949
|
+
</languageSection>`;
|
|
950
|
+
}).join("\n");
|
|
951
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
952
|
+
<tbx style="dca" type="TBX-Basic" xml:lang="en" xmlns="urn:iso:std:iso:30042:ed-2">
|
|
953
|
+
<tbxHeader>
|
|
954
|
+
<fileDesc>
|
|
955
|
+
<source>${escapeXml(uri)}</source>
|
|
956
|
+
</fileDesc>
|
|
957
|
+
</tbxHeader>
|
|
958
|
+
<text>
|
|
959
|
+
<body>
|
|
960
|
+
<conceptEntry id="${escapeXml(id)}">
|
|
961
|
+
${bodyEntries}
|
|
962
|
+
</conceptEntry>
|
|
963
|
+
</body>
|
|
964
|
+
</text>
|
|
965
|
+
</tbx>
|
|
966
|
+
`;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// scripts/generate-data.ts
|
|
970
|
+
var __dirname3 = path3.dirname(new URL(import.meta.url).pathname);
|
|
971
|
+
var ROOT = process.cwd();
|
|
972
|
+
var PUBLIC = path3.join(ROOT, "public");
|
|
973
|
+
var DATA = path3.join(PUBLIC, "data");
|
|
974
|
+
function datasetDir(ds) {
|
|
975
|
+
return ds.localPath ? path3.resolve(ROOT, ds.localPath) : path3.join(ROOT, ".datasets", ds.id);
|
|
976
|
+
}
|
|
977
|
+
var DS_PALETTE = [
|
|
978
|
+
"#3366ff",
|
|
979
|
+
"#0d9488",
|
|
980
|
+
"#d97706",
|
|
981
|
+
"#8b5cf6",
|
|
982
|
+
"#ec4899",
|
|
983
|
+
"#059669",
|
|
984
|
+
"#dc2626",
|
|
985
|
+
"#6366f1",
|
|
986
|
+
"#0891b2",
|
|
987
|
+
"#65a30d"
|
|
988
|
+
];
|
|
989
|
+
function writeJson(filePath, data) {
|
|
990
|
+
fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
991
|
+
fs2.writeFileSync(filePath, JSON.stringify(data));
|
|
992
|
+
}
|
|
993
|
+
async function writeDatasetRdf(register, manifest, concepts, refMaps2, opts) {
|
|
994
|
+
const uriBase = refMaps2?.uriBase;
|
|
995
|
+
if (!uriBase) throw new Error("generate-data: uriBase is required \u2014 set uriBase in site-config.yml");
|
|
996
|
+
const datasetIri = `${uriBase}/${register}/`;
|
|
997
|
+
const topConceptUris = concepts.slice(0, 32).map((c) => buildConceptUri(uriBase, register, c.id));
|
|
998
|
+
const sections = (manifest.sections ?? []).map((section) => {
|
|
999
|
+
const sectionId = section.id ?? section.slug ?? section.title;
|
|
1000
|
+
return {
|
|
1001
|
+
collectionIri: `${uriBase}/${register}/section/${sectionId}`,
|
|
1002
|
+
title: section.title ?? section.name ?? sectionId,
|
|
1003
|
+
memberUris: (section.members ?? []).map((id) => buildConceptUri(uriBase, register, id))
|
|
1004
|
+
};
|
|
1005
|
+
});
|
|
1006
|
+
const distributions = [
|
|
1007
|
+
{
|
|
1008
|
+
id: `${register}-ttl`,
|
|
1009
|
+
title: "Turtle distribution",
|
|
1010
|
+
mediaType: "text/turtle",
|
|
1011
|
+
downloadUrl: `${uriBase}/data/${register}/${register}.ttl`
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
id: `${register}-jsonld`,
|
|
1015
|
+
title: "JSON-LD distribution",
|
|
1016
|
+
mediaType: "application/ld+json",
|
|
1017
|
+
downloadUrl: `${uriBase}/data/${register}/${register}.jsonld`
|
|
1018
|
+
}
|
|
1019
|
+
];
|
|
1020
|
+
const ttl = await buildDatasetTurtle({
|
|
1021
|
+
datasetIri,
|
|
1022
|
+
registerId: register,
|
|
1023
|
+
title: manifest.title ?? register,
|
|
1024
|
+
description: manifest.description,
|
|
1025
|
+
modified: manifest.lastUpdated ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
1026
|
+
languages: opts.languages ?? ["eng"],
|
|
1027
|
+
distributions,
|
|
1028
|
+
topConceptUris,
|
|
1029
|
+
sections,
|
|
1030
|
+
sourceRepoUrl: manifest.sourceRepoUrl,
|
|
1031
|
+
publisherIri: manifest.publisher,
|
|
1032
|
+
contactIri: manifest.contactPoint
|
|
1033
|
+
});
|
|
1034
|
+
fs2.writeFileSync(path3.join(DATA, register, `${register}.ttl`), ttl);
|
|
1035
|
+
}
|
|
1036
|
+
async function writeBuildActivity(conceptCount, datasetRegisters, baseUri) {
|
|
1037
|
+
const runId = process.env.GITHUB_RUN_ID ? `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}` : `local-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
1038
|
+
const startedAt = process.env.BUILD_STARTED_AT ?? new Date(Date.now() - 6e4).toISOString();
|
|
1039
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1040
|
+
const gitSha = process.env.GITHUB_SHA ?? null;
|
|
1041
|
+
const gitBranch = process.env.GITHUB_REF_NAME ?? null;
|
|
1042
|
+
const pkgVersion = JSON.parse(fs2.readFileSync(path3.join(__dirname3, "..", "package.json"), "utf8")).version;
|
|
1043
|
+
const agentIri = process.env.CI_BOT_AGENT_IRI ?? null;
|
|
1044
|
+
const ttl = await buildActivityTurtle({
|
|
1045
|
+
runId,
|
|
1046
|
+
startedAt,
|
|
1047
|
+
endedAt,
|
|
1048
|
+
gitSha,
|
|
1049
|
+
gitBranch,
|
|
1050
|
+
toolId: "concept-browser",
|
|
1051
|
+
toolVersion: pkgVersion,
|
|
1052
|
+
datasetRegisters,
|
|
1053
|
+
conceptCount,
|
|
1054
|
+
associatedAgentIri: agentIri,
|
|
1055
|
+
baseUri
|
|
1056
|
+
});
|
|
1057
|
+
const activityDir = path3.join(DATA, "activity");
|
|
1058
|
+
fs2.mkdirSync(activityDir, { recursive: true });
|
|
1059
|
+
fs2.writeFileSync(path3.join(activityDir, `${runId}.ttl`), ttl);
|
|
1060
|
+
console.log(`Emitted build activity record: data/activity/${runId}.ttl`);
|
|
1061
|
+
}
|
|
1062
|
+
function buildPatternIndex(datasets, registerCache2) {
|
|
1063
|
+
const entries = [];
|
|
1064
|
+
for (const ds of datasets) {
|
|
1065
|
+
const reg = registerCache2[ds.id] || null;
|
|
1066
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
1067
|
+
if (ds.uri) patterns.add(ds.uri);
|
|
1068
|
+
for (const alias of ds.uriAliases || []) patterns.add(alias);
|
|
1069
|
+
if (reg) {
|
|
1070
|
+
if (reg.urn && typeof reg.urn === "string" && reg.urn.endsWith("*")) patterns.add(reg.urn);
|
|
1071
|
+
for (const alias of reg.urnAliases || []) patterns.add(alias);
|
|
1072
|
+
}
|
|
1073
|
+
for (const pattern of patterns) {
|
|
1074
|
+
if (typeof pattern !== "string" || !pattern.endsWith("*")) continue;
|
|
1075
|
+
const prefix = pattern.slice(0, -1);
|
|
1076
|
+
if (prefix) entries.push({ prefix, datasetId: ds.id });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
entries.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
1080
|
+
function resolve2(uri) {
|
|
1081
|
+
for (const { prefix, datasetId } of entries) {
|
|
1082
|
+
if (uri.startsWith(prefix)) {
|
|
1083
|
+
return { datasetId, conceptId: uri.slice(prefix.length) };
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
return { resolve: resolve2, entries };
|
|
1089
|
+
}
|
|
1090
|
+
function buildRefMaps(config2, registerCache2) {
|
|
1091
|
+
const refPrefixMap = {};
|
|
1092
|
+
const patternIndex = buildPatternIndex(config2.datasets, registerCache2);
|
|
1093
|
+
for (const route of config2.routing || []) {
|
|
1094
|
+
if (route.uri && route.uri.includes("iec") && route.uri.includes("60050")) {
|
|
1095
|
+
const mapped = route.targetDataset;
|
|
1096
|
+
if (mapped) refPrefixMap["IEV"] = mapped;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const xref = config2.crossReferences || {};
|
|
1100
|
+
if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
|
|
1101
|
+
const uriBase = config2.uriBase;
|
|
1102
|
+
if (!uriBase) throw new Error("site-config.yml: uriBase is required");
|
|
1103
|
+
return { patternIndex, refPrefixMap, uriBase, register: null };
|
|
1104
|
+
}
|
|
1105
|
+
function resolveIevRef(display, term, refPrefixMap, uriBase) {
|
|
1106
|
+
if (!display.startsWith("IEV:")) return null;
|
|
1107
|
+
const datasetId = refPrefixMap["IEV"];
|
|
1108
|
+
if (!datasetId) return null;
|
|
1109
|
+
return { id: buildConceptUri(uriBase, datasetId, display.slice(4)), term };
|
|
1110
|
+
}
|
|
1111
|
+
function resolvePatternRef(identifier, display, refPrefixMap, patternIndex, uriBase) {
|
|
1112
|
+
const r = patternIndex.resolve(identifier);
|
|
1113
|
+
if (!r) return null;
|
|
1114
|
+
return { id: buildConceptUri(uriBase, r.datasetId, r.conceptId), term: display };
|
|
1115
|
+
}
|
|
1116
|
+
function handleCiteRef(parsed, allSources) {
|
|
1117
|
+
const sourceEntry = allSources.find((s) => s.id === parsed.key);
|
|
1118
|
+
if (sourceEntry) {
|
|
1119
|
+
return {
|
|
1120
|
+
id: `cite:${sourceEntry.id}`,
|
|
1121
|
+
term: parsed.label || sourceEntry.origin?.toString?.() || sourceEntry.id,
|
|
1122
|
+
sourceId: sourceEntry.id,
|
|
1123
|
+
citation: sourceEntry.origin || null
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
id: `cite:${parsed.key}`,
|
|
1128
|
+
term: parsed.label || parsed.key,
|
|
1129
|
+
sourceId: parsed.key,
|
|
1130
|
+
citation: null
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function handleNumeric(parsed, register, uriBase) {
|
|
1134
|
+
if (!register) return null;
|
|
1135
|
+
const term = parsed.label ?? parsed.id;
|
|
1136
|
+
return { id: buildConceptUri(uriBase, register, parsed.id), term };
|
|
1137
|
+
}
|
|
1138
|
+
function handleDesignation(parsed, refMaps2) {
|
|
1139
|
+
const register = refMaps2.register;
|
|
1140
|
+
if (!register) return null;
|
|
1141
|
+
const designation = parsed.id;
|
|
1142
|
+
const display = parsed.label ?? designation;
|
|
1143
|
+
const conceptId = refMaps2.designationLookup?.get(designation.toLowerCase());
|
|
1144
|
+
return {
|
|
1145
|
+
id: buildConceptUri(refMaps2.uriBase, register, conceptId ?? designation),
|
|
1146
|
+
term: display
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function handleUnresolved(body, refMaps2) {
|
|
1150
|
+
const commaMatch = body.match(/^([^,}]+),\s*(.+)$/);
|
|
1151
|
+
if (!commaMatch) return null;
|
|
1152
|
+
const identifier = commaMatch[1].trim();
|
|
1153
|
+
const display = commaMatch[2].trim();
|
|
1154
|
+
const iev = resolveIevRef(identifier, display, refMaps2.refPrefixMap, refMaps2.uriBase);
|
|
1155
|
+
if (iev) return iev;
|
|
1156
|
+
const pattern = resolvePatternRef(identifier, display, refMaps2.refPrefixMap, refMaps2.patternIndex, refMaps2.uriBase);
|
|
1157
|
+
if (pattern) return pattern;
|
|
1158
|
+
const register = refMaps2.register;
|
|
1159
|
+
if (register && (/^\d/.test(identifier) || /^[A-Z]\.\d/.test(identifier))) {
|
|
1160
|
+
return { id: buildConceptUri(refMaps2.uriBase, register, identifier), term: display };
|
|
1161
|
+
}
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
function collectTextContent(localizedData) {
|
|
1165
|
+
const texts = [];
|
|
1166
|
+
const textFields = ["definition", "notes", "examples"];
|
|
1167
|
+
for (const field of textFields) {
|
|
1168
|
+
const items = localizedData[field];
|
|
1169
|
+
if (!items) continue;
|
|
1170
|
+
const arr = Array.isArray(items) ? items : [items];
|
|
1171
|
+
for (const item of arr) {
|
|
1172
|
+
texts.push(typeof item === "string" ? item : item.content || "");
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return texts.join(" ");
|
|
1176
|
+
}
|
|
1177
|
+
function extractInlineRefs(localizedData, refMaps2, conceptSources = []) {
|
|
1178
|
+
const refs = [];
|
|
1179
|
+
const { refPrefixMap, patternIndex, uriBase } = refMaps2;
|
|
1180
|
+
const fullText = collectTextContent(localizedData);
|
|
1181
|
+
const allSources = [...localizedData.sources || [], ...conceptSources];
|
|
1182
|
+
for (const m of fullText.matchAll(/\{([^,}]+),([^,}]+)(?:,([^}]+))?\}/g)) {
|
|
1183
|
+
const identifier = m[1].trim();
|
|
1184
|
+
const display = m[2].trim();
|
|
1185
|
+
const altDisplay = (m[3] || "").trim();
|
|
1186
|
+
if (!identifier || !display) continue;
|
|
1187
|
+
const iev = resolveIevRef(display, identifier, refPrefixMap, uriBase);
|
|
1188
|
+
if (iev) {
|
|
1189
|
+
refs.push(iev);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
const pattern = resolvePatternRef(identifier, altDisplay || display, refPrefixMap, patternIndex, uriBase);
|
|
1193
|
+
if (pattern) {
|
|
1194
|
+
refs.push(pattern);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
for (const m of fullText.matchAll(/\{\{([^{}]+?)\}\}/g)) {
|
|
1198
|
+
const body = m[1];
|
|
1199
|
+
const parsed = parseMention(body);
|
|
1200
|
+
let ref = null;
|
|
1201
|
+
if (parsed.kind === "cite-ref") {
|
|
1202
|
+
ref = handleCiteRef(parsed, allSources);
|
|
1203
|
+
} else if (parsed.kind === "numeric") {
|
|
1204
|
+
ref = handleNumeric(parsed, refMaps2.register, uriBase);
|
|
1205
|
+
} else if (parsed.kind === "urn-ref") {
|
|
1206
|
+
const uri = parsed.uri;
|
|
1207
|
+
const term = parsed.label ?? uri;
|
|
1208
|
+
const pattern = refMaps2.patternIndex.resolve(uri);
|
|
1209
|
+
if (pattern) {
|
|
1210
|
+
ref = { id: buildConceptUri(refMaps2.uriBase, pattern.datasetId, pattern.conceptId), term };
|
|
1211
|
+
} else {
|
|
1212
|
+
ref = { id: uri, term };
|
|
1213
|
+
}
|
|
1214
|
+
} else if (parsed.kind === "designation") {
|
|
1215
|
+
ref = handleDesignation(parsed, refMaps2);
|
|
1216
|
+
} else {
|
|
1217
|
+
ref = handleUnresolved(body, refMaps2);
|
|
1218
|
+
}
|
|
1219
|
+
if (ref) refs.push(ref);
|
|
1220
|
+
}
|
|
1221
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1222
|
+
return refs.filter((r) => {
|
|
1223
|
+
if (seen.has(r.id)) return false;
|
|
1224
|
+
seen.add(r.id);
|
|
1225
|
+
return true;
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
var LANG_CODES = ["eng", "ara", "deu", "fra", "spa", "ita", "jpn", "kor", "pol", "por", "srp", "swe", "zho", "rus", "fin", "dan", "nld", "msa", "nob", "nno", "zho"];
|
|
1229
|
+
function yamlToJsonLd(conceptYaml, register, refMaps2) {
|
|
1230
|
+
const termid = String(conceptYaml.termid);
|
|
1231
|
+
const base = refMaps2.uriBase;
|
|
1232
|
+
const doc = {
|
|
1233
|
+
"@context": "https://glossarist.org/ns/context.jsonld",
|
|
1234
|
+
"@id": buildConceptUri(base, register, termid),
|
|
1235
|
+
"@type": "gl:Concept",
|
|
1236
|
+
"gl:identifier": termid
|
|
1237
|
+
};
|
|
1238
|
+
const localizations = {};
|
|
1239
|
+
for (const lang of LANG_CODES) {
|
|
1240
|
+
const lc = conceptYaml[lang];
|
|
1241
|
+
if (!lc) continue;
|
|
1242
|
+
const lDoc = {
|
|
1243
|
+
"@id": `${base}/${register}/concept/${termid}/${lang}`,
|
|
1244
|
+
"@type": "gl:LocalizedConcept",
|
|
1245
|
+
"gl:languageCode": lang
|
|
1246
|
+
};
|
|
1247
|
+
if (lc.entry_status) lDoc["gl:entryStatus"] = lc.entry_status;
|
|
1248
|
+
if (lc.classification) lDoc["gl:classification"] = lc.classification;
|
|
1249
|
+
if (lc.review_type) lDoc["gl:reviewType"] = lc.review_type;
|
|
1250
|
+
if (lc.script) lDoc["gl:script"] = lc.script;
|
|
1251
|
+
if (lc.system) lDoc["gl:system"] = lc.system;
|
|
1252
|
+
if (lc.terms && lc.terms.length > 0) lDoc["gl:designation"] = lc.terms.map(termToDesignation);
|
|
1253
|
+
if (lc.definition) lDoc["gl:definition"] = defsToJsonLd(lc.definition);
|
|
1254
|
+
if (lc.notes && lc.notes.length > 0) lDoc["gl:notes"] = defsToJsonLd(lc.notes);
|
|
1255
|
+
if (lc.annotations && lc.annotations.length > 0) lDoc["gl:annotations"] = defsToJsonLd(lc.annotations);
|
|
1256
|
+
if (lc.examples && lc.examples.length > 0) lDoc["gl:examples"] = defsToJsonLd(lc.examples);
|
|
1257
|
+
if (lc.sources && lc.sources.length > 0) lDoc["gl:source"] = sourcesToJsonLd(lc.sources);
|
|
1258
|
+
if (lc.lineage_source_similarity !== void 0) lDoc["gl:lineageSourceSimilarity"] = lc.lineage_source_similarity;
|
|
1259
|
+
if (lc.release) lDoc["gl:release"] = lc.release;
|
|
1260
|
+
if (lc.review_date) lDoc["gl:reviewDate"] = lc.review_date;
|
|
1261
|
+
if (lc.review_decision_date) lDoc["gl:reviewDecisionDate"] = lc.review_decision_date;
|
|
1262
|
+
if (lc.review_decision_event) lDoc["gl:reviewDecisionEvent"] = lc.review_decision_event;
|
|
1263
|
+
if (lc.review_status) lDoc["gl:reviewStatus"] = lc.review_status;
|
|
1264
|
+
if (lc.review_decision) lDoc["gl:reviewDecision"] = lc.review_decision;
|
|
1265
|
+
if (lc.review_decision_notes) lDoc["gl:reviewDecisionNotes"] = lc.review_decision_notes;
|
|
1266
|
+
if (lc.domain) lDoc["gl:domain"] = lc.domain;
|
|
1267
|
+
if (lc.dates && lc.dates.length > 0) {
|
|
1268
|
+
lDoc["gl:dates"] = lc.dates.map((d) => ({
|
|
1269
|
+
"gl:dateType": d.type,
|
|
1270
|
+
"gl:date": d.date
|
|
1271
|
+
}));
|
|
1272
|
+
}
|
|
1273
|
+
if (lc.references && lc.references.length > 0) {
|
|
1274
|
+
lDoc["gl:references"] = refsToJsonLd(lc.references, refMaps2);
|
|
1275
|
+
} else if (refMaps2) {
|
|
1276
|
+
const inlineRefs = extractInlineRefs(lc, refMaps2, conceptYaml._sources);
|
|
1277
|
+
if (inlineRefs.length > 0) {
|
|
1278
|
+
lDoc["gl:references"] = refsToJsonLd(inlineRefs, refMaps2);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
localizations[lang] = lDoc;
|
|
1282
|
+
}
|
|
1283
|
+
if (Object.keys(localizations).length > 0) {
|
|
1284
|
+
doc["gl:localizedConcept"] = localizations;
|
|
1285
|
+
}
|
|
1286
|
+
if (conceptYaml._status) doc["gl:status"] = conceptYaml._status;
|
|
1287
|
+
if (conceptYaml._schemaVersion) doc["gl:schemaVersion"] = conceptYaml._schemaVersion;
|
|
1288
|
+
if (conceptYaml._dateAccepted) doc["gl:dateAccepted"] = conceptYaml._dateAccepted;
|
|
1289
|
+
if (conceptYaml._dates && conceptYaml._dates.length > 0) {
|
|
1290
|
+
doc["gl:dates"] = conceptYaml._dates.map((d) => ({
|
|
1291
|
+
"gl:dateType": d.type,
|
|
1292
|
+
"gl:date": d.date
|
|
1293
|
+
}));
|
|
1294
|
+
}
|
|
1295
|
+
if (conceptYaml._sources && conceptYaml._sources.length > 0) {
|
|
1296
|
+
doc["gl:source"] = sourcesToJsonLd(conceptYaml._sources);
|
|
1297
|
+
}
|
|
1298
|
+
if (conceptYaml._domains && conceptYaml._domains.length > 0) {
|
|
1299
|
+
doc["gl:domain"] = conceptYaml._domains.map((d) => {
|
|
1300
|
+
const domain = { "@type": "gl:ConceptReference" };
|
|
1301
|
+
if (d.concept_id) domain["gl:conceptId"] = d.concept_id;
|
|
1302
|
+
if (d.source) domain["gl:source"] = d.source;
|
|
1303
|
+
if (d.urn) domain["gl:urn"] = d.urn;
|
|
1304
|
+
if (d.ref_type) domain["gl:refType"] = d.ref_type;
|
|
1305
|
+
return domain;
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (conceptYaml._related && conceptYaml._related.length > 0) {
|
|
1309
|
+
doc["gl:related"] = conceptYaml._related.map((r) => {
|
|
1310
|
+
const rel = { "@type": "gl:RelatedConcept" };
|
|
1311
|
+
if (r.type) rel["gl:relationshipType"] = r.type;
|
|
1312
|
+
if (r.content) rel["gl:content"] = r.content;
|
|
1313
|
+
if (r.ref) {
|
|
1314
|
+
const ref = {};
|
|
1315
|
+
if (r.ref.source) ref["gl:source"] = r.ref.source;
|
|
1316
|
+
if (r.ref.id) ref["gl:id"] = r.ref.id;
|
|
1317
|
+
if (r.ref.text) ref["gl:text"] = r.ref.text;
|
|
1318
|
+
rel["gl:ref"] = ref;
|
|
1319
|
+
}
|
|
1320
|
+
return rel;
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
if (conceptYaml._partitiveRelations?.length > 0) {
|
|
1324
|
+
doc["gl:partitiveRelations"] = conceptYaml._partitiveRelations.map((rel) => {
|
|
1325
|
+
const out = { "@type": "gl:PartitiveRelation" };
|
|
1326
|
+
if (rel.comprehensive) {
|
|
1327
|
+
out["gl:comprehensive"] = refToJsonLd(rel.comprehensive, "gl:ConceptRef");
|
|
1328
|
+
}
|
|
1329
|
+
if (Array.isArray(rel.partitives) && rel.partitives.length > 0) {
|
|
1330
|
+
out["gl:hasPartitive"] = rel.partitives.map((member) => {
|
|
1331
|
+
const m = { "@type": "gl:PartitiveMember" };
|
|
1332
|
+
if (member.ref) {
|
|
1333
|
+
m["gl:ref"] = refToJsonLd(member.ref, "gl:ConceptRef");
|
|
1334
|
+
} else {
|
|
1335
|
+
m["gl:ref"] = refToJsonLd(member, "gl:ConceptRef");
|
|
1336
|
+
}
|
|
1337
|
+
const presence = member.presence ?? splitMultiplicity(member.multiplicity ?? "").presence;
|
|
1338
|
+
const count = member.count ?? splitMultiplicity(member.multiplicity ?? "").count;
|
|
1339
|
+
m["gl:presence"] = presence;
|
|
1340
|
+
m["gl:count"] = count;
|
|
1341
|
+
if (member.is_delimiting === true || member.isDelimiting === true) {
|
|
1342
|
+
m["gl:isDelimiting"] = true;
|
|
1343
|
+
}
|
|
1344
|
+
if (member.multiplicity) m["gl:multiplicity"] = member.multiplicity;
|
|
1345
|
+
const legacyCertainty = member.certainty ?? LEGACY_CERTAINTY_FROM_AXIS[presence];
|
|
1346
|
+
if (legacyCertainty) m["gl:certainty"] = legacyCertainty;
|
|
1347
|
+
return m;
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
if (rel.completeness) {
|
|
1351
|
+
out["gl:completeness"] = rel.completeness;
|
|
1352
|
+
}
|
|
1353
|
+
if (rel.plurality) {
|
|
1354
|
+
const pl = { "@type": "gl:TypeSharedPlurality" };
|
|
1355
|
+
pl["gl:isShared"] = rel.plurality.is_shared ?? rel.plurality.isShared ?? false;
|
|
1356
|
+
if (rel.plurality.is_uncertain ?? rel.plurality.isUncertain) {
|
|
1357
|
+
pl["gl:isUncertain"] = rel.plurality.is_uncertain ?? rel.plurality.isUncertain;
|
|
1358
|
+
}
|
|
1359
|
+
if (rel.plurality.shared_type ?? rel.plurality.sharedType) {
|
|
1360
|
+
pl["gl:sharedType"] = refToJsonLd(
|
|
1361
|
+
rel.plurality.shared_type ?? rel.plurality.sharedType,
|
|
1362
|
+
"gl:ConceptRef"
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
out["gl:hasPlurality"] = pl;
|
|
1366
|
+
}
|
|
1367
|
+
if (rel.criterion) {
|
|
1368
|
+
out["gl:criterion"] = typeof rel.criterion === "string" ? { default: rel.criterion } : rel.criterion;
|
|
1369
|
+
}
|
|
1370
|
+
return out;
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (conceptYaml._genericRelations?.length > 0) {
|
|
1374
|
+
doc["gl:genericRelations"] = conceptYaml._genericRelations.map((rel) => {
|
|
1375
|
+
const out = { "@type": "gl:GenericRelation" };
|
|
1376
|
+
if (rel.comprehensive) {
|
|
1377
|
+
out["gl:comprehensive"] = refToJsonLd(rel.comprehensive, "gl:ConceptRef");
|
|
1378
|
+
}
|
|
1379
|
+
if (Array.isArray(rel.members) && rel.members.length > 0) {
|
|
1380
|
+
out["gl:hasGeneric"] = rel.members.map((member) => {
|
|
1381
|
+
const m = { "@type": "gl:GenericMember" };
|
|
1382
|
+
if (member.ref) {
|
|
1383
|
+
m["gl:ref"] = refToJsonLd(member.ref, "gl:ConceptRef");
|
|
1384
|
+
} else {
|
|
1385
|
+
m["gl:ref"] = refToJsonLd(member, "gl:ConceptRef");
|
|
1386
|
+
}
|
|
1387
|
+
const presence = member.presence ?? splitMultiplicity(member.multiplicity ?? "").presence;
|
|
1388
|
+
const count = member.count ?? splitMultiplicity(member.multiplicity ?? "").count;
|
|
1389
|
+
m["gl:presence"] = presence;
|
|
1390
|
+
m["gl:count"] = count;
|
|
1391
|
+
if (member.delimiting_characteristic || member.delimitingCharacteristic) {
|
|
1392
|
+
const dc = member.delimiting_characteristic ?? member.delimitingCharacteristic;
|
|
1393
|
+
m["gl:delimitingCharacteristic"] = typeof dc === "string" ? { default: dc } : dc;
|
|
1394
|
+
}
|
|
1395
|
+
if (member.multiplicity) m["gl:multiplicity"] = member.multiplicity;
|
|
1396
|
+
const legacyCertainty = member.certainty ?? LEGACY_CERTAINTY_FROM_AXIS[presence];
|
|
1397
|
+
if (legacyCertainty) m["gl:certainty"] = legacyCertainty;
|
|
1398
|
+
return m;
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
if (rel.completeness) {
|
|
1402
|
+
out["gl:completeness"] = rel.completeness;
|
|
1403
|
+
}
|
|
1404
|
+
if (rel.criterion) {
|
|
1405
|
+
out["gl:criterion"] = typeof rel.criterion === "string" ? { default: rel.criterion } : rel.criterion;
|
|
1406
|
+
}
|
|
1407
|
+
return out;
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
return doc;
|
|
1411
|
+
}
|
|
1412
|
+
function getPrimaryDesignation(conceptYaml) {
|
|
1413
|
+
const descs = {};
|
|
1414
|
+
for (const lang of LANG_CODES) {
|
|
1415
|
+
const lc = conceptYaml[lang];
|
|
1416
|
+
if (lc && lc.terms && lc.terms.length > 0) {
|
|
1417
|
+
const preferredExpr = lc.terms.find((t) => t.normative_status === "preferred" && t.type === "expression");
|
|
1418
|
+
const preferred = preferredExpr || lc.terms.find((t) => t.normative_status === "preferred") || lc.terms[0];
|
|
1419
|
+
descs[lang] = preferred.designation;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return descs;
|
|
1423
|
+
}
|
|
1424
|
+
async function processDataset(dir, register, opts) {
|
|
1425
|
+
const files = fs2.readdirSync(dir).filter((f) => f.endsWith(".yaml")).sort((a, b) => naturalSort(a.replace(".yaml", ""), b.replace(".yaml", "")));
|
|
1426
|
+
console.log(`Processing ${register}: ${files.length} files`);
|
|
1427
|
+
const conceptsDir = path3.join(DATA, register, "concepts");
|
|
1428
|
+
if (fs2.existsSync(conceptsDir)) {
|
|
1429
|
+
for (const f of fs2.readdirSync(conceptsDir)) fs2.unlinkSync(path3.join(conceptsDir, f));
|
|
1430
|
+
}
|
|
1431
|
+
const concepts = [];
|
|
1432
|
+
const langTermCounts = {};
|
|
1433
|
+
const langDefCounts = {};
|
|
1434
|
+
const availableFormats = ["ttl", "jsonld", "yaml", "tbx"];
|
|
1435
|
+
const designationLookup = /* @__PURE__ */ new Map();
|
|
1436
|
+
for (const file of files) {
|
|
1437
|
+
try {
|
|
1438
|
+
const conceptYaml = loadConceptFile(path3.join(dir, file));
|
|
1439
|
+
if (!conceptYaml?.termid) continue;
|
|
1440
|
+
const termid = String(conceptYaml.termid);
|
|
1441
|
+
for (const lang of Object.keys(conceptYaml)) {
|
|
1442
|
+
const lc = conceptYaml[lang];
|
|
1443
|
+
if (!lc || typeof lc !== "object" || !Array.isArray(lc.terms)) continue;
|
|
1444
|
+
for (const term of lc.terms) {
|
|
1445
|
+
const designation = term.designation;
|
|
1446
|
+
if (typeof designation === "string" && designation && !designationLookup.has(designation.toLowerCase())) {
|
|
1447
|
+
designationLookup.set(designation.toLowerCase(), termid);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
const dsRefMaps = { ...refMaps, register, designationLookup };
|
|
1455
|
+
const stats = {
|
|
1456
|
+
sourceMap: /* @__PURE__ */ new Map(),
|
|
1457
|
+
relTypeCounts: {},
|
|
1458
|
+
partitiveRelations: {
|
|
1459
|
+
count: 0,
|
|
1460
|
+
byCompleteness: { complete: 0, partial: 0 },
|
|
1461
|
+
byPresence: { required: 0, optional: 0 },
|
|
1462
|
+
byCount: { exactly_one: 0, at_least_one: 0, multiple: 0 },
|
|
1463
|
+
delimitingMembers: 0,
|
|
1464
|
+
withCriterion: 0,
|
|
1465
|
+
withoutCriterion: 0,
|
|
1466
|
+
withPlurality: 0
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
const STATS_PROCESSORS = [
|
|
1470
|
+
function collectSources(cy, termid, s) {
|
|
1471
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1472
|
+
const allSources = [
|
|
1473
|
+
...cy._sources || [],
|
|
1474
|
+
...opts.languages.flatMap((l) => cy[l]?.sources || [])
|
|
1475
|
+
];
|
|
1476
|
+
for (const src of allSources) {
|
|
1477
|
+
const ref = src.origin?.ref?.source || src.origin?.ref?.id || "Unknown";
|
|
1478
|
+
refs.add(ref);
|
|
1479
|
+
if (!s.sourceMap.has(ref)) s.sourceMap.set(ref, { ref, types: /* @__PURE__ */ new Set(), conceptIds: [] });
|
|
1480
|
+
if (src.type) s.sourceMap.get(ref).types.add(src.type);
|
|
1481
|
+
}
|
|
1482
|
+
for (const ref of refs) s.sourceMap.get(ref)?.conceptIds.push(termid);
|
|
1483
|
+
},
|
|
1484
|
+
function countRelationships(cy, _termid, s) {
|
|
1485
|
+
for (const r of cy._related || []) {
|
|
1486
|
+
const type = r.type || "unknown";
|
|
1487
|
+
s.relTypeCounts[type] = (s.relTypeCounts[type] || 0) + 1;
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1490
|
+
function countPartitiveRelations(cy, _termid, s) {
|
|
1491
|
+
for (const rel of cy._partitiveRelations || []) {
|
|
1492
|
+
s.partitiveRelations.count += 1;
|
|
1493
|
+
const completeness = rel.completeness || "complete";
|
|
1494
|
+
s.partitiveRelations.byCompleteness[completeness] = (s.partitiveRelations.byCompleteness[completeness] || 0) + 1;
|
|
1495
|
+
const partitives = rel.partitives || [];
|
|
1496
|
+
for (const member of partitives) {
|
|
1497
|
+
const axes = splitMultiplicity(member.multiplicity ?? "");
|
|
1498
|
+
const presence = member.presence ?? axes.presence;
|
|
1499
|
+
const count = member.count ?? axes.count;
|
|
1500
|
+
s.partitiveRelations.byPresence[presence] = (s.partitiveRelations.byPresence[presence] || 0) + 1;
|
|
1501
|
+
s.partitiveRelations.byCount[count] = (s.partitiveRelations.byCount[count] || 0) + 1;
|
|
1502
|
+
if (member.is_delimiting === true || member.isDelimiting === true) {
|
|
1503
|
+
s.partitiveRelations.delimitingMembers += 1;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
if (rel.criterion) {
|
|
1507
|
+
s.partitiveRelations.withCriterion += 1;
|
|
1508
|
+
} else {
|
|
1509
|
+
s.partitiveRelations.withoutCriterion += 1;
|
|
1510
|
+
}
|
|
1511
|
+
if (rel.plurality) {
|
|
1512
|
+
s.partitiveRelations.withPlurality += 1;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
function countLanguages(cy, _termid, _s) {
|
|
1517
|
+
for (const lang of opts.languages) {
|
|
1518
|
+
const lc = cy[lang];
|
|
1519
|
+
if (lc) {
|
|
1520
|
+
if (lc.terms?.length > 0) langTermCounts[lang] = (langTermCounts[lang] || 0) + 1;
|
|
1521
|
+
if (lc.definition && (Array.isArray(lc.definition) ? lc.definition.some((d) => d.content) : lc.definition)) {
|
|
1522
|
+
langDefCounts[lang] = (langDefCounts[lang] || 0) + 1;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
];
|
|
1528
|
+
for (let i = 0; i < files.length; i++) {
|
|
1529
|
+
const file = files[i];
|
|
1530
|
+
try {
|
|
1531
|
+
const conceptYaml = loadConceptFile(path3.join(dir, file));
|
|
1532
|
+
if (!conceptYaml || !conceptYaml.termid) continue;
|
|
1533
|
+
const termid = String(conceptYaml.termid);
|
|
1534
|
+
const jsonld = yamlToJsonLd(conceptYaml, register, dsRefMaps);
|
|
1535
|
+
writeJson(path3.join(conceptsDir, `${termid}.json`), jsonld);
|
|
1536
|
+
const ttlContent = conceptJsonToTurtle(jsonld);
|
|
1537
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.ttl`), ttlContent);
|
|
1538
|
+
const skosJsonLd = conceptJsonToSkosJsonLd(jsonld);
|
|
1539
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.jsonld`), skosJsonLd);
|
|
1540
|
+
const tbxContent = conceptJsonToTbx(jsonld);
|
|
1541
|
+
if (tbxContent) {
|
|
1542
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.tbx`), tbxContent);
|
|
1543
|
+
}
|
|
1544
|
+
fs2.copyFileSync(path3.join(dir, file), path3.join(conceptsDir, `${termid}.yaml`));
|
|
1545
|
+
concepts.push({
|
|
1546
|
+
id: termid,
|
|
1547
|
+
designations: getPrimaryDesignation(conceptYaml),
|
|
1548
|
+
groups: getGroups(conceptYaml),
|
|
1549
|
+
status: conceptYaml.eng?.entry_status || "valid"
|
|
1550
|
+
});
|
|
1551
|
+
for (const processor of STATS_PROCESSORS) {
|
|
1552
|
+
processor(conceptYaml, termid, stats);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (e) {
|
|
1555
|
+
console.warn(` Skipping ${file}: ${e.message}`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
const CHUNK_SIZE = 500;
|
|
1559
|
+
const chunks = [];
|
|
1560
|
+
for (let i = 0; i < concepts.length; i += CHUNK_SIZE) {
|
|
1561
|
+
const chunk = concepts.slice(i, i + CHUNK_SIZE);
|
|
1562
|
+
const chunkIndex = Math.floor(i / CHUNK_SIZE);
|
|
1563
|
+
const chunkFile = `index-${String(chunkIndex).padStart(4, "0")}.json`;
|
|
1564
|
+
writeJson(path3.join(DATA, register, "chunks", chunkFile), {
|
|
1565
|
+
registerId: register,
|
|
1566
|
+
chunkIndex,
|
|
1567
|
+
concepts: chunk
|
|
1568
|
+
});
|
|
1569
|
+
chunks.push({ file: chunkFile, count: chunk.length });
|
|
1570
|
+
}
|
|
1571
|
+
const summary = concepts.map((c) => ({
|
|
1572
|
+
id: c.id,
|
|
1573
|
+
designations: c.designations,
|
|
1574
|
+
eng: c.designations.eng || Object.values(c.designations)[0] || "",
|
|
1575
|
+
status: c.status,
|
|
1576
|
+
groups: c.groups || []
|
|
1577
|
+
}));
|
|
1578
|
+
const plainSummary = summary.map((c) => {
|
|
1579
|
+
const designations = {};
|
|
1580
|
+
for (const [lang, term] of Object.entries(c.designations)) {
|
|
1581
|
+
if (term) designations[lang] = stripHtml(String(term));
|
|
1582
|
+
}
|
|
1583
|
+
return {
|
|
1584
|
+
...c,
|
|
1585
|
+
designations,
|
|
1586
|
+
eng: stripHtml(String(c.eng))
|
|
1587
|
+
};
|
|
1588
|
+
});
|
|
1589
|
+
const graphNodeEntries = concepts.map((c) => {
|
|
1590
|
+
const cleanDesignations = {};
|
|
1591
|
+
for (const [l, t] of Object.entries(c.designations)) {
|
|
1592
|
+
if (t) cleanDesignations[l] = stripHtml(String(t));
|
|
1593
|
+
}
|
|
1594
|
+
return [c.id, cleanDesignations, c.status];
|
|
1595
|
+
});
|
|
1596
|
+
fs2.mkdirSync(path3.join(DATA, register), { recursive: true });
|
|
1597
|
+
fs2.writeFileSync(
|
|
1598
|
+
path3.join(DATA, register, "graph-nodes.json"),
|
|
1599
|
+
JSON.stringify({
|
|
1600
|
+
uriPrefix: `${refMaps.uriBase}/${register}/concept/`,
|
|
1601
|
+
registerId: register,
|
|
1602
|
+
nodes: graphNodeEntries
|
|
1603
|
+
})
|
|
1604
|
+
);
|
|
1605
|
+
writeJson(path3.join(DATA, register, "index.json"), {
|
|
1606
|
+
registerId: register,
|
|
1607
|
+
schemaVersion: "1.0.0",
|
|
1608
|
+
conceptCount: concepts.length,
|
|
1609
|
+
chunkSize: CHUNK_SIZE,
|
|
1610
|
+
chunks,
|
|
1611
|
+
concepts: plainSummary
|
|
1612
|
+
});
|
|
1613
|
+
writeJson(path3.join(DATA, register, "index-meta.json"), {
|
|
1614
|
+
registerId: register,
|
|
1615
|
+
schemaVersion: "1.0.0",
|
|
1616
|
+
conceptCount: concepts.length,
|
|
1617
|
+
chunkSize: CHUNK_SIZE,
|
|
1618
|
+
chunks
|
|
1619
|
+
});
|
|
1620
|
+
const langStats = {};
|
|
1621
|
+
for (const lang of opts.languages) {
|
|
1622
|
+
langStats[lang] = {
|
|
1623
|
+
terms: langTermCounts[lang] || 0,
|
|
1624
|
+
definitions: langDefCounts[lang] || 0
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
const sourceRoot = path3.dirname(dir);
|
|
1628
|
+
const compiledDir = path3.join(sourceRoot, "compiled");
|
|
1629
|
+
const bulkFormats = [];
|
|
1630
|
+
if (fs2.existsSync(compiledDir)) {
|
|
1631
|
+
for (const file of fs2.readdirSync(compiledDir)) {
|
|
1632
|
+
const src = path3.join(compiledDir, file);
|
|
1633
|
+
const dest = path3.join(DATA, register, file);
|
|
1634
|
+
fs2.copyFileSync(src, dest);
|
|
1635
|
+
const ext = path3.extname(file);
|
|
1636
|
+
const formatMap = {
|
|
1637
|
+
".ttl": "turtle",
|
|
1638
|
+
".jsonld": "jsonld",
|
|
1639
|
+
".xml": "tbx",
|
|
1640
|
+
".jsonl": "jsonl",
|
|
1641
|
+
".yaml": "yaml"
|
|
1642
|
+
};
|
|
1643
|
+
const formatName = formatMap[ext] || ext.slice(1);
|
|
1644
|
+
bulkFormats.push({ file, format: formatName, size: fs2.statSync(src).size });
|
|
1645
|
+
}
|
|
1646
|
+
if (bulkFormats.length) {
|
|
1647
|
+
console.log(` Copied ${bulkFormats.length} bulk format files`);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
const sourceStats = Array.from(stats.sourceMap.values()).map((s) => ({
|
|
1651
|
+
ref: s.ref,
|
|
1652
|
+
types: Array.from(s.types),
|
|
1653
|
+
conceptCount: s.conceptIds.length,
|
|
1654
|
+
conceptIds: s.conceptIds
|
|
1655
|
+
})).sort((a, b) => b.conceptCount - a.conceptCount);
|
|
1656
|
+
const totalRelationships = Object.values(stats.relTypeCounts).reduce((a, b) => a + b, 0);
|
|
1657
|
+
writeJson(path3.join(DATA, register, "stats.json"), {
|
|
1658
|
+
sourceCount: sourceStats.length,
|
|
1659
|
+
sources: sourceStats,
|
|
1660
|
+
relationshipCount: totalRelationships,
|
|
1661
|
+
relationshipTypes: stats.relTypeCounts,
|
|
1662
|
+
partitiveRelations: stats.partitiveRelations
|
|
1663
|
+
});
|
|
1664
|
+
const manifest = {
|
|
1665
|
+
id: register,
|
|
1666
|
+
datasetUri: opts.datasetUri,
|
|
1667
|
+
uriAliases: opts.uriAliases,
|
|
1668
|
+
title: opts.title,
|
|
1669
|
+
description: opts.description,
|
|
1670
|
+
owner: opts.owner,
|
|
1671
|
+
baseUrl: `/data/${register}`,
|
|
1672
|
+
languages: opts.languages,
|
|
1673
|
+
conceptCount: concepts.length,
|
|
1674
|
+
sourceCount: sourceStats.length,
|
|
1675
|
+
relationshipCount: totalRelationships,
|
|
1676
|
+
conceptUrlTemplate: "{baseUrl}/concepts/{conceptId}.json",
|
|
1677
|
+
indexUrl: "{baseUrl}/index.json",
|
|
1678
|
+
contextUrl: "https://glossarist.org/ns/context.jsonld",
|
|
1679
|
+
uriBase: refMaps.uriBase,
|
|
1680
|
+
status: "valid",
|
|
1681
|
+
schemaVersion: "1.0.0",
|
|
1682
|
+
tags: opts.tags,
|
|
1683
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1684
|
+
sourceRepo: opts.sourceRepo,
|
|
1685
|
+
chunkSize: CHUNK_SIZE,
|
|
1686
|
+
color: opts.color,
|
|
1687
|
+
year: opts.year ?? void 0,
|
|
1688
|
+
languageStats: langStats,
|
|
1689
|
+
availableFormats,
|
|
1690
|
+
bulkFormats,
|
|
1691
|
+
hasBibliography: opts.hasBibliography,
|
|
1692
|
+
hasImages: opts.hasImages
|
|
1693
|
+
};
|
|
1694
|
+
if (opts.languageOrder) manifest.languageOrder = opts.languageOrder;
|
|
1695
|
+
if (opts.ref) manifest.ref = opts.ref;
|
|
1696
|
+
if (opts.refAliases) manifest.refAliases = opts.refAliases;
|
|
1697
|
+
if (opts.status) manifest.editionStatus = opts.status;
|
|
1698
|
+
if (opts.ordering) manifest.ordering = opts.ordering;
|
|
1699
|
+
if (opts.sections && opts.sections.length > 0) manifest.sections = opts.sections;
|
|
1700
|
+
writeJson(path3.join(DATA, register, "manifest.json"), manifest);
|
|
1701
|
+
await writeDatasetRdf(register, manifest, concepts, refMaps, opts);
|
|
1702
|
+
const bibPath = path3.join(sourceRoot, "bibliography.yaml");
|
|
1703
|
+
if (fs2.existsSync(bibPath)) {
|
|
1704
|
+
const rawBib = readYaml(bibPath);
|
|
1705
|
+
const bibData = normalizeBibliography(rawBib);
|
|
1706
|
+
writeJson(path3.join(DATA, register, "bibliography.json"), bibData);
|
|
1707
|
+
const bibCount = bibData.bibliography.length;
|
|
1708
|
+
console.log(` Copied bibliography (${bibCount} entries)`);
|
|
1709
|
+
} else {
|
|
1710
|
+
writeJson(path3.join(DATA, register, "bibliography.json"), { bibliography: [] });
|
|
1711
|
+
console.log(` Emitted empty bibliography.json (no source bibliography.yaml)`);
|
|
1712
|
+
}
|
|
1713
|
+
const imagesSrcDir = path3.join(sourceRoot, "images");
|
|
1714
|
+
if (fs2.existsSync(imagesSrcDir) && fs2.statSync(imagesSrcDir).isDirectory()) {
|
|
1715
|
+
const imagesDestDir = path3.join(DATA, register, "images");
|
|
1716
|
+
const result = await copyImageAssets(imagesSrcDir, imagesDestDir);
|
|
1717
|
+
console.log(` Copied ${result.count} images (skipped ${result.skipped.length})`);
|
|
1718
|
+
for (const w of result.skipped) {
|
|
1719
|
+
console.warn(` Warning: skipped image ${w}`);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const nvResult = await consumeDatasetEntities(sourceRoot, path3.join(DATA, register));
|
|
1723
|
+
const nvTotal = nvResult.figures + nvResult.tables + nvResult.formulas;
|
|
1724
|
+
if (nvTotal > 0) {
|
|
1725
|
+
console.log(` Consumed ${nvResult.figures} figures, ${nvResult.tables} tables, ${nvResult.formulas} formulas`);
|
|
1726
|
+
for (const w of nvResult.warnings) {
|
|
1727
|
+
console.warn(` Warning: ${w}`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
console.log(` Generated ${concepts.length} concepts, manifest, ${chunks.length} index chunks`);
|
|
1731
|
+
return concepts.length;
|
|
1732
|
+
}
|
|
1733
|
+
console.log("Generating Glossarist vocabulary browser data...\n");
|
|
1734
|
+
var { config } = loadSiteConfig();
|
|
1735
|
+
var counts = {};
|
|
1736
|
+
var registry = [];
|
|
1737
|
+
var registerCache = {};
|
|
1738
|
+
for (const ds of config.datasets) {
|
|
1739
|
+
const dsDir = datasetDir(ds);
|
|
1740
|
+
const registerYamlPath = path3.join(dsDir, "register.yaml");
|
|
1741
|
+
if (fs2.existsSync(registerYamlPath)) {
|
|
1742
|
+
try {
|
|
1743
|
+
const raw = yaml4.load(fs2.readFileSync(registerYamlPath, "utf8"));
|
|
1744
|
+
registerCache[ds.id] = Register.fromJSON(raw);
|
|
1745
|
+
} catch (e) {
|
|
1746
|
+
console.warn(` Warning: failed to parse register.yaml for ${ds.id}: ${e.message}`);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
var refMaps = buildRefMaps(config, registerCache);
|
|
1751
|
+
for (let i = 0; i < config.datasets.length; i++) {
|
|
1752
|
+
const ds = config.datasets[i];
|
|
1753
|
+
const dir = path3.join(datasetDir(ds), "concepts");
|
|
1754
|
+
if (!fs2.existsSync(dir)) {
|
|
1755
|
+
console.warn(`Skipping ${ds.id}: source directory not found (${dir})`);
|
|
1756
|
+
console.warn(` Run: npm run fetch-datasets`);
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const reg = registerCache[ds.id] || null;
|
|
1760
|
+
const dsLanguages = firstNonEmpty(reg?.languages, ds.languages, ["eng"]);
|
|
1761
|
+
const defaultLang = dsLanguages[0] || "eng";
|
|
1762
|
+
const regDesc = reg?.description;
|
|
1763
|
+
const dsDesc = ds.description;
|
|
1764
|
+
const resolvedDescription = typeof regDesc === "object" && Object.keys(regDesc).length > 0 ? regDesc[defaultLang] || Object.values(regDesc)[0] || "" : dsDesc || "";
|
|
1765
|
+
const resolvedTitle = reg?.displayName(defaultLang) ?? ds.title ?? reg?.ref ?? ds.id;
|
|
1766
|
+
counts[ds.id] = await processDataset(dir, ds.id, {
|
|
1767
|
+
title: resolvedTitle,
|
|
1768
|
+
description: resolvedDescription,
|
|
1769
|
+
owner: firstNonEmpty(reg?.owner, ds.owner),
|
|
1770
|
+
languages: dsLanguages,
|
|
1771
|
+
sourceRepo: firstNonEmpty(reg?.sourceRepo, ds.sourceRepo),
|
|
1772
|
+
languageOrder: firstNonEmpty(reg?.languageOrder, ds.languageOrder),
|
|
1773
|
+
ref: firstNonEmpty(reg?.ref, ds.ref),
|
|
1774
|
+
refAliases: firstNonEmpty(reg?.refAliases, ds.refAliases),
|
|
1775
|
+
year: reg?.year ?? null,
|
|
1776
|
+
tags: firstNonEmpty(reg?.tags, ds.tags),
|
|
1777
|
+
color: ds.color || DS_PALETTE[i % DS_PALETTE.length],
|
|
1778
|
+
datasetUri: firstNonEmpty(reg?.urn, ds.uri),
|
|
1779
|
+
uriAliases: firstNonEmpty(reg?.urnAliases, ds.uriAliases),
|
|
1780
|
+
status: firstNonEmpty(reg?.status, ds.editionStatus),
|
|
1781
|
+
ordering: reg?.ordering || null,
|
|
1782
|
+
sections: reg?.sections ? reg.sections.map((s) => s.toJSON()) : [],
|
|
1783
|
+
hasBibliography: fs2.existsSync(path3.join(datasetDir(ds), "bibliography.yaml")),
|
|
1784
|
+
hasImages: fs2.existsSync(path3.join(datasetDir(ds), "images"))
|
|
1785
|
+
});
|
|
1786
|
+
const resolvedOwner = firstNonEmpty(reg?.owner, ds.owner) ?? "";
|
|
1787
|
+
const resolvedTags = firstNonEmpty(reg?.tags, ds.tags) ?? [];
|
|
1788
|
+
const resolvedDatasetUri = firstNonEmpty(reg?.urn, ds.uri);
|
|
1789
|
+
const resolvedUriAliases = firstNonEmpty(reg?.urnAliases, ds.uriAliases);
|
|
1790
|
+
const resolvedRef = firstNonEmpty(reg?.ref, ds.ref);
|
|
1791
|
+
const resolvedRefAliases = firstNonEmpty(reg?.refAliases, ds.refAliases);
|
|
1792
|
+
registry.push({
|
|
1793
|
+
id: ds.id,
|
|
1794
|
+
manifestUrl: `/data/${ds.id}/manifest.json`,
|
|
1795
|
+
summary: {
|
|
1796
|
+
title: resolvedTitle,
|
|
1797
|
+
description: resolvedDescription,
|
|
1798
|
+
conceptCount: counts[ds.id] || 0,
|
|
1799
|
+
languages: dsLanguages,
|
|
1800
|
+
owner: resolvedOwner,
|
|
1801
|
+
tags: resolvedTags,
|
|
1802
|
+
color: ds.color || DS_PALETTE[i % DS_PALETTE.length],
|
|
1803
|
+
year: reg?.year ?? void 0
|
|
1804
|
+
},
|
|
1805
|
+
datasetUri: resolvedDatasetUri,
|
|
1806
|
+
uriBase: config.uriBase || void 0,
|
|
1807
|
+
uriAliases: resolvedUriAliases,
|
|
1808
|
+
ref: resolvedRef,
|
|
1809
|
+
refAliases: resolvedRefAliases
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
writeJson(path3.join(PUBLIC, "datasets.json"), registry);
|
|
1813
|
+
var configuredIds = new Set(config.datasets.map((d) => d.id));
|
|
1814
|
+
if (fs2.existsSync(DATA)) {
|
|
1815
|
+
for (const entry of fs2.readdirSync(DATA)) {
|
|
1816
|
+
const stalePath = path3.join(DATA, entry);
|
|
1817
|
+
if (!configuredIds.has(entry) && fs2.statSync(stalePath).isDirectory()) {
|
|
1818
|
+
fs2.rmSync(stalePath, { recursive: true, force: true });
|
|
1819
|
+
console.log(` Removed stale data directory: ${entry}`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
writeJson(path3.join(PUBLIC, "routing.json"), config.routing || []);
|
|
1824
|
+
console.log("Generated routing.json");
|
|
1825
|
+
async function processLogo(logoConfig, filename) {
|
|
1826
|
+
if (!logoConfig) return;
|
|
1827
|
+
const destDir = path3.join(PUBLIC, "logos");
|
|
1828
|
+
fs2.mkdirSync(destDir, { recursive: true });
|
|
1829
|
+
const destPath = path3.join(destDir, filename);
|
|
1830
|
+
if (logoConfig.localPath) {
|
|
1831
|
+
const src = path3.resolve(process.cwd(), logoConfig.localPath);
|
|
1832
|
+
if (fs2.existsSync(src)) {
|
|
1833
|
+
fs2.copyFileSync(src, destPath);
|
|
1834
|
+
console.log(` Copied logo: ${src} \u2192 ${destPath}`);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
console.warn(` Logo not found at: ${src}`);
|
|
1838
|
+
}
|
|
1839
|
+
if (logoConfig.remoteUrl) {
|
|
1840
|
+
try {
|
|
1841
|
+
console.log(` Downloading logo: ${logoConfig.remoteUrl}`);
|
|
1842
|
+
const resp = await fetch(logoConfig.remoteUrl);
|
|
1843
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
1844
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
1845
|
+
fs2.writeFileSync(destPath, buf);
|
|
1846
|
+
console.log(` Saved logo: ${destPath}`);
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
console.warn(` Logo download failed: ${e.message}`);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
await processLogo(config.branding?.logo, `${config.id}-logo.svg`);
|
|
1853
|
+
await processLogo(config.branding?.footerLogo, `${config.id}-footer-logo.svg`);
|
|
1854
|
+
if (config.branding?.logo?.localLight) {
|
|
1855
|
+
await processLogo({ localPath: config.branding.logo.localLight }, `${config.id}-logo-light.svg`);
|
|
1856
|
+
}
|
|
1857
|
+
if (config.branding?.logo?.localDark) {
|
|
1858
|
+
await processLogo({ localPath: config.branding.logo.localDark }, `${config.id}-logo-dark.svg`);
|
|
1859
|
+
}
|
|
1860
|
+
function processNewsPage(config2, page) {
|
|
1861
|
+
const newsDir = page.source ? path3.resolve(process.cwd(), page.source) : config2.newsDir ? path3.resolve(process.cwd(), config2.newsDir) : null;
|
|
1862
|
+
if (!newsDir || !fs2.existsSync(newsDir)) {
|
|
1863
|
+
if (newsDir) console.warn(`News directory not found: ${newsDir}`);
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const index = [];
|
|
1867
|
+
const newsOutDir = path3.join(PUBLIC, "news");
|
|
1868
|
+
fs2.mkdirSync(newsOutDir, { recursive: true });
|
|
1869
|
+
const postFiles = fs2.readdirSync(newsDir).filter((f) => f.endsWith(".adoc") || f.endsWith(".md")).sort().reverse();
|
|
1870
|
+
for (const file of postFiles) {
|
|
1871
|
+
const content = fs2.readFileSync(path3.join(newsDir, file), "utf8");
|
|
1872
|
+
const frontmatter = {};
|
|
1873
|
+
const bodyLines = [];
|
|
1874
|
+
let inFm = false;
|
|
1875
|
+
const lines = content.split("\n");
|
|
1876
|
+
if (lines[0] === "---") {
|
|
1877
|
+
inFm = true;
|
|
1878
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1879
|
+
if (lines[i] === "---") {
|
|
1880
|
+
inFm = false;
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (inFm) {
|
|
1884
|
+
const m = lines[i].match(/^(\w[\w\s]*):\s*(.*)/);
|
|
1885
|
+
if (m) frontmatter[m[1].trim()] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
1886
|
+
} else {
|
|
1887
|
+
bodyLines.push(lines[i]);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
} else {
|
|
1891
|
+
bodyLines.push(...lines);
|
|
1892
|
+
}
|
|
1893
|
+
const slug = file.replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/\.(adoc|md)$/, "");
|
|
1894
|
+
const body = bodyLines.join("\n").trim();
|
|
1895
|
+
const ext = path3.extname(file);
|
|
1896
|
+
const destFile = path3.join(newsOutDir, `${slug}${ext}`);
|
|
1897
|
+
fs2.copyFileSync(path3.join(newsDir, file), destFile);
|
|
1898
|
+
index.push({
|
|
1899
|
+
slug,
|
|
1900
|
+
title: frontmatter.title || slug,
|
|
1901
|
+
date: frontmatter.date || "",
|
|
1902
|
+
categories: frontmatter.categories ? frontmatter.categories.split(",").map((s) => s.trim()) : [],
|
|
1903
|
+
file: `/news/${slug}${ext}`,
|
|
1904
|
+
excerpt: body.split("\n").find((l) => l.trim())?.slice(0, 200) || ""
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
writeJson(path3.join(PUBLIC, "news.json"), index);
|
|
1908
|
+
console.log(`Generated news index: ${index.length} posts, ${postFiles.length} files copied to public/news/`);
|
|
1909
|
+
}
|
|
1910
|
+
function renderMarkdown(input) {
|
|
1911
|
+
const INLINE_PATTERNS = [
|
|
1912
|
+
[/\*\*(.+?)\*\*/g, (m) => `<strong>${m[1]}</strong>`],
|
|
1913
|
+
[/(?<!\*)\*([^*]+?)\*(?!\*)/g, (m) => `<em>${m[1]}</em>`],
|
|
1914
|
+
[/`([^`]+?)`/g, (m) => `<code>${m[1]}</code>`],
|
|
1915
|
+
[/\[([^\]]+)\]\(([^)]+)\)/g, (m) => `<a href="${m[2]}" target="_blank">${m[1]}</a>`]
|
|
1916
|
+
];
|
|
1917
|
+
function renderInline(text) {
|
|
1918
|
+
for (const [re, fn] of INLINE_PATTERNS) {
|
|
1919
|
+
text = text.replace(re, (...args) => fn(args));
|
|
1920
|
+
}
|
|
1921
|
+
return text;
|
|
1922
|
+
}
|
|
1923
|
+
function escapeHtml(s) {
|
|
1924
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1925
|
+
}
|
|
1926
|
+
const blocks = [];
|
|
1927
|
+
const lines = input.split("\n");
|
|
1928
|
+
let i = 0;
|
|
1929
|
+
while (i < lines.length) {
|
|
1930
|
+
const line = lines[i];
|
|
1931
|
+
if (line.trimStart().startsWith("```")) {
|
|
1932
|
+
const lang = line.trim().slice(3);
|
|
1933
|
+
const codeLines = [];
|
|
1934
|
+
i++;
|
|
1935
|
+
while (i < lines.length && !lines[i].trimStart().startsWith("```")) {
|
|
1936
|
+
codeLines.push(lines[i]);
|
|
1937
|
+
i++;
|
|
1938
|
+
}
|
|
1939
|
+
i++;
|
|
1940
|
+
blocks.push(`<pre><code${lang ? ` class="language-${lang}"` : ""}>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
const hm = line.match(/^(#{1,4})\s+(.+)/);
|
|
1944
|
+
if (hm) {
|
|
1945
|
+
blocks.push(`<h${hm[1].length + 1}>${renderInline(hm[2])}</h${hm[1].length + 1}>`);
|
|
1946
|
+
i++;
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
if (/^---+\s*$/.test(line)) {
|
|
1950
|
+
blocks.push("<hr>");
|
|
1951
|
+
i++;
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
1955
|
+
const items = [];
|
|
1956
|
+
while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) {
|
|
1957
|
+
items.push(`<li>${renderInline(lines[i].replace(/^\s*[-*]\s+/, ""))}</li>`);
|
|
1958
|
+
i++;
|
|
1959
|
+
}
|
|
1960
|
+
blocks.push(`<ul>${items.join("")}</ul>`);
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
1964
|
+
const items = [];
|
|
1965
|
+
while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
|
|
1966
|
+
items.push(`<li>${renderInline(lines[i].replace(/^\s*\d+\.\s+/, ""))}</li>`);
|
|
1967
|
+
i++;
|
|
1968
|
+
}
|
|
1969
|
+
blocks.push(`<ol>${items.join("")}</ol>`);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
if (/^>\s?/.test(line)) {
|
|
1973
|
+
const ql = [];
|
|
1974
|
+
while (i < lines.length && /^>\s?/.test(lines[i])) {
|
|
1975
|
+
ql.push(lines[i].replace(/^>\s?/, ""));
|
|
1976
|
+
i++;
|
|
1977
|
+
}
|
|
1978
|
+
blocks.push(`<blockquote>${renderInline(ql.join(" "))}</blockquote>`);
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
if (/^\|(.+)\|$/.test(line) && i + 1 < lines.length && /^\|[-:| ]+\|$/.test(lines[i + 1])) {
|
|
1982
|
+
const headerCells = line.split("|").map((c) => c.trim()).filter(Boolean);
|
|
1983
|
+
i += 2;
|
|
1984
|
+
const rows = [];
|
|
1985
|
+
while (i < lines.length && /^\|(.+)\|$/.test(lines[i])) {
|
|
1986
|
+
rows.push(lines[i].split("|").map((c) => c.trim()).filter(Boolean));
|
|
1987
|
+
i++;
|
|
1988
|
+
}
|
|
1989
|
+
const thCells = headerCells.map((c) => `<th>${renderInline(c)}</th>`).join("");
|
|
1990
|
+
const trRows = rows.map((r) => `<tr>${r.map((c) => `<td>${renderInline(c)}</td>`).join("")}</tr>`).join("");
|
|
1991
|
+
blocks.push(`<table><thead><tr>${thCells}</tr></thead><tbody>${trRows}</tbody></table>`);
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
if (!line.trim()) {
|
|
1995
|
+
i++;
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
const pl = [];
|
|
1999
|
+
while (i < lines.length && lines[i].trim() && !/^#{1,4}\s/.test(lines[i]) && !/^\s*[-*]\s+/.test(lines[i]) && !/^\s*\d+\.\s+/.test(lines[i]) && !/^>\s?/.test(lines[i]) && !lines[i].trimStart().startsWith("```")) {
|
|
2000
|
+
pl.push(lines[i]);
|
|
2001
|
+
i++;
|
|
2002
|
+
}
|
|
2003
|
+
if (pl.length) blocks.push(`<p>${renderInline(pl.join(" "))}</p>`);
|
|
2004
|
+
}
|
|
2005
|
+
return blocks.join("\n");
|
|
2006
|
+
}
|
|
2007
|
+
function processContentPage(config2, page) {
|
|
2008
|
+
if (!page.source) {
|
|
2009
|
+
console.warn(` Skipping content page '${page.route}': no source file`);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const srcPath = path3.resolve(ROOT, page.source);
|
|
2013
|
+
if (!fs2.existsSync(srcPath)) {
|
|
2014
|
+
console.warn(` Skipping content page '${page.route}': source not found (${srcPath})`);
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const raw = fs2.readFileSync(srcPath, "utf8");
|
|
2018
|
+
const ext = path3.extname(srcPath).toLowerCase();
|
|
2019
|
+
let html;
|
|
2020
|
+
if (ext === ".html" || ext === ".htm") {
|
|
2021
|
+
html = raw;
|
|
2022
|
+
} else {
|
|
2023
|
+
const stripped = stripFrontmatter(raw);
|
|
2024
|
+
html = renderMarkdown(stripped);
|
|
2025
|
+
}
|
|
2026
|
+
const pagesDir = path3.join(PUBLIC, "pages");
|
|
2027
|
+
fs2.mkdirSync(pagesDir, { recursive: true });
|
|
2028
|
+
writeJson(path3.join(pagesDir, `${page.route}.json`), { title: page.title, html });
|
|
2029
|
+
console.log(` Generated content page: ${page.route} (${ext})`);
|
|
2030
|
+
if (page.translations) {
|
|
2031
|
+
for (const [lang, tr] of Object.entries(page.translations)) {
|
|
2032
|
+
const { source, title: trTitle } = tr;
|
|
2033
|
+
if (!source) continue;
|
|
2034
|
+
const trSrcPath = path3.resolve(ROOT, source);
|
|
2035
|
+
if (!fs2.existsSync(trSrcPath)) {
|
|
2036
|
+
console.warn(` Skipping '${page.route}' translation '${lang}': source not found (${trSrcPath})`);
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
const trRaw = fs2.readFileSync(trSrcPath, "utf8");
|
|
2040
|
+
const trExt = path3.extname(trSrcPath).toLowerCase();
|
|
2041
|
+
let trHtml;
|
|
2042
|
+
if (trExt === ".html" || trExt === ".htm") {
|
|
2043
|
+
trHtml = trRaw;
|
|
2044
|
+
} else {
|
|
2045
|
+
trHtml = renderMarkdown(stripFrontmatter(trRaw));
|
|
2046
|
+
}
|
|
2047
|
+
writeJson(path3.join(pagesDir, `${page.route}.${lang}.json`), { title: trTitle || page.title, html: trHtml });
|
|
2048
|
+
console.log(` Generated localized page: ${page.route}.${lang} (${trExt})`);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function stripFrontmatter(text) {
|
|
2053
|
+
const lines = text.split("\n");
|
|
2054
|
+
if (lines[0] !== "---") return text;
|
|
2055
|
+
let end = -1;
|
|
2056
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2057
|
+
if (lines[i] === "---") {
|
|
2058
|
+
end = i;
|
|
2059
|
+
break;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (end < 0) return text;
|
|
2063
|
+
return lines.slice(end + 1).join("\n").trim();
|
|
2064
|
+
}
|
|
2065
|
+
var pageProcessors = {
|
|
2066
|
+
news: processNewsPage,
|
|
2067
|
+
page: processContentPage,
|
|
2068
|
+
about: processContentPage
|
|
2069
|
+
};
|
|
2070
|
+
function synthesizePages(config2) {
|
|
2071
|
+
const pages = [];
|
|
2072
|
+
if (config2.newsDir) pages.push({ type: "news", route: "news", title: "News", icon: "newspaper" });
|
|
2073
|
+
return pages;
|
|
2074
|
+
}
|
|
2075
|
+
function processPages(config2) {
|
|
2076
|
+
const pages = config2.pages || synthesizePages(config2);
|
|
2077
|
+
for (const page of pages) {
|
|
2078
|
+
const processor = pageProcessors[page.type];
|
|
2079
|
+
if (processor) processor(config2, page);
|
|
2080
|
+
}
|
|
2081
|
+
return pages;
|
|
2082
|
+
}
|
|
2083
|
+
var processedPages = processPages(config);
|
|
2084
|
+
var _pagesDir = path3.join(PUBLIC, "pages");
|
|
2085
|
+
for (const ds of config.datasets || []) {
|
|
2086
|
+
if (!ds.localPath) continue;
|
|
2087
|
+
const dsDir = path3.resolve(ROOT, ds.localPath);
|
|
2088
|
+
const defaultLang = (ds.languages || ["eng"])[0];
|
|
2089
|
+
const route = `${ds.id}-about`;
|
|
2090
|
+
const dsTranslations = ds.translations || {};
|
|
2091
|
+
const defaultSrc = [
|
|
2092
|
+
path3.join(dsDir, `about-${defaultLang}.md`),
|
|
2093
|
+
path3.join(dsDir, "about.md")
|
|
2094
|
+
].find((p) => fs2.existsSync(p));
|
|
2095
|
+
if (defaultSrc) {
|
|
2096
|
+
const raw = fs2.readFileSync(defaultSrc, "utf8");
|
|
2097
|
+
const html = renderMarkdown(stripFrontmatter(raw));
|
|
2098
|
+
writeJson(path3.join(_pagesDir, `${route}.json`), { title: "About", html });
|
|
2099
|
+
console.log(` Auto-generated dataset about page: ${route} (from ${path3.basename(defaultSrc)})`);
|
|
2100
|
+
}
|
|
2101
|
+
const uiLangs = (config.uiLanguages || []).map((l) => l.code).filter((l) => l !== defaultLang);
|
|
2102
|
+
for (const lang of uiLangs) {
|
|
2103
|
+
const trAboutSrc = path3.join(dsDir, `about-${lang}.md`);
|
|
2104
|
+
if (!fs2.existsSync(trAboutSrc)) continue;
|
|
2105
|
+
const trRaw = fs2.readFileSync(trAboutSrc, "utf8");
|
|
2106
|
+
const trHtml = renderMarkdown(stripFrontmatter(trRaw));
|
|
2107
|
+
const trTitle = dsTranslations[lang]?.title ? `About ${dsTranslations[lang].title}` : "About";
|
|
2108
|
+
writeJson(path3.join(_pagesDir, `${route}.${lang}.json`), { title: trTitle, html: trHtml });
|
|
2109
|
+
console.log(` Auto-generated dataset about page: ${route}.${lang}`);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
var siteBranding = { ...config.branding };
|
|
2113
|
+
var basePathPrefix = process.env.BASE_PATH?.replace(/\/+$/, "") || "";
|
|
2114
|
+
for (const key of ["logo", "footerLogo"]) {
|
|
2115
|
+
const suffix = key === "logo" ? "logo.svg" : "footer-logo.svg";
|
|
2116
|
+
if (siteBranding[key]) {
|
|
2117
|
+
const updated = { ...siteBranding[key], path: `${basePathPrefix}/logos/${config.id}-${suffix}` };
|
|
2118
|
+
if (siteBranding[key].localLight) updated.light = `${basePathPrefix}/logos/${config.id}-${suffix.replace(".svg", "-light.svg")}`;
|
|
2119
|
+
if (siteBranding[key].localDark) updated.dark = `${basePathPrefix}/logos/${config.id}-${suffix.replace(".svg", "-dark.svg")}`;
|
|
2120
|
+
delete updated.localPath;
|
|
2121
|
+
delete updated.remoteUrl;
|
|
2122
|
+
delete updated.localLight;
|
|
2123
|
+
delete updated.localDark;
|
|
2124
|
+
siteBranding[key] = updated;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
var datasetTranslations = {};
|
|
2128
|
+
for (const d of config.datasets) {
|
|
2129
|
+
const reg = registerCache[d.id];
|
|
2130
|
+
const translations = { ...d.translations };
|
|
2131
|
+
if (reg?.description && typeof reg.description === "object") {
|
|
2132
|
+
for (const [lang, desc] of Object.entries(reg.description)) {
|
|
2133
|
+
if (!translations[lang]) translations[lang] = {};
|
|
2134
|
+
translations[lang].description = desc;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (reg?.ref) {
|
|
2138
|
+
const langs = reg.languages || [];
|
|
2139
|
+
for (const lang of langs) {
|
|
2140
|
+
if (!translations[lang]) translations[lang] = {};
|
|
2141
|
+
if (!translations[lang].title) translations[lang].title = reg.ref;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (Object.keys(translations).length > 0) {
|
|
2145
|
+
datasetTranslations[d.id] = translations;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
writeJson(path3.join(PUBLIC, "site-config.json"), {
|
|
2149
|
+
id: config.id,
|
|
2150
|
+
domain: config.domain,
|
|
2151
|
+
title: config.title,
|
|
2152
|
+
subtitle: config.subtitle,
|
|
2153
|
+
description: config.description,
|
|
2154
|
+
translations: config.translations || void 0,
|
|
2155
|
+
datasets: config.datasets.map((d) => d.id),
|
|
2156
|
+
datasetGroups: config.datasetGroups || void 0,
|
|
2157
|
+
datasetTranslations: Object.keys(datasetTranslations).length ? datasetTranslations : void 0,
|
|
2158
|
+
defaultDataset: config.datasets.length === 1 ? config.datasets[0].id : void 0,
|
|
2159
|
+
uiLanguages: config.uiLanguages || void 0,
|
|
2160
|
+
branding: siteBranding,
|
|
2161
|
+
analytics: config.analytics,
|
|
2162
|
+
features: config.features,
|
|
2163
|
+
social: config.social,
|
|
2164
|
+
nav: config.nav,
|
|
2165
|
+
footerNav: config.footerNav,
|
|
2166
|
+
defaults: config.defaults,
|
|
2167
|
+
email: config.email,
|
|
2168
|
+
pages: processedPages.length > 0 ? processedPages : void 0,
|
|
2169
|
+
contributors: config.contributors || void 0,
|
|
2170
|
+
copyright: config.copyright || void 0
|
|
2171
|
+
});
|
|
2172
|
+
console.log("Generated site-config.json");
|
|
2173
|
+
var total = Object.values(counts).reduce((s, n) => s + n, 0);
|
|
2174
|
+
console.log(`
|
|
2175
|
+
Done! Generated data for ${total} concepts across ${registry.length} datasets.`);
|
|
2176
|
+
for (const [id, count] of Object.entries(counts)) {
|
|
2177
|
+
console.log(` ${id}: ${count} concepts`);
|
|
2178
|
+
}
|
|
2179
|
+
await writeBuildActivity(total, registry.map((r) => r.id), refMaps.uriBase);
|
|
2180
|
+
fs2.writeFileSync(path3.join(DATA, "_vocab.ttl"), await buildVocabularyTurtle());
|
|
2181
|
+
console.log("Emitted vocabulary graph: data/_vocab.ttl");
|
|
2182
|
+
var contributors = config.contributors ?? [];
|
|
2183
|
+
if (contributors.length > 0) {
|
|
2184
|
+
fs2.writeFileSync(path3.join(DATA, "agents.ttl"), await buildAgentsTurtle(contributors, refMaps.uriBase + "/agent", refMaps.uriBase + "/org"));
|
|
2185
|
+
console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
|
|
2186
|
+
}
|
|
2187
|
+
for (const ds of registry) {
|
|
2188
|
+
const bibPath = path3.join(DATA, ds.id, "bibliography.json");
|
|
2189
|
+
if (fs2.existsSync(bibPath)) {
|
|
2190
|
+
const bibJson = JSON.parse(fs2.readFileSync(bibPath, "utf8"));
|
|
2191
|
+
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson, refMaps.uriBase);
|
|
2192
|
+
fs2.writeFileSync(path3.join(DATA, ds.id, "bib.ttl"), bibTtl);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
var datasetVersions = registry.map((ds) => ({
|
|
2196
|
+
registerId: ds.id,
|
|
2197
|
+
datasetIri: `${refMaps.uriBase}/${ds.id}/`,
|
|
2198
|
+
versions: [
|
|
2199
|
+
{
|
|
2200
|
+
version: pkgVersionForVersions(),
|
|
2201
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2202
|
+
changeSummary: `Build ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`
|
|
2203
|
+
}
|
|
2204
|
+
]
|
|
2205
|
+
}));
|
|
2206
|
+
if (datasetVersions.length > 0) {
|
|
2207
|
+
const versionTtl = (await Promise.all(datasetVersions.map(
|
|
2208
|
+
(v) => buildVersionHistoryTurtle({
|
|
2209
|
+
registerId: v.registerId,
|
|
2210
|
+
datasetIri: v.datasetIri,
|
|
2211
|
+
versions: v.versions,
|
|
2212
|
+
associatedAgentIri: process.env.CI_BOT_AGENT_IRI ?? null
|
|
2213
|
+
})
|
|
2214
|
+
))).join("\n");
|
|
2215
|
+
fs2.writeFileSync(path3.join(DATA, "versions.ttl"), versionTtl);
|
|
2216
|
+
console.log(`Emitted versions graph: data/versions.ttl (${datasetVersions.length} datasets)`);
|
|
2217
|
+
}
|
|
2218
|
+
function pkgVersionForVersions() {
|
|
2219
|
+
return JSON.parse(fs2.readFileSync(path3.join(__dirname3, "..", "package.json"), "utf8")).version;
|
|
2220
|
+
}
|