@glossarist/concept-browser 0.7.134 → 0.7.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/data/concept-model/SOURCE.json +2 -3
- package/data/concept-model/glossarist.context.jsonld +15 -0
- package/data/concept-model/glossarist.ttl +86 -1
- package/data/concept-model/shapes/glossarist.shacl.ttl +169 -1
- package/package.json +2 -2
- package/scripts/__tests__/doctor.test.ts +45 -5
- package/scripts/doctor.js +64 -34
- package/scripts/doctor.ts +83 -35
- package/scripts/fetch-datasets.js +1 -0
- package/scripts/generate-data.js +73 -231
- package/scripts/generate-data.ts +94 -41
- package/scripts/lib/concept-uri.ts +11 -0
- package/scripts/load-site-config.js +1 -0
- package/scripts/load-site-config.ts +1 -0
- package/scripts/process-about-pages.js +1 -0
- package/src/__tests__/scripts/concept-csv.test.ts +106 -0
- package/src/composables/use-format-registry.ts +1 -0
- package/src/config/types.ts +1 -0
- package/src/data/ontology-schema.json +374 -8
- package/scripts/lib/concept-formats.ts +0 -206
- package/src/__tests__/concept-formats.test.ts +0 -111
- package/src/utils/concept-formats.ts +0 -123
package/scripts/doctor.ts
CHANGED
|
@@ -22,7 +22,9 @@ export const NODE_MIN_MAJOR = 18;
|
|
|
22
22
|
/**
|
|
23
23
|
* @typedef {'pass'|'fail'|'warn'} CheckStatus
|
|
24
24
|
* @typedef {{ id: string, label: string, status: CheckStatus, detail?: string, hint?: string }} CheckResult
|
|
25
|
-
* @typedef {{
|
|
25
|
+
* @typedef {{ id: string, localPath?: string }} RegisteredDataset
|
|
26
|
+
* @typedef {{ registered: RegisteredDataset[], source: 'datasets.yml'|'site-config.yml'|null, error?: string }} DatasetRegistry
|
|
27
|
+
* @typedef {{ cwd: string, pkgRoot: string, datasetsYml?: any, datasetsYmlError?: string, registry: DatasetRegistry }} DoctorContext
|
|
26
28
|
*/
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -63,8 +65,12 @@ function nodeVersionCheck() {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
function packageDepsCheck(ctx) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
// When installed as a library, the CLI's deps may be hoisted into the
|
|
69
|
+
// consumer's node_modules or nested under the package's own — accept
|
|
70
|
+
// either. Checking only PKG_ROOT (the installed package dir) reported
|
|
71
|
+
// false "node_modules missing" in consumer deployments.
|
|
72
|
+
const roots = [resolve(ctx.cwd, 'node_modules'), resolve(ctx.pkgRoot, 'node_modules')];
|
|
73
|
+
if (!roots.some((nm) => existsSync(nm))) {
|
|
68
74
|
return {
|
|
69
75
|
id: 'package-deps',
|
|
70
76
|
label: 'Dependencies installed',
|
|
@@ -74,7 +80,7 @@ function packageDepsCheck(ctx) {
|
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
const required = ['vue', 'vue-router', 'pinia', 'vite', 'n3', 'glossarist', '@rdfjs/dataset'];
|
|
77
|
-
const missing = required.filter((p) => !existsSync(join(nm, p)));
|
|
83
|
+
const missing = required.filter((p) => !roots.some((nm) => existsSync(join(nm, p))));
|
|
78
84
|
if (missing.length) {
|
|
79
85
|
return {
|
|
80
86
|
id: 'package-deps',
|
|
@@ -87,67 +93,103 @@ function packageDepsCheck(ctx) {
|
|
|
87
93
|
return { id: 'package-deps', label: 'Dependencies installed', status: 'pass' };
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Dataset registrations come from either `datasets.yml` (source repos to
|
|
98
|
+
* fetch) or `site-config.yml` (deployment sites, entries carry
|
|
99
|
+
* `local_path` to in-repo YAML). Doctor only knew the former, so
|
|
100
|
+
* site-config deployments reported "no datasets registered".
|
|
101
|
+
* @returns {{ registered: Array<{id: string, localPath?: string}>, source: 'datasets.yml'|'site-config.yml'|null, error?: string }}
|
|
102
|
+
*/
|
|
103
|
+
export function resolveDatasetRegistry(cwd, fileError = undefined) {
|
|
104
|
+
if (fileError) return { registered: [], source: 'datasets.yml', error: fileError };
|
|
105
|
+
const ymlPath = resolve(cwd, 'datasets.yml');
|
|
106
|
+
if (existsSync(ymlPath)) {
|
|
107
|
+
const list = normalizeRegistry(yaml.load(readFileSync(ymlPath, 'utf8')));
|
|
108
|
+
return { registered: list, source: 'datasets.yml' };
|
|
109
|
+
}
|
|
110
|
+
const sitePath = resolve(cwd, 'site-config.yml');
|
|
111
|
+
if (existsSync(sitePath)) {
|
|
112
|
+
try {
|
|
113
|
+
const list = normalizeRegistry(yaml.load(readFileSync(sitePath, 'utf8')));
|
|
114
|
+
return { registered: list, source: 'site-config.yml' };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { registered: [], source: 'site-config.yml', error: err.message };
|
|
117
|
+
}
|
|
100
118
|
}
|
|
101
|
-
|
|
119
|
+
return { registered: [], source: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeRegistry(doc) {
|
|
123
|
+
const datasets = doc?.datasets;
|
|
124
|
+
if (!Array.isArray(datasets)) return [];
|
|
125
|
+
return datasets
|
|
126
|
+
.filter((d) => d && typeof d === 'object' && d.id)
|
|
127
|
+
.map((d) => ({ id: String(d.id), localPath: d.local_path ?? d.localPath }));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function datasetsRegisteredCheck(ctx) {
|
|
131
|
+
if (ctx.registry.error) {
|
|
102
132
|
return {
|
|
103
|
-
id: 'datasets-
|
|
104
|
-
label:
|
|
133
|
+
id: 'datasets-registered',
|
|
134
|
+
label: `${ctx.registry.source} parses`,
|
|
105
135
|
status: 'fail',
|
|
106
|
-
detail: ctx.
|
|
136
|
+
detail: ctx.registry.error,
|
|
107
137
|
hint: 'fix the YAML syntax error',
|
|
108
138
|
};
|
|
109
139
|
}
|
|
110
|
-
|
|
111
|
-
if (!Array.isArray(datasets) || datasets.length === 0) {
|
|
140
|
+
if (!ctx.registry.source) {
|
|
112
141
|
return {
|
|
113
|
-
id: 'datasets-
|
|
114
|
-
label: '
|
|
142
|
+
id: 'datasets-registered',
|
|
143
|
+
label: 'Dataset registry present',
|
|
115
144
|
status: 'warn',
|
|
116
|
-
detail: '
|
|
145
|
+
detail: 'neither datasets.yml nor site-config.yml found at project root',
|
|
146
|
+
hint: 'datasets.yml / site-config.yml register every dataset the browser serves',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (ctx.registry.registered.length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
id: 'datasets-registered',
|
|
152
|
+
label: 'Dataset registry lists datasets',
|
|
153
|
+
status: 'warn',
|
|
154
|
+
detail: `\`datasets\` key is empty or missing in ${ctx.registry.source}`,
|
|
117
155
|
hint: 'add at least one dataset entry; see docs/adding-a-dataset.md',
|
|
118
156
|
};
|
|
119
157
|
}
|
|
120
158
|
return {
|
|
121
|
-
id: 'datasets-
|
|
122
|
-
label:
|
|
159
|
+
id: 'datasets-registered',
|
|
160
|
+
label: `${ctx.registry.source} lists ${ctx.registry.registered.length} dataset(s)`,
|
|
123
161
|
status: 'pass',
|
|
124
162
|
};
|
|
125
163
|
}
|
|
126
164
|
|
|
127
165
|
function datasetsFetchedCheck(ctx) {
|
|
128
|
-
const datasets = ctx.
|
|
129
|
-
if (!datasets.length) {
|
|
166
|
+
const datasets = ctx.registry.registered;
|
|
167
|
+
if (!ctx.registry.source || !datasets.length) {
|
|
130
168
|
return { id: 'datasets-fetched', label: 'Source datasets fetched', status: 'warn', detail: 'no datasets registered' };
|
|
131
169
|
}
|
|
132
|
-
|
|
170
|
+
// site-config deployments keep sources in-repo at local_path; the
|
|
171
|
+
// datasets.yml flow fetches source repos into .datasets/{id}/concepts.
|
|
133
172
|
const missing = datasets
|
|
134
|
-
.filter((d) =>
|
|
173
|
+
.filter((d) => {
|
|
174
|
+
if (d.localPath) return !existsSync(resolve(ctx.cwd, d.localPath));
|
|
175
|
+
return !existsSync(join(resolve(ctx.cwd, '.datasets'), d.id, 'concepts'));
|
|
176
|
+
})
|
|
135
177
|
.map((d) => d.id);
|
|
136
178
|
if (missing.length) {
|
|
137
179
|
return {
|
|
138
180
|
id: 'datasets-fetched',
|
|
139
181
|
label: 'Source datasets fetched',
|
|
140
182
|
status: 'fail',
|
|
141
|
-
detail:
|
|
142
|
-
hint: 'run `npm run fetch-datasets`',
|
|
183
|
+
detail: `dataset sources missing: ${missing.join(', ')}`,
|
|
184
|
+
hint: 'run `npm run fetch-datasets` (or check local_path entries in site-config.yml)',
|
|
143
185
|
};
|
|
144
186
|
}
|
|
145
187
|
return { id: 'datasets-fetched', label: 'Source datasets fetched', status: 'pass' };
|
|
146
188
|
}
|
|
147
189
|
|
|
148
190
|
function datasetsGeneratedCheck(ctx) {
|
|
149
|
-
const datasets = ctx.
|
|
150
|
-
if (!datasets.length) {
|
|
191
|
+
const datasets = ctx.registry.registered;
|
|
192
|
+
if (!ctx.registry.source || !datasets.length) {
|
|
151
193
|
return { id: 'datasets-generated', label: 'Generated data present', status: 'warn', detail: 'no datasets registered' };
|
|
152
194
|
}
|
|
153
195
|
const publicData = resolve(ctx.cwd, 'public', 'data');
|
|
@@ -256,7 +298,7 @@ function jsonldContextCheck(ctx) {
|
|
|
256
298
|
export const CHECKS = [
|
|
257
299
|
safe('node-version', nodeVersionCheck),
|
|
258
300
|
safe('package-deps', packageDepsCheck),
|
|
259
|
-
safe('datasets-
|
|
301
|
+
safe('datasets-registered', datasetsRegisteredCheck),
|
|
260
302
|
safe('datasets-fetched', datasetsFetchedCheck),
|
|
261
303
|
safe('datasets-generated', datasetsGeneratedCheck),
|
|
262
304
|
safe('public-datasets-json', publicDatasetsJsonCheck),
|
|
@@ -280,7 +322,13 @@ export async function runDoctor(cwd = process.cwd()) {
|
|
|
280
322
|
}
|
|
281
323
|
}
|
|
282
324
|
/** @type {DoctorContext} */
|
|
283
|
-
const ctx = {
|
|
325
|
+
const ctx = {
|
|
326
|
+
cwd,
|
|
327
|
+
pkgRoot: PKG_ROOT,
|
|
328
|
+
datasetsYml,
|
|
329
|
+
datasetsYmlError,
|
|
330
|
+
registry: resolveDatasetRegistry(cwd, datasetsYmlError),
|
|
331
|
+
};
|
|
284
332
|
|
|
285
333
|
const results = [];
|
|
286
334
|
for (const check of CHECKS) {
|
package/scripts/generate-data.js
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import fs2 from "fs";
|
|
3
3
|
import path3 from "path";
|
|
4
4
|
import yaml4 from "js-yaml";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
naturalSort,
|
|
7
|
+
Register,
|
|
8
|
+
parseMentions,
|
|
9
|
+
InvalidMentionError,
|
|
10
|
+
parseConceptYaml,
|
|
11
|
+
conceptToTurtle,
|
|
12
|
+
conceptsToTbx,
|
|
13
|
+
emitOutputSet
|
|
14
|
+
} from "glossarist";
|
|
6
15
|
|
|
7
16
|
// scripts/load-site-config.ts
|
|
8
17
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -26,6 +35,7 @@ var SNAKE_TO_CAMEL = {
|
|
|
26
35
|
footer_nav: "footerNav",
|
|
27
36
|
footer_logo: "footerLogo",
|
|
28
37
|
news_dir: "newsDir",
|
|
38
|
+
bulk_exports: "bulkExports",
|
|
29
39
|
// Dataset-level
|
|
30
40
|
gcr_package: "gcrPackage",
|
|
31
41
|
source_repo: "sourceRepo",
|
|
@@ -545,6 +555,9 @@ function normalizeBibliography(raw) {
|
|
|
545
555
|
function buildConceptUri(uriBase, registerId, conceptId) {
|
|
546
556
|
return `${uriBase}/dataset/${registerId}/concept/${conceptId}`;
|
|
547
557
|
}
|
|
558
|
+
function buildConceptUriPrefix(uriBase, registerId) {
|
|
559
|
+
return `${uriBase}/dataset/${registerId}/concept/`;
|
|
560
|
+
}
|
|
548
561
|
|
|
549
562
|
// scripts/lib/harmonize-concept.ts
|
|
550
563
|
import fs from "fs";
|
|
@@ -781,207 +794,6 @@ function refsToJsonLd(refs, refMaps2) {
|
|
|
781
794
|
}).filter((r) => r["@id"]);
|
|
782
795
|
}
|
|
783
796
|
|
|
784
|
-
// scripts/lib/turtle-escape.ts
|
|
785
|
-
var STRING_ESCAPES = {
|
|
786
|
-
"\\": "\\\\",
|
|
787
|
-
'"': '\\"',
|
|
788
|
-
"\n": "\\n",
|
|
789
|
-
"\r": "\\r",
|
|
790
|
-
" ": "\\t"
|
|
791
|
-
};
|
|
792
|
-
function ttlLit(s) {
|
|
793
|
-
if (s == null) return '""';
|
|
794
|
-
const escaped = String(s).replace(/[\\\"\n\r\t]/g, (ch) => STRING_ESCAPES[ch] ?? ch);
|
|
795
|
-
return `"${escaped}"`;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// scripts/lib/concept-formats.ts
|
|
799
|
-
function escapeTurtle(s) {
|
|
800
|
-
return ttlLit(s).slice(1, -1);
|
|
801
|
-
}
|
|
802
|
-
function escapeXml(s) {
|
|
803
|
-
const str = Array.isArray(s) ? s.join(", ") : String(s ?? "");
|
|
804
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
805
|
-
}
|
|
806
|
-
function conceptJsonToTurtle(concept) {
|
|
807
|
-
const uri = concept["@id"] || "";
|
|
808
|
-
const id = concept["gl:identifier"] || "";
|
|
809
|
-
const lines = [
|
|
810
|
-
"@prefix skos: <http://www.w3.org/2004/02/skos/core#> .",
|
|
811
|
-
"@prefix dcterms: <http://purl.org/dc/terms/> .",
|
|
812
|
-
""
|
|
813
|
-
];
|
|
814
|
-
const props = [" a skos:Concept"];
|
|
815
|
-
props.push(` skos:notation "${escapeTurtle(id)}"`);
|
|
816
|
-
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
817
|
-
if (lc["gl:designation"]) {
|
|
818
|
-
for (const d of lc["gl:designation"]) {
|
|
819
|
-
const term = d["gl:term"];
|
|
820
|
-
if (!term) continue;
|
|
821
|
-
const pred = d["gl:normativeStatus"] === "preferred" ? "skos:prefLabel" : "skos:altLabel";
|
|
822
|
-
props.push(` ${pred} "${escapeTurtle(term)}"@${lang}`);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
if (lc["gl:definition"]) {
|
|
826
|
-
for (const d of lc["gl:definition"]) {
|
|
827
|
-
if (d["gl:content"]) props.push(` skos:definition "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
if (lc["gl:notes"]) {
|
|
831
|
-
for (const d of lc["gl:notes"]) {
|
|
832
|
-
if (d["gl:content"]) props.push(` skos:scopeNote "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
lines.push(`<${uri}>`);
|
|
837
|
-
lines.push(props.join(" ;\n"));
|
|
838
|
-
lines.push(" .");
|
|
839
|
-
return lines.join("\n");
|
|
840
|
-
}
|
|
841
|
-
function conceptJsonToSkosJsonLd(concept) {
|
|
842
|
-
const uri = concept["@id"] || "";
|
|
843
|
-
const id = concept["gl:identifier"] || "";
|
|
844
|
-
const doc = {
|
|
845
|
-
"@context": {
|
|
846
|
-
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
847
|
-
dcterms: "http://purl.org/dc/terms/",
|
|
848
|
-
"@language": { "@container": "@language" }
|
|
849
|
-
},
|
|
850
|
-
"@id": uri,
|
|
851
|
-
"@type": "skos:Concept",
|
|
852
|
-
"skos:notation": id
|
|
853
|
-
};
|
|
854
|
-
const prefLabels = {};
|
|
855
|
-
const altLabels = {};
|
|
856
|
-
const definitions = {};
|
|
857
|
-
const scopeNotes = {};
|
|
858
|
-
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
859
|
-
const descs = lc["gl:designation"] || [];
|
|
860
|
-
const pref = descs.find((d) => d["gl:normativeStatus"] === "preferred" && d["gl:term"]);
|
|
861
|
-
const alt = descs.find((d) => d["gl:normativeStatus"] !== "preferred" && d["gl:term"]);
|
|
862
|
-
if (pref) prefLabels[lang] = pref["gl:term"];
|
|
863
|
-
if (alt) altLabels[lang] = alt["gl:term"];
|
|
864
|
-
const def = (lc["gl:definition"] || [])[0];
|
|
865
|
-
if (def?.["gl:content"]) definitions[lang] = def["gl:content"];
|
|
866
|
-
const note = (lc["gl:notes"] || [])[0];
|
|
867
|
-
if (note?.["gl:content"]) scopeNotes[lang] = note["gl:content"];
|
|
868
|
-
}
|
|
869
|
-
if (Object.keys(prefLabels).length) doc["skos:prefLabel"] = prefLabels;
|
|
870
|
-
if (Object.keys(altLabels).length) doc["skos:altLabel"] = altLabels;
|
|
871
|
-
if (Object.keys(definitions).length) doc["skos:definition"] = definitions;
|
|
872
|
-
if (Object.keys(scopeNotes).length) doc["skos:scopeNote"] = scopeNotes;
|
|
873
|
-
return JSON.stringify(doc);
|
|
874
|
-
}
|
|
875
|
-
function conceptJsonToTbx(concept) {
|
|
876
|
-
const id = concept["gl:identifier"] || "";
|
|
877
|
-
const uri = concept["@id"] || "";
|
|
878
|
-
const localized = concept["gl:localizedConcept"] || {};
|
|
879
|
-
const langSections = [];
|
|
880
|
-
for (const [lang, lc] of Object.entries(localized)) {
|
|
881
|
-
const descs = lc["gl:designation"] || [];
|
|
882
|
-
const definitions = (lc["gl:definition"] || []).filter((d) => d["gl:content"]);
|
|
883
|
-
const notes = (lc["gl:notes"] || []).filter((d) => d["gl:content"]);
|
|
884
|
-
const examples = (lc["gl:examples"] || []).filter((d) => d["gl:content"]);
|
|
885
|
-
const sources = lc["gl:source"] || [];
|
|
886
|
-
const entryStatus = lc["gl:entryStatus"] || "";
|
|
887
|
-
if (!descs.length && !definitions.length) continue;
|
|
888
|
-
const termEntries = [];
|
|
889
|
-
for (const d of descs) {
|
|
890
|
-
const term = d["gl:term"];
|
|
891
|
-
if (!term) continue;
|
|
892
|
-
const status = d["gl:normativeStatus"] || "";
|
|
893
|
-
const type = d["@type"] || "";
|
|
894
|
-
let gramGrp = "";
|
|
895
|
-
if (d["gl:grammarInfo"] && d["gl:grammarInfo"].length > 0) {
|
|
896
|
-
const gi = d["gl:grammarInfo"][0];
|
|
897
|
-
if (gi["gl:gender"]) gramGrp = `
|
|
898
|
-
<grammaticalGender>${escapeXml(gi["gl:gender"])}</grammaticalGender>`;
|
|
899
|
-
if (gi["gl:number"]) gramGrp += `
|
|
900
|
-
<grammaticalNumber>${escapeXml(gi["gl:number"])}</grammaticalNumber>`;
|
|
901
|
-
for (const pos of ["noun", "verb", "adj", "adverb", "preposition", "participle"]) {
|
|
902
|
-
if (gi[`gl:${pos}`]) gramGrp += `
|
|
903
|
-
<partOfSpeech>${pos}</partOfSpeech>`;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
let posBlock = "";
|
|
907
|
-
if (type.includes("Abbreviation")) posBlock = "\n <partOfSpeech>abbreviation</partOfSpeech>";
|
|
908
|
-
if (type.includes("Symbol")) posBlock = "\n <partOfSpeech>symbol</partOfSpeech>";
|
|
909
|
-
termEntries.push(` <termEntry>
|
|
910
|
-
<langSet xml:lang="${lang}">
|
|
911
|
-
<tig>
|
|
912
|
-
<term>${escapeXml(term)}</term>${gramGrp}${posBlock}
|
|
913
|
-
</tig>
|
|
914
|
-
</langSet>
|
|
915
|
-
</termEntry>`);
|
|
916
|
-
}
|
|
917
|
-
let defBlock = "";
|
|
918
|
-
if (definitions.length) {
|
|
919
|
-
const defParts = definitions.map((d) => ` <p>${escapeXml(d["gl:content"])}</p>`).join("\n");
|
|
920
|
-
defBlock = `
|
|
921
|
-
<descrip type="definition">
|
|
922
|
-
${defParts}
|
|
923
|
-
</descrip>`;
|
|
924
|
-
}
|
|
925
|
-
let noteBlock = "";
|
|
926
|
-
for (let i = 0; i < notes.length; i++) {
|
|
927
|
-
noteBlock += `
|
|
928
|
-
<note type="note">${escapeXml(notes[i]["gl:content"])}</note>`;
|
|
929
|
-
}
|
|
930
|
-
for (let i = 0; i < examples.length; i++) {
|
|
931
|
-
noteBlock += `
|
|
932
|
-
<note type="example">${escapeXml(examples[i]["gl:content"])}</note>`;
|
|
933
|
-
}
|
|
934
|
-
let sourceBlock = "";
|
|
935
|
-
for (const src of sources) {
|
|
936
|
-
const origin = src["gl:origin"] || {};
|
|
937
|
-
const parts = [];
|
|
938
|
-
const ref = origin["gl:ref"];
|
|
939
|
-
if (ref) {
|
|
940
|
-
const refParts = [];
|
|
941
|
-
if (ref["gl:source"]) refParts.push(ref["gl:source"]);
|
|
942
|
-
if (ref["gl:id"]) refParts.push(ref["gl:id"]);
|
|
943
|
-
parts.push(refParts.join(" ") || "");
|
|
944
|
-
}
|
|
945
|
-
if (origin["gl:locality"]) {
|
|
946
|
-
const loc = origin["gl:locality"];
|
|
947
|
-
if (loc["gl:referenceFrom"]) parts.push(loc["gl:localityType"] ? `${loc["gl:localityType"]} ${loc["gl:referenceFrom"]}` : loc["gl:referenceFrom"]);
|
|
948
|
-
}
|
|
949
|
-
if (parts.filter(Boolean).length) {
|
|
950
|
-
sourceBlock += `
|
|
951
|
-
<ref>${escapeXml(parts.filter(Boolean).join(", "))}</ref>`;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
let statusBlock = "";
|
|
955
|
-
if (entryStatus) {
|
|
956
|
-
statusBlock += `
|
|
957
|
-
<descrip type="entryStatus">${escapeXml(entryStatus)}</descrip>`;
|
|
958
|
-
}
|
|
959
|
-
const termEntriesBlock = termEntries.length ? "\n" + termEntries.join("\n") : "";
|
|
960
|
-
langSections.push({ lang, termEntries, blocks: [defBlock, noteBlock, sourceBlock, statusBlock].filter((b) => b).join("") });
|
|
961
|
-
}
|
|
962
|
-
if (!langSections.length) return "";
|
|
963
|
-
const bodyEntries = langSections.map((ls) => {
|
|
964
|
-
return ` <languageSection xml:lang="${ls.lang}">${ls.blocks}
|
|
965
|
-
</languageSection>`;
|
|
966
|
-
}).join("\n");
|
|
967
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
968
|
-
<tbx style="dca" type="TBX-Basic" xml:lang="en" xmlns="urn:iso:std:iso:30042:ed-2">
|
|
969
|
-
<tbxHeader>
|
|
970
|
-
<fileDesc>
|
|
971
|
-
<source>${escapeXml(uri)}</source>
|
|
972
|
-
</fileDesc>
|
|
973
|
-
</tbxHeader>
|
|
974
|
-
<text>
|
|
975
|
-
<body>
|
|
976
|
-
<conceptEntry id="${escapeXml(id)}">
|
|
977
|
-
${bodyEntries}
|
|
978
|
-
</conceptEntry>
|
|
979
|
-
</body>
|
|
980
|
-
</text>
|
|
981
|
-
</tbx>
|
|
982
|
-
`;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
797
|
// scripts/generate-data.ts
|
|
986
798
|
var __dirname3 = path3.dirname(new URL(import.meta.url).pathname);
|
|
987
799
|
var ROOT = process.cwd();
|
|
@@ -1417,9 +1229,10 @@ async function processDataset(dir, register, opts) {
|
|
|
1417
1229
|
for (const f of fs2.readdirSync(conceptsDir)) fs2.unlinkSync(path3.join(conceptsDir, f));
|
|
1418
1230
|
}
|
|
1419
1231
|
const concepts = [];
|
|
1232
|
+
const conceptInstances = [];
|
|
1420
1233
|
const langTermCounts = {};
|
|
1421
1234
|
const langDefCounts = {};
|
|
1422
|
-
const availableFormats = ["ttl", "jsonld", "yaml", "tbx"];
|
|
1235
|
+
const availableFormats = ["ttl", "jsonld", "yaml", "tbx", "csv"];
|
|
1423
1236
|
const designationLookup = /* @__PURE__ */ new Map();
|
|
1424
1237
|
for (const file of files) {
|
|
1425
1238
|
try {
|
|
@@ -1521,13 +1334,28 @@ async function processDataset(dir, register, opts) {
|
|
|
1521
1334
|
const termid = String(conceptYaml.termid);
|
|
1522
1335
|
const jsonld = yamlToJsonLd(conceptYaml, register, dsRefMaps);
|
|
1523
1336
|
writeJson(path3.join(conceptsDir, `${termid}.json`), jsonld);
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1337
|
+
let conceptInstance = null;
|
|
1338
|
+
try {
|
|
1339
|
+
conceptInstance = parseConceptYaml(fs2.readFileSync(path3.join(dir, file), "utf8"), termid);
|
|
1340
|
+
} catch (e) {
|
|
1341
|
+
console.warn(` ${termid}: canonical parse failed (${e.message}); skipping glossarist exports`);
|
|
1342
|
+
}
|
|
1343
|
+
if (conceptInstance) {
|
|
1344
|
+
conceptInstances.push(conceptInstance);
|
|
1345
|
+
fs2.writeFileSync(
|
|
1346
|
+
path3.join(conceptsDir, `${termid}.ttl`),
|
|
1347
|
+
conceptToTurtle(conceptInstance, { uriBase: refMaps.uriBase, registerId: register })
|
|
1348
|
+
);
|
|
1349
|
+
const conceptJsonLd = await emitOutputSet([conceptInstance], {
|
|
1350
|
+
registerId: register,
|
|
1351
|
+
uriBase: refMaps.uriBase,
|
|
1352
|
+
formats: ["jsonld"]
|
|
1353
|
+
});
|
|
1354
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.jsonld`), conceptJsonLd.jsonld);
|
|
1355
|
+
const tbxContent = conceptsToTbx([conceptInstance], { registerId: register });
|
|
1356
|
+
if (tbxContent) {
|
|
1357
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.tbx`), tbxContent);
|
|
1358
|
+
}
|
|
1531
1359
|
}
|
|
1532
1360
|
fs2.copyFileSync(path3.join(dir, file), path3.join(conceptsDir, `${termid}.yaml`));
|
|
1533
1361
|
concepts.push({
|
|
@@ -1543,6 +1371,13 @@ async function processDataset(dir, register, opts) {
|
|
|
1543
1371
|
console.warn(` Skipping ${file}: ${e.message}`);
|
|
1544
1372
|
}
|
|
1545
1373
|
}
|
|
1374
|
+
const aggregate = await emitOutputSet(conceptInstances, {
|
|
1375
|
+
registerId: register,
|
|
1376
|
+
uriBase: refMaps.uriBase,
|
|
1377
|
+
languageOrder: opts.languageOrder || opts.languages
|
|
1378
|
+
});
|
|
1379
|
+
fs2.writeFileSync(path3.join(DATA, register, `${register}.csv`), aggregate.csv);
|
|
1380
|
+
fs2.writeFileSync(path3.join(DATA, register, `${register}.jsonld`), aggregate.jsonld);
|
|
1546
1381
|
const CHUNK_SIZE = 500;
|
|
1547
1382
|
const chunks = [];
|
|
1548
1383
|
for (let i = 0; i < concepts.length; i += CHUNK_SIZE) {
|
|
@@ -1585,7 +1420,7 @@ async function processDataset(dir, register, opts) {
|
|
|
1585
1420
|
fs2.writeFileSync(
|
|
1586
1421
|
path3.join(DATA, register, "graph-nodes.json"),
|
|
1587
1422
|
JSON.stringify({
|
|
1588
|
-
uriPrefix:
|
|
1423
|
+
uriPrefix: buildConceptUriPrefix(refMaps.uriBase, register),
|
|
1589
1424
|
registerId: register,
|
|
1590
1425
|
nodes: graphNodeEntries
|
|
1591
1426
|
})
|
|
@@ -1612,28 +1447,32 @@ async function processDataset(dir, register, opts) {
|
|
|
1612
1447
|
definitions: langDefCounts[lang] || 0
|
|
1613
1448
|
};
|
|
1614
1449
|
}
|
|
1450
|
+
const BULK_FORMAT_BY_EXT = {
|
|
1451
|
+
".ttl": "turtle",
|
|
1452
|
+
".jsonld": "jsonld",
|
|
1453
|
+
".xml": "tbx",
|
|
1454
|
+
".jsonl": "jsonl",
|
|
1455
|
+
".yaml": "yaml",
|
|
1456
|
+
".csv": "csv"
|
|
1457
|
+
};
|
|
1458
|
+
const bulkFormats = [];
|
|
1459
|
+
const addBulkFormat = (file) => {
|
|
1460
|
+
const dest = path3.join(DATA, register, file);
|
|
1461
|
+
if (!fs2.existsSync(dest) || bulkFormats.some((f) => f.file === file)) return;
|
|
1462
|
+
const formatName = BULK_FORMAT_BY_EXT[path3.extname(file)] || path3.extname(file).slice(1);
|
|
1463
|
+
bulkFormats.push({ file, format: formatName, size: fs2.statSync(dest).size });
|
|
1464
|
+
};
|
|
1465
|
+
for (const file of [`${register}.csv`, `${register}.jsonld`]) addBulkFormat(file);
|
|
1615
1466
|
const sourceRoot = path3.dirname(dir);
|
|
1616
1467
|
const compiledDir = path3.join(sourceRoot, "compiled");
|
|
1617
|
-
const bulkFormats = [];
|
|
1618
1468
|
if (fs2.existsSync(compiledDir)) {
|
|
1469
|
+
let copied = 0;
|
|
1619
1470
|
for (const file of fs2.readdirSync(compiledDir)) {
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
const ext = path3.extname(file);
|
|
1624
|
-
const formatMap = {
|
|
1625
|
-
".ttl": "turtle",
|
|
1626
|
-
".jsonld": "jsonld",
|
|
1627
|
-
".xml": "tbx",
|
|
1628
|
-
".jsonl": "jsonl",
|
|
1629
|
-
".yaml": "yaml"
|
|
1630
|
-
};
|
|
1631
|
-
const formatName = formatMap[ext] || ext.slice(1);
|
|
1632
|
-
bulkFormats.push({ file, format: formatName, size: fs2.statSync(src).size });
|
|
1633
|
-
}
|
|
1634
|
-
if (bulkFormats.length) {
|
|
1635
|
-
console.log(` Copied ${bulkFormats.length} bulk format files`);
|
|
1471
|
+
fs2.copyFileSync(path3.join(compiledDir, file), path3.join(DATA, register, file));
|
|
1472
|
+
addBulkFormat(file);
|
|
1473
|
+
copied += 1;
|
|
1636
1474
|
}
|
|
1475
|
+
if (copied) console.log(` Copied ${copied} bulk format files from compiled/`);
|
|
1637
1476
|
}
|
|
1638
1477
|
const sourceStats = Array.from(stats.sourceMap.values()).map((s) => ({
|
|
1639
1478
|
ref: s.ref,
|
|
@@ -1675,7 +1514,6 @@ async function processDataset(dir, register, opts) {
|
|
|
1675
1514
|
year: opts.year ?? void 0,
|
|
1676
1515
|
languageStats: langStats,
|
|
1677
1516
|
availableFormats,
|
|
1678
|
-
bulkFormats,
|
|
1679
1517
|
hasBibliography: opts.hasBibliography,
|
|
1680
1518
|
hasImages: opts.hasImages
|
|
1681
1519
|
};
|
|
@@ -1685,8 +1523,10 @@ async function processDataset(dir, register, opts) {
|
|
|
1685
1523
|
if (opts.status) manifest.editionStatus = opts.status;
|
|
1686
1524
|
if (opts.ordering) manifest.ordering = opts.ordering;
|
|
1687
1525
|
if (opts.sections && opts.sections.length > 0) manifest.sections = opts.sections;
|
|
1688
|
-
writeJson(path3.join(DATA, register, "manifest.json"), manifest);
|
|
1689
1526
|
await writeDatasetRdf(register, manifest, concepts, refMaps, opts);
|
|
1527
|
+
addBulkFormat(`${register}.ttl`);
|
|
1528
|
+
manifest.bulkFormats = opts.bulkExports?.length ? bulkFormats.filter((f) => opts.bulkExports.includes(f.format)) : bulkFormats;
|
|
1529
|
+
writeJson(path3.join(DATA, register, "manifest.json"), manifest);
|
|
1690
1530
|
const bibPath = path3.join(sourceRoot, "bibliography.yaml");
|
|
1691
1531
|
if (fs2.existsSync(bibPath)) {
|
|
1692
1532
|
const rawBib = readYaml(bibPath);
|
|
@@ -1736,6 +1576,7 @@ for (const ds of config.datasets) {
|
|
|
1736
1576
|
}
|
|
1737
1577
|
}
|
|
1738
1578
|
var refMaps = buildRefMaps(config, registerCache);
|
|
1579
|
+
var bulkExports = (config.bulkExports || []).map((f) => f === "ttl" ? "turtle" : f);
|
|
1739
1580
|
for (let i = 0; i < config.datasets.length; i++) {
|
|
1740
1581
|
const ds = config.datasets[i];
|
|
1741
1582
|
const dir = path3.join(datasetDir(ds), "concepts");
|
|
@@ -1769,7 +1610,8 @@ for (let i = 0; i < config.datasets.length; i++) {
|
|
|
1769
1610
|
ordering: reg?.ordering || null,
|
|
1770
1611
|
sections: reg?.sections ? reg.sections.map((s) => s.toJSON()) : [],
|
|
1771
1612
|
hasBibliography: fs2.existsSync(path3.join(datasetDir(ds), "bibliography.yaml")),
|
|
1772
|
-
hasImages: fs2.existsSync(path3.join(datasetDir(ds), "images"))
|
|
1613
|
+
hasImages: fs2.existsSync(path3.join(datasetDir(ds), "images")),
|
|
1614
|
+
bulkExports
|
|
1773
1615
|
});
|
|
1774
1616
|
const resolvedOwner = firstNonEmpty(reg?.owner, ds.owner) ?? "";
|
|
1775
1617
|
const resolvedTags = firstNonEmpty(reg?.tags, ds.tags) ?? [];
|