@glossarist/concept-browser 0.7.135 → 0.7.137
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/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/generate-data.js +44 -308
- package/scripts/generate-data.ts +53 -27
- package/scripts/lib/concept-uri.ts +11 -0
- package/src/__tests__/scripts/concept-csv.test.ts +52 -88
- package/src/data/ontology-schema.json +374 -8
- package/scripts/lib/concept-formats.ts +0 -326
- 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";
|
|
@@ -546,6 +555,9 @@ function normalizeBibliography(raw) {
|
|
|
546
555
|
function buildConceptUri(uriBase, registerId, conceptId) {
|
|
547
556
|
return `${uriBase}/dataset/${registerId}/concept/${conceptId}`;
|
|
548
557
|
}
|
|
558
|
+
function buildConceptUriPrefix(uriBase, registerId) {
|
|
559
|
+
return `${uriBase}/dataset/${registerId}/concept/`;
|
|
560
|
+
}
|
|
549
561
|
|
|
550
562
|
// scripts/lib/harmonize-concept.ts
|
|
551
563
|
import fs from "fs";
|
|
@@ -782,301 +794,6 @@ function refsToJsonLd(refs, refMaps2) {
|
|
|
782
794
|
}).filter((r) => r["@id"]);
|
|
783
795
|
}
|
|
784
796
|
|
|
785
|
-
// scripts/lib/turtle-escape.ts
|
|
786
|
-
var STRING_ESCAPES = {
|
|
787
|
-
"\\": "\\\\",
|
|
788
|
-
'"': '\\"',
|
|
789
|
-
"\n": "\\n",
|
|
790
|
-
"\r": "\\r",
|
|
791
|
-
" ": "\\t"
|
|
792
|
-
};
|
|
793
|
-
function ttlLit(s) {
|
|
794
|
-
if (s == null) return '""';
|
|
795
|
-
const escaped = String(s).replace(/[\\\"\n\r\t]/g, (ch) => STRING_ESCAPES[ch] ?? ch);
|
|
796
|
-
return `"${escaped}"`;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// scripts/lib/concept-formats.ts
|
|
800
|
-
function escapeTurtle(s) {
|
|
801
|
-
return ttlLit(s).slice(1, -1);
|
|
802
|
-
}
|
|
803
|
-
function escapeXml(s) {
|
|
804
|
-
const str = Array.isArray(s) ? s.join(", ") : String(s ?? "");
|
|
805
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
806
|
-
}
|
|
807
|
-
function conceptJsonToTurtle(concept) {
|
|
808
|
-
const uri = concept["@id"] || "";
|
|
809
|
-
const id = concept["gl:identifier"] || "";
|
|
810
|
-
const lines = [
|
|
811
|
-
"@prefix skos: <http://www.w3.org/2004/02/skos/core#> .",
|
|
812
|
-
"@prefix dcterms: <http://purl.org/dc/terms/> .",
|
|
813
|
-
""
|
|
814
|
-
];
|
|
815
|
-
const props = [" a skos:Concept"];
|
|
816
|
-
props.push(` skos:notation "${escapeTurtle(id)}"`);
|
|
817
|
-
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
818
|
-
if (lc["gl:designation"]) {
|
|
819
|
-
for (const d of lc["gl:designation"]) {
|
|
820
|
-
const term = d["gl:term"];
|
|
821
|
-
if (!term) continue;
|
|
822
|
-
const pred = d["gl:normativeStatus"] === "preferred" ? "skos:prefLabel" : "skos:altLabel";
|
|
823
|
-
props.push(` ${pred} "${escapeTurtle(term)}"@${lang}`);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
if (lc["gl:definition"]) {
|
|
827
|
-
for (const d of lc["gl:definition"]) {
|
|
828
|
-
if (d["gl:content"]) props.push(` skos:definition "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (lc["gl:notes"]) {
|
|
832
|
-
for (const d of lc["gl:notes"]) {
|
|
833
|
-
if (d["gl:content"]) props.push(` skos:scopeNote "${escapeTurtle(d["gl:content"])}"@${lang}`);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
lines.push(`<${uri}>`);
|
|
838
|
-
lines.push(props.join(" ;\n"));
|
|
839
|
-
lines.push(" .");
|
|
840
|
-
return lines.join("\n");
|
|
841
|
-
}
|
|
842
|
-
var SKOS_JSON_LD_CONTEXT = {
|
|
843
|
-
skos: "http://www.w3.org/2004/02/skos/core#",
|
|
844
|
-
dcterms: "http://purl.org/dc/terms/",
|
|
845
|
-
"@language": { "@container": "@language" }
|
|
846
|
-
};
|
|
847
|
-
function buildSkosJsonLdObject(concept) {
|
|
848
|
-
const uri = concept["@id"] || "";
|
|
849
|
-
const id = concept["gl:identifier"] || "";
|
|
850
|
-
const doc = {
|
|
851
|
-
"@id": uri,
|
|
852
|
-
"@type": "skos:Concept",
|
|
853
|
-
"skos:notation": id
|
|
854
|
-
};
|
|
855
|
-
const prefLabels = {};
|
|
856
|
-
const altLabels = {};
|
|
857
|
-
const definitions = {};
|
|
858
|
-
const scopeNotes = {};
|
|
859
|
-
for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
|
|
860
|
-
const descs = lc["gl:designation"] || [];
|
|
861
|
-
const pref = descs.find((d) => d["gl:normativeStatus"] === "preferred" && d["gl:term"]);
|
|
862
|
-
const alt = descs.find((d) => d["gl:normativeStatus"] !== "preferred" && d["gl:term"]);
|
|
863
|
-
if (pref) prefLabels[lang] = pref["gl:term"];
|
|
864
|
-
if (alt) altLabels[lang] = alt["gl:term"];
|
|
865
|
-
const def = (lc["gl:definition"] || [])[0];
|
|
866
|
-
if (def?.["gl:content"]) definitions[lang] = def["gl:content"];
|
|
867
|
-
const note = (lc["gl:notes"] || [])[0];
|
|
868
|
-
if (note?.["gl:content"]) scopeNotes[lang] = note["gl:content"];
|
|
869
|
-
}
|
|
870
|
-
if (Object.keys(prefLabels).length) doc["skos:prefLabel"] = prefLabels;
|
|
871
|
-
if (Object.keys(altLabels).length) doc["skos:altLabel"] = altLabels;
|
|
872
|
-
if (Object.keys(definitions).length) doc["skos:definition"] = definitions;
|
|
873
|
-
if (Object.keys(scopeNotes).length) doc["skos:scopeNote"] = scopeNotes;
|
|
874
|
-
return doc;
|
|
875
|
-
}
|
|
876
|
-
function conceptJsonToSkosJsonLd(concept) {
|
|
877
|
-
return JSON.stringify({
|
|
878
|
-
"@context": SKOS_JSON_LD_CONTEXT,
|
|
879
|
-
...buildSkosJsonLdObject(concept)
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
function conceptsToSkosJsonLdGraph(concepts) {
|
|
883
|
-
return JSON.stringify(
|
|
884
|
-
{
|
|
885
|
-
"@context": SKOS_JSON_LD_CONTEXT,
|
|
886
|
-
"@graph": concepts.map(buildSkosJsonLdObject)
|
|
887
|
-
},
|
|
888
|
-
null,
|
|
889
|
-
2
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
var CSV_COLUMNS = [
|
|
893
|
-
"termid",
|
|
894
|
-
"uri",
|
|
895
|
-
"status",
|
|
896
|
-
"section",
|
|
897
|
-
"language",
|
|
898
|
-
"term",
|
|
899
|
-
"alt_terms",
|
|
900
|
-
"definition",
|
|
901
|
-
"notes",
|
|
902
|
-
"examples",
|
|
903
|
-
"sources",
|
|
904
|
-
"source_links"
|
|
905
|
-
];
|
|
906
|
-
function csvEscape(value) {
|
|
907
|
-
if (/[",\r\n]/.test(value)) {
|
|
908
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
909
|
-
}
|
|
910
|
-
return value;
|
|
911
|
-
}
|
|
912
|
-
function joinLines(items) {
|
|
913
|
-
return items.filter(Boolean).join("\n");
|
|
914
|
-
}
|
|
915
|
-
function localizedLanguages(localized, languageOrder) {
|
|
916
|
-
const present = Object.keys(localized);
|
|
917
|
-
if (!languageOrder?.length) return present;
|
|
918
|
-
const ordered = languageOrder.filter((lang) => present.includes(lang));
|
|
919
|
-
return ordered.concat(present.filter((lang) => !ordered.includes(lang)));
|
|
920
|
-
}
|
|
921
|
-
function sourceRefString(src) {
|
|
922
|
-
const ref = src?.["gl:origin"]?.["gl:ref"];
|
|
923
|
-
if (!ref) return "";
|
|
924
|
-
return [ref["gl:source"], ref["gl:id"]].filter(Boolean).join(": ");
|
|
925
|
-
}
|
|
926
|
-
function sourceLinkString(src) {
|
|
927
|
-
return src?.["gl:origin"]?.["gl:link"] || "";
|
|
928
|
-
}
|
|
929
|
-
function conceptsToCsv(concepts, opts = {}) {
|
|
930
|
-
const rows = [CSV_COLUMNS.join(",")];
|
|
931
|
-
for (const concept of concepts) {
|
|
932
|
-
const localized = concept["gl:localizedConcept"] || {};
|
|
933
|
-
const domains = (concept["gl:domain"] || []).map((d) => d["gl:conceptId"]).filter(Boolean).join("; ");
|
|
934
|
-
const managedSources = concept["gl:source"] || [];
|
|
935
|
-
const shared = {
|
|
936
|
-
termid: concept["gl:identifier"] || "",
|
|
937
|
-
uri: concept["@id"] || "",
|
|
938
|
-
status: concept["gl:status"] || "",
|
|
939
|
-
section: domains
|
|
940
|
-
};
|
|
941
|
-
const languages = localizedLanguages(localized, opts.languageOrder);
|
|
942
|
-
if (languages.length === 0) {
|
|
943
|
-
rows.push([shared.termid, shared.uri, shared.status, shared.section, "", "", "", "", "", "", "", ""].map(csvEscape).join(","));
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
for (const lang of languages) {
|
|
947
|
-
const lc = localized[lang] || {};
|
|
948
|
-
const designations = (lc["gl:designation"] || []).filter((d) => d["gl:term"]);
|
|
949
|
-
const preferred = designations.find((d) => d["gl:normativeStatus"] === "preferred") || designations[0];
|
|
950
|
-
const altTerms = designations.filter((d) => d !== preferred).map((d) => d["gl:term"]);
|
|
951
|
-
const localizedSources = lc["gl:source"]?.length ? lc["gl:source"] : managedSources;
|
|
952
|
-
rows.push([
|
|
953
|
-
shared.termid,
|
|
954
|
-
shared.uri,
|
|
955
|
-
shared.status,
|
|
956
|
-
shared.section,
|
|
957
|
-
lang,
|
|
958
|
-
preferred?.["gl:term"] || "",
|
|
959
|
-
altTerms.join("; "),
|
|
960
|
-
joinLines((lc["gl:definition"] || []).map((d) => d["gl:content"])),
|
|
961
|
-
joinLines((lc["gl:notes"] || []).map((d) => d["gl:content"])),
|
|
962
|
-
joinLines((lc["gl:examples"] || []).map((d) => d["gl:content"])),
|
|
963
|
-
localizedSources.map(sourceRefString).filter(Boolean).join("; "),
|
|
964
|
-
localizedSources.map(sourceLinkString).filter(Boolean).join("; ")
|
|
965
|
-
].map(csvEscape).join(","));
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
return "\uFEFF" + rows.join("\r\n") + "\r\n";
|
|
969
|
-
}
|
|
970
|
-
function conceptJsonToTbx(concept) {
|
|
971
|
-
const id = concept["gl:identifier"] || "";
|
|
972
|
-
const uri = concept["@id"] || "";
|
|
973
|
-
const localized = concept["gl:localizedConcept"] || {};
|
|
974
|
-
const langSections = [];
|
|
975
|
-
for (const [lang, lc] of Object.entries(localized)) {
|
|
976
|
-
const descs = lc["gl:designation"] || [];
|
|
977
|
-
const definitions = (lc["gl:definition"] || []).filter((d) => d["gl:content"]);
|
|
978
|
-
const notes = (lc["gl:notes"] || []).filter((d) => d["gl:content"]);
|
|
979
|
-
const examples = (lc["gl:examples"] || []).filter((d) => d["gl:content"]);
|
|
980
|
-
const sources = lc["gl:source"] || [];
|
|
981
|
-
const entryStatus = lc["gl:entryStatus"] || "";
|
|
982
|
-
if (!descs.length && !definitions.length) continue;
|
|
983
|
-
const termEntries = [];
|
|
984
|
-
for (const d of descs) {
|
|
985
|
-
const term = d["gl:term"];
|
|
986
|
-
if (!term) continue;
|
|
987
|
-
const status = d["gl:normativeStatus"] || "";
|
|
988
|
-
const type = d["@type"] || "";
|
|
989
|
-
let gramGrp = "";
|
|
990
|
-
if (d["gl:grammarInfo"] && d["gl:grammarInfo"].length > 0) {
|
|
991
|
-
const gi = d["gl:grammarInfo"][0];
|
|
992
|
-
if (gi["gl:gender"]) gramGrp = `
|
|
993
|
-
<grammaticalGender>${escapeXml(gi["gl:gender"])}</grammaticalGender>`;
|
|
994
|
-
if (gi["gl:number"]) gramGrp += `
|
|
995
|
-
<grammaticalNumber>${escapeXml(gi["gl:number"])}</grammaticalNumber>`;
|
|
996
|
-
for (const pos of ["noun", "verb", "adj", "adverb", "preposition", "participle"]) {
|
|
997
|
-
if (gi[`gl:${pos}`]) gramGrp += `
|
|
998
|
-
<partOfSpeech>${pos}</partOfSpeech>`;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
let posBlock = "";
|
|
1002
|
-
if (type.includes("Abbreviation")) posBlock = "\n <partOfSpeech>abbreviation</partOfSpeech>";
|
|
1003
|
-
if (type.includes("Symbol")) posBlock = "\n <partOfSpeech>symbol</partOfSpeech>";
|
|
1004
|
-
termEntries.push(` <termEntry>
|
|
1005
|
-
<langSet xml:lang="${lang}">
|
|
1006
|
-
<tig>
|
|
1007
|
-
<term>${escapeXml(term)}</term>${gramGrp}${posBlock}
|
|
1008
|
-
</tig>
|
|
1009
|
-
</langSet>
|
|
1010
|
-
</termEntry>`);
|
|
1011
|
-
}
|
|
1012
|
-
let defBlock = "";
|
|
1013
|
-
if (definitions.length) {
|
|
1014
|
-
const defParts = definitions.map((d) => ` <p>${escapeXml(d["gl:content"])}</p>`).join("\n");
|
|
1015
|
-
defBlock = `
|
|
1016
|
-
<descrip type="definition">
|
|
1017
|
-
${defParts}
|
|
1018
|
-
</descrip>`;
|
|
1019
|
-
}
|
|
1020
|
-
let noteBlock = "";
|
|
1021
|
-
for (let i = 0; i < notes.length; i++) {
|
|
1022
|
-
noteBlock += `
|
|
1023
|
-
<note type="note">${escapeXml(notes[i]["gl:content"])}</note>`;
|
|
1024
|
-
}
|
|
1025
|
-
for (let i = 0; i < examples.length; i++) {
|
|
1026
|
-
noteBlock += `
|
|
1027
|
-
<note type="example">${escapeXml(examples[i]["gl:content"])}</note>`;
|
|
1028
|
-
}
|
|
1029
|
-
let sourceBlock = "";
|
|
1030
|
-
for (const src of sources) {
|
|
1031
|
-
const origin = src["gl:origin"] || {};
|
|
1032
|
-
const parts = [];
|
|
1033
|
-
const ref = origin["gl:ref"];
|
|
1034
|
-
if (ref) {
|
|
1035
|
-
const refParts = [];
|
|
1036
|
-
if (ref["gl:source"]) refParts.push(ref["gl:source"]);
|
|
1037
|
-
if (ref["gl:id"]) refParts.push(ref["gl:id"]);
|
|
1038
|
-
parts.push(refParts.join(" ") || "");
|
|
1039
|
-
}
|
|
1040
|
-
if (origin["gl:locality"]) {
|
|
1041
|
-
const loc = origin["gl:locality"];
|
|
1042
|
-
if (loc["gl:referenceFrom"]) parts.push(loc["gl:localityType"] ? `${loc["gl:localityType"]} ${loc["gl:referenceFrom"]}` : loc["gl:referenceFrom"]);
|
|
1043
|
-
}
|
|
1044
|
-
if (parts.filter(Boolean).length) {
|
|
1045
|
-
sourceBlock += `
|
|
1046
|
-
<ref>${escapeXml(parts.filter(Boolean).join(", "))}</ref>`;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
let statusBlock = "";
|
|
1050
|
-
if (entryStatus) {
|
|
1051
|
-
statusBlock += `
|
|
1052
|
-
<descrip type="entryStatus">${escapeXml(entryStatus)}</descrip>`;
|
|
1053
|
-
}
|
|
1054
|
-
const termEntriesBlock = termEntries.length ? "\n" + termEntries.join("\n") : "";
|
|
1055
|
-
langSections.push({ lang, termEntries, blocks: [defBlock, noteBlock, sourceBlock, statusBlock].filter((b) => b).join("") });
|
|
1056
|
-
}
|
|
1057
|
-
if (!langSections.length) return "";
|
|
1058
|
-
const bodyEntries = langSections.map((ls) => {
|
|
1059
|
-
return ` <languageSection xml:lang="${ls.lang}">${ls.blocks}
|
|
1060
|
-
</languageSection>`;
|
|
1061
|
-
}).join("\n");
|
|
1062
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1063
|
-
<tbx style="dca" type="TBX-Basic" xml:lang="en" xmlns="urn:iso:std:iso:30042:ed-2">
|
|
1064
|
-
<tbxHeader>
|
|
1065
|
-
<fileDesc>
|
|
1066
|
-
<source>${escapeXml(uri)}</source>
|
|
1067
|
-
</fileDesc>
|
|
1068
|
-
</tbxHeader>
|
|
1069
|
-
<text>
|
|
1070
|
-
<body>
|
|
1071
|
-
<conceptEntry id="${escapeXml(id)}">
|
|
1072
|
-
${bodyEntries}
|
|
1073
|
-
</conceptEntry>
|
|
1074
|
-
</body>
|
|
1075
|
-
</text>
|
|
1076
|
-
</tbx>
|
|
1077
|
-
`;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
797
|
// scripts/generate-data.ts
|
|
1081
798
|
var __dirname3 = path3.dirname(new URL(import.meta.url).pathname);
|
|
1082
799
|
var ROOT = process.cwd();
|
|
@@ -1512,7 +1229,7 @@ async function processDataset(dir, register, opts) {
|
|
|
1512
1229
|
for (const f of fs2.readdirSync(conceptsDir)) fs2.unlinkSync(path3.join(conceptsDir, f));
|
|
1513
1230
|
}
|
|
1514
1231
|
const concepts = [];
|
|
1515
|
-
const
|
|
1232
|
+
const conceptInstances = [];
|
|
1516
1233
|
const langTermCounts = {};
|
|
1517
1234
|
const langDefCounts = {};
|
|
1518
1235
|
const availableFormats = ["ttl", "jsonld", "yaml", "tbx", "csv"];
|
|
@@ -1617,14 +1334,28 @@ async function processDataset(dir, register, opts) {
|
|
|
1617
1334
|
const termid = String(conceptYaml.termid);
|
|
1618
1335
|
const jsonld = yamlToJsonLd(conceptYaml, register, dsRefMaps);
|
|
1619
1336
|
writeJson(path3.join(conceptsDir, `${termid}.json`), jsonld);
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1337
|
+
let conceptInstance = null;
|
|
1338
|
+
try {
|
|
1339
|
+
conceptInstance = parseConceptYaml(fs2.readFileSync(path3.join(dir, file), "utf8"), termid);
|
|
1340
|
+
} catch (e) {
|
|
1341
|
+
console.warn(` ${termid}: canonical parse failed (${e.message}); skipping glossarist exports`);
|
|
1342
|
+
}
|
|
1343
|
+
if (conceptInstance) {
|
|
1344
|
+
conceptInstances.push(conceptInstance);
|
|
1345
|
+
fs2.writeFileSync(
|
|
1346
|
+
path3.join(conceptsDir, `${termid}.ttl`),
|
|
1347
|
+
conceptToTurtle(conceptInstance, { uriBase: refMaps.uriBase, registerId: register })
|
|
1348
|
+
);
|
|
1349
|
+
const conceptJsonLd = await emitOutputSet([conceptInstance], {
|
|
1350
|
+
registerId: register,
|
|
1351
|
+
uriBase: refMaps.uriBase,
|
|
1352
|
+
formats: ["jsonld"]
|
|
1353
|
+
});
|
|
1354
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.jsonld`), conceptJsonLd.jsonld);
|
|
1355
|
+
const tbxContent = conceptsToTbx([conceptInstance], { registerId: register });
|
|
1356
|
+
if (tbxContent) {
|
|
1357
|
+
fs2.writeFileSync(path3.join(conceptsDir, `${termid}.tbx`), tbxContent);
|
|
1358
|
+
}
|
|
1628
1359
|
}
|
|
1629
1360
|
fs2.copyFileSync(path3.join(dir, file), path3.join(conceptsDir, `${termid}.yaml`));
|
|
1630
1361
|
concepts.push({
|
|
@@ -1640,8 +1371,13 @@ async function processDataset(dir, register, opts) {
|
|
|
1640
1371
|
console.warn(` Skipping ${file}: ${e.message}`);
|
|
1641
1372
|
}
|
|
1642
1373
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1374
|
+
const aggregate = await emitOutputSet(conceptInstances, {
|
|
1375
|
+
registerId: register,
|
|
1376
|
+
uriBase: refMaps.uriBase,
|
|
1377
|
+
languageOrder: opts.languageOrder || opts.languages
|
|
1378
|
+
});
|
|
1379
|
+
fs2.writeFileSync(path3.join(DATA, register, `${register}.csv`), aggregate.csv);
|
|
1380
|
+
fs2.writeFileSync(path3.join(DATA, register, `${register}.jsonld`), aggregate.jsonld);
|
|
1645
1381
|
const CHUNK_SIZE = 500;
|
|
1646
1382
|
const chunks = [];
|
|
1647
1383
|
for (let i = 0; i < concepts.length; i += CHUNK_SIZE) {
|
|
@@ -1684,7 +1420,7 @@ async function processDataset(dir, register, opts) {
|
|
|
1684
1420
|
fs2.writeFileSync(
|
|
1685
1421
|
path3.join(DATA, register, "graph-nodes.json"),
|
|
1686
1422
|
JSON.stringify({
|
|
1687
|
-
uriPrefix:
|
|
1423
|
+
uriPrefix: buildConceptUriPrefix(refMaps.uriBase, register),
|
|
1688
1424
|
registerId: register,
|
|
1689
1425
|
nodes: graphNodeEntries
|
|
1690
1426
|
})
|