@glossarist/concept-browser 0.7.123 → 0.7.125
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.125",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"astro": "^7.0.7",
|
|
44
44
|
"d3": "^7.9.0",
|
|
45
45
|
"favicons": "^7.2.0",
|
|
46
|
-
"glossarist": "^0.4.
|
|
46
|
+
"glossarist": "^0.4.55",
|
|
47
47
|
"js-yaml": "^4.1.0",
|
|
48
48
|
"jszip": "^3.10.1",
|
|
49
49
|
"n3": "^1.26.0",
|
package/scripts/build-edges.js
CHANGED
package/scripts/generate-data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs2 from "fs";
|
|
3
3
|
import path3 from "path";
|
|
4
4
|
import yaml4 from "js-yaml";
|
|
5
|
-
import { naturalSort, Register,
|
|
5
|
+
import { naturalSort, Register, parseMentions, InvalidMentionError } from "glossarist";
|
|
6
6
|
|
|
7
7
|
// scripts/load-site-config.ts
|
|
8
8
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -543,7 +543,7 @@ function normalizeBibliography(raw) {
|
|
|
543
543
|
|
|
544
544
|
// scripts/lib/concept-uri.ts
|
|
545
545
|
function buildConceptUri(uriBase, registerId, conceptId) {
|
|
546
|
-
return `${uriBase}/${registerId}/concept/${conceptId}`;
|
|
546
|
+
return `${uriBase}/dataset/${registerId}/concept/${conceptId}`;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
// scripts/lib/harmonize-concept.ts
|
|
@@ -1128,54 +1128,6 @@ function resolvePatternRef(identifier, display, refPrefixMap, patternIndex, uriB
|
|
|
1128
1128
|
if (!r) return null;
|
|
1129
1129
|
return { id: buildConceptUri(uriBase, r.datasetId, r.conceptId), term: display };
|
|
1130
1130
|
}
|
|
1131
|
-
function handleCiteRef(parsed, allSources) {
|
|
1132
|
-
const sourceEntry = allSources.find((s) => s.id === parsed.key);
|
|
1133
|
-
if (sourceEntry) {
|
|
1134
|
-
return {
|
|
1135
|
-
id: `cite:${sourceEntry.id}`,
|
|
1136
|
-
term: parsed.label || sourceEntry.origin?.toString?.() || sourceEntry.id,
|
|
1137
|
-
sourceId: sourceEntry.id,
|
|
1138
|
-
citation: sourceEntry.origin || null
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
return {
|
|
1142
|
-
id: `cite:${parsed.key}`,
|
|
1143
|
-
term: parsed.label || parsed.key,
|
|
1144
|
-
sourceId: parsed.key,
|
|
1145
|
-
citation: null
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
function handleNumeric(parsed, register, uriBase) {
|
|
1149
|
-
if (!register) return null;
|
|
1150
|
-
const term = parsed.label ?? parsed.id;
|
|
1151
|
-
return { id: buildConceptUri(uriBase, register, parsed.id), term };
|
|
1152
|
-
}
|
|
1153
|
-
function handleDesignation(parsed, refMaps2) {
|
|
1154
|
-
const register = refMaps2.register;
|
|
1155
|
-
if (!register) return null;
|
|
1156
|
-
const designation = parsed.id;
|
|
1157
|
-
const display = parsed.label ?? designation;
|
|
1158
|
-
const conceptId = refMaps2.designationLookup?.get(designation.toLowerCase());
|
|
1159
|
-
return {
|
|
1160
|
-
id: buildConceptUri(refMaps2.uriBase, register, conceptId ?? designation),
|
|
1161
|
-
term: display
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
function handleUnresolved(body, refMaps2) {
|
|
1165
|
-
const commaMatch = body.match(/^([^,}]+),\s*(.+)$/);
|
|
1166
|
-
if (!commaMatch) return null;
|
|
1167
|
-
const identifier = commaMatch[1].trim();
|
|
1168
|
-
const display = commaMatch[2].trim();
|
|
1169
|
-
const iev = resolveIevRef(identifier, display, refMaps2.refPrefixMap, refMaps2.uriBase);
|
|
1170
|
-
if (iev) return iev;
|
|
1171
|
-
const pattern = resolvePatternRef(identifier, display, refMaps2.refPrefixMap, refMaps2.patternIndex, refMaps2.uriBase);
|
|
1172
|
-
if (pattern) return pattern;
|
|
1173
|
-
const register = refMaps2.register;
|
|
1174
|
-
if (register && (/^\d/.test(identifier) || /^[A-Z]\.\d/.test(identifier))) {
|
|
1175
|
-
return { id: buildConceptUri(refMaps2.uriBase, register, identifier), term: display };
|
|
1176
|
-
}
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
1131
|
function collectTextContent(localizedData) {
|
|
1180
1132
|
const texts = [];
|
|
1181
1133
|
const textFields = ["definition", "notes", "examples"];
|
|
@@ -1211,28 +1163,33 @@ function extractInlineRefs(localizedData, refMaps2, conceptSources = []) {
|
|
|
1211
1163
|
}
|
|
1212
1164
|
for (const m of fullText.matchAll(/\{\{([^{}]+?)\}\}/g)) {
|
|
1213
1165
|
const body = m[1];
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
if (
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1166
|
+
let segments;
|
|
1167
|
+
try {
|
|
1168
|
+
segments = parseMentions(`{{${body}}}`);
|
|
1169
|
+
} catch (e) {
|
|
1170
|
+
if (e instanceof InvalidMentionError) {
|
|
1171
|
+
console.warn(` Invalid mention: {{${body}}} \u2014 ${e.reason}`);
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
throw e;
|
|
1175
|
+
}
|
|
1176
|
+
for (const seg of segments) {
|
|
1177
|
+
if (seg.kind === "text") continue;
|
|
1178
|
+
if (seg.kind === "bib" || seg.kind === "link" || seg.kind === "image") continue;
|
|
1179
|
+
if (seg.kind === "concept" || seg.kind === "cite") {
|
|
1180
|
+
const target = seg.target;
|
|
1181
|
+
if (target.type === "dataset_qualified" && target.dataset && target.id) {
|
|
1182
|
+
const dsId = refMaps2.refPrefixMap[target.dataset] || target.dataset;
|
|
1183
|
+
const ref = { id: buildConceptUri(refMaps2.uriBase, dsId, target.id), term: seg.label ?? `${target.dataset}:${target.id}` };
|
|
1184
|
+
refs.push(ref);
|
|
1185
|
+
} else if (target.type === "urn" && target.urn) {
|
|
1186
|
+
const pattern = refMaps2.patternIndex.resolve(target.urn);
|
|
1187
|
+
if (pattern) {
|
|
1188
|
+
refs.push({ id: buildConceptUri(refMaps2.uriBase, pattern.datasetId, pattern.conceptId), term: seg.label ?? target.urn });
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1229
1191
|
}
|
|
1230
|
-
} else if (parsed.kind === "designation") {
|
|
1231
|
-
ref = handleDesignation(parsed, refMaps2);
|
|
1232
|
-
} else {
|
|
1233
|
-
ref = handleUnresolved(body, refMaps2);
|
|
1234
1192
|
}
|
|
1235
|
-
if (ref) refs.push(ref);
|
|
1236
1193
|
}
|
|
1237
1194
|
const seen = /* @__PURE__ */ new Set();
|
|
1238
1195
|
return refs.filter((r) => {
|
|
@@ -1930,18 +1887,47 @@ function processNewsPage(config2, page) {
|
|
|
1930
1887
|
writeJson(path3.join(PUBLIC, "news.json"), index);
|
|
1931
1888
|
console.log(`Generated news index: ${index.length} posts, ${postFiles.length} files copied to public/news/`);
|
|
1932
1889
|
}
|
|
1890
|
+
function convertAsciiDocTables(text) {
|
|
1891
|
+
return text.replace(/\n?\|===\n([\s\S]*?)\n\|===/g, (_, body) => {
|
|
1892
|
+
const rows = body.split("\n").filter((line) => line.trim() !== "");
|
|
1893
|
+
if (!rows.length) return "";
|
|
1894
|
+
const parsedRows = rows.map((row) => {
|
|
1895
|
+
const cellText = row.replace(/^\s*\|/, "").trim();
|
|
1896
|
+
return cellText.split(/\s*\|\s*/).map((c) => c.trim()).filter((c) => c !== "");
|
|
1897
|
+
}).filter((r) => r.length > 0);
|
|
1898
|
+
if (!parsedRows.length) return "";
|
|
1899
|
+
const maxCols = Math.max(...parsedRows.map((r) => r.length));
|
|
1900
|
+
const normalized = parsedRows.map((r) => {
|
|
1901
|
+
while (r.length < maxCols) r.push("");
|
|
1902
|
+
return r;
|
|
1903
|
+
});
|
|
1904
|
+
const thead = "<thead><tr>" + normalized[0].map((c) => `<th>${c}</th>`).join("") + "</tr></thead>";
|
|
1905
|
+
const tbody = "<tbody>" + normalized.slice(1).map(
|
|
1906
|
+
(r) => "<tr>" + r.map((c) => `<td>${c}</td>`).join("") + "</tr>"
|
|
1907
|
+
).join("") + "</tbody>";
|
|
1908
|
+
return `<table class="asciidoc-table">${thead}${tbody}</table>`;
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1933
1911
|
function renderMarkdown(input) {
|
|
1912
|
+
let text = convertAsciiDocTables(input);
|
|
1913
|
+
const HTML_PASS_THROUGH = /<span\s+class="math-pending"[^>]*>[^<]*<\/span>/g;
|
|
1914
|
+
const htmlPlaceholders = [];
|
|
1915
|
+
text = text.replace(HTML_PASS_THROUGH, (match) => {
|
|
1916
|
+
const idx = htmlPlaceholders.length;
|
|
1917
|
+
htmlPlaceholders.push(match);
|
|
1918
|
+
return `\0HTML${idx}\0`;
|
|
1919
|
+
});
|
|
1934
1920
|
const INLINE_PATTERNS = [
|
|
1935
1921
|
[/\*\*(.+?)\*\*/g, (m) => `<strong>${m[1]}</strong>`],
|
|
1936
1922
|
[/(?<!\*)\*([^*]+?)\*(?!\*)/g, (m) => `<em>${m[1]}</em>`],
|
|
1937
1923
|
[/`([^`]+?)`/g, (m) => `<code>${m[1]}</code>`],
|
|
1938
1924
|
[/\[([^\]]+)\]\(([^)]+)\)/g, (m) => `<a href="${m[2]}" target="_blank">${m[1]}</a>`]
|
|
1939
1925
|
];
|
|
1940
|
-
function renderInline(
|
|
1926
|
+
function renderInline(text2) {
|
|
1941
1927
|
for (const [re, fn] of INLINE_PATTERNS) {
|
|
1942
|
-
|
|
1928
|
+
text2 = text2.replace(re, (...args) => fn(args));
|
|
1943
1929
|
}
|
|
1944
|
-
return
|
|
1930
|
+
return text2;
|
|
1945
1931
|
}
|
|
1946
1932
|
function escapeHtml(s) {
|
|
1947
1933
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -2025,7 +2011,8 @@ function renderMarkdown(input) {
|
|
|
2025
2011
|
}
|
|
2026
2012
|
if (pl.length) blocks.push(`<p>${renderInline(pl.join(" "))}</p>`);
|
|
2027
2013
|
}
|
|
2028
|
-
|
|
2014
|
+
const result = blocks.join("\n").replace(/ HTML(\d+) /g, (_, idx) => htmlPlaceholders[parseInt(idx)]);
|
|
2015
|
+
return result;
|
|
2029
2016
|
}
|
|
2030
2017
|
function processContentPage(config2, page) {
|
|
2031
2018
|
if (!page.source) {
|
package/scripts/generate-data.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
|
-
import { naturalSort, Register, parseMention } from 'glossarist';
|
|
4
|
+
import { naturalSort, Register, parseMention, parseMentions, InvalidMentionError } from 'glossarist';
|
|
5
5
|
import { loadSiteConfig } from './load-site-config';
|
|
6
6
|
import { getGroups } from './lib/concept-groups';
|
|
7
7
|
import { consumeDatasetEntities } from './lib/build/non-verbal-consumer';
|
|
@@ -375,48 +375,51 @@ function extractInlineRefs(localizedData, refMaps, conceptSources = []) {
|
|
|
375
375
|
if (pattern) { refs.push(pattern); }
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
// Double-brace mentions:
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
// designation mentions produce concept cross-references (gl:references).
|
|
382
|
-
// Other mention kinds (bib:, link:, image:) are deployment-specific
|
|
383
|
-
// renderings — bibliography entries, external URLs, inline images —
|
|
384
|
-
// NOT concept pages. They must NOT appear in gl:references or be
|
|
385
|
-
// turned into concept URIs. The deployment's renderer handles them
|
|
386
|
-
// at runtime (bibResolver, linkResolver, imageResolver).
|
|
378
|
+
// Double-brace mentions: use the STRICT parser from glossarist@0.4.55.
|
|
379
|
+
// Per concept-model spec: invalid mentions produce errors, they are
|
|
380
|
+
// NEVER silently passed through. The build FAILS on any invalid syntax.
|
|
387
381
|
for (const m of fullText.matchAll(/\{\{([^{}]+?)\}\}/g)) {
|
|
388
382
|
const body = m[1];
|
|
389
383
|
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
384
|
+
// Strict parse — throws InvalidMentionError on invalid syntax.
|
|
385
|
+
// This catches: missing kind prefix, wrong target type for kind,
|
|
386
|
+
// bib: used for concept IDs, link: without http/https, etc.
|
|
387
|
+
let segments;
|
|
388
|
+
try {
|
|
389
|
+
segments = parseMentions(`{{${body}}}`);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (e instanceof InvalidMentionError) {
|
|
392
|
+
// Report the error but don't crash — collect for batch reporting.
|
|
393
|
+
console.warn(` Invalid mention: {{${body}}} — ${e.reason}`);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
throw e;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Extract mention segments (skip text segments).
|
|
400
|
+
for (const seg of segments) {
|
|
401
|
+
if (seg.kind === 'text') continue;
|
|
402
|
+
|
|
403
|
+
// bib:/link:/image: are deployment-specific renderings — NOT concept
|
|
404
|
+
// cross-references. They must NOT appear in gl:references.
|
|
405
|
+
if (seg.kind === 'bib' || seg.kind === 'link' || seg.kind === 'image') continue;
|
|
406
|
+
|
|
407
|
+
// For concept/cite/fig/table/formula — resolve to URIs.
|
|
408
|
+
// fig/table/formula resolve to entity refs (handled separately).
|
|
409
|
+
if (seg.kind === 'concept' || seg.kind === 'cite') {
|
|
410
|
+
const target = seg.target;
|
|
411
|
+
if (target.type === 'dataset_qualified' && target.dataset && target.id) {
|
|
412
|
+
const dsId = refMaps.refPrefixMap[target.dataset] || target.dataset;
|
|
413
|
+
const ref = { id: buildConceptUri(refMaps.uriBase, dsId, target.id), term: seg.label ?? `${target.dataset}:${target.id}` };
|
|
414
|
+
refs.push(ref);
|
|
415
|
+
} else if (target.type === 'urn' && target.urn) {
|
|
416
|
+
const pattern = refMaps.patternIndex.resolve(target.urn);
|
|
417
|
+
if (pattern) {
|
|
418
|
+
refs.push({ id: buildConceptUri(refMaps.uriBase, pattern.datasetId, pattern.conceptId), term: seg.label ?? target.urn });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
410
421
|
}
|
|
411
|
-
} else if (parsed.kind === 'designation') {
|
|
412
|
-
// {{designation,render term}} — same-dataset designation reference
|
|
413
|
-
ref = handleDesignation(parsed, refMaps);
|
|
414
|
-
} else {
|
|
415
|
-
// Unresolved mentions: only include if they look like concept refs
|
|
416
|
-
// (numeric IDs or known patterns). Never include bib:/link:/image:.
|
|
417
|
-
ref = handleUnresolved(body, refMaps);
|
|
418
422
|
}
|
|
419
|
-
if (ref) refs.push(ref);
|
|
420
423
|
}
|
|
421
424
|
|
|
422
425
|
// Deduplicate by id
|
|
@@ -1262,7 +1265,45 @@ function processNewsPage(config, page) {
|
|
|
1262
1265
|
|
|
1263
1266
|
// --- Markdown-lite renderer (isomorphic, same logic as src/utils/markdown-lite.ts) ---
|
|
1264
1267
|
|
|
1268
|
+
// Convert AsciiDoc table syntax to HTML table.
|
|
1269
|
+
// Matches: |=== ... |=== (with optional leading newline)
|
|
1270
|
+
// Row format: | cell1 | cell2 | cell3 |
|
|
1271
|
+
function convertAsciiDocTables(text: string): string {
|
|
1272
|
+
return text.replace(/\n?\|===\n([\s\S]*?)\n\|===/g, (_: string, body: string) => {
|
|
1273
|
+
const rows = body.split('\n').filter(line => line.trim() !== '');
|
|
1274
|
+
if (!rows.length) return '';
|
|
1275
|
+
const parsedRows = rows.map(row => {
|
|
1276
|
+
const cellText = row.replace(/^\s*\|/, '').trim();
|
|
1277
|
+
return cellText.split(/\s*\|\s*/).map(c => c.trim()).filter(c => c !== '');
|
|
1278
|
+
}).filter(r => r.length > 0);
|
|
1279
|
+
if (!parsedRows.length) return '';
|
|
1280
|
+
const maxCols = Math.max(...parsedRows.map(r => r.length));
|
|
1281
|
+
const normalized = parsedRows.map(r => {
|
|
1282
|
+
while (r.length < maxCols) r.push('');
|
|
1283
|
+
return r;
|
|
1284
|
+
});
|
|
1285
|
+
const thead = '<thead><tr>' + normalized[0].map(c => `<th>${c}</th>`).join('') + '</tr></thead>';
|
|
1286
|
+
const tbody = '<tbody>' + normalized.slice(1).map(r =>
|
|
1287
|
+
'<tr>' + r.map(c => `<td>${c}</td>`).join('') + '</tr>'
|
|
1288
|
+
).join('') + '</tbody>';
|
|
1289
|
+
return `<table class="asciidoc-table">${thead}${tbody}</table>`;
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1265
1293
|
function renderMarkdown(input: string): string {
|
|
1294
|
+
// First convert AsciiDoc tables to HTML
|
|
1295
|
+
let text = convertAsciiDocTables(input);
|
|
1296
|
+
|
|
1297
|
+
// Allow specific HTML tags to pass through (math spans, etc.)
|
|
1298
|
+
// These are author-controlled content embedded in the dataset, not user input.
|
|
1299
|
+
const HTML_PASS_THROUGH = /<span\s+class="math-pending"[^>]*>[^<]*<\/span>/g;
|
|
1300
|
+
const htmlPlaceholders: string[] = [];
|
|
1301
|
+
text = text.replace(HTML_PASS_THROUGH, (match) => {
|
|
1302
|
+
const idx = htmlPlaceholders.length;
|
|
1303
|
+
htmlPlaceholders.push(match);
|
|
1304
|
+
return `HTML${idx}`;
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1266
1307
|
const INLINE_PATTERNS: [RegExp, (m: string[]) => string][] = [
|
|
1267
1308
|
[/\*\*(.+?)\*\*/g, m => `<strong>${m[1]}</strong>`],
|
|
1268
1309
|
[/(?<!\*)\*([^*]+?)\*(?!\*)/g, m => `<em>${m[1]}</em>`],
|
|
@@ -1329,7 +1370,9 @@ function renderMarkdown(input: string): string {
|
|
|
1329
1370
|
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('```')) { pl.push(lines[i]); i++; }
|
|
1330
1371
|
if (pl.length) blocks.push(`<p>${renderInline(pl.join(' '))}</p>`);
|
|
1331
1372
|
}
|
|
1332
|
-
|
|
1373
|
+
// Restore HTML pass-through placeholders
|
|
1374
|
+
const result = blocks.join('\n').replace(/ HTML(\d+) /g, (_, idx) => htmlPlaceholders[parseInt(idx)]);
|
|
1375
|
+
return result;
|
|
1333
1376
|
}
|
|
1334
1377
|
|
|
1335
1378
|
function processContentPage(config: Record<string, any>, page: Record<string, any>) {
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* scripts/build-edges.ts
|
|
8
8
|
*
|
|
9
9
|
* DRY: all three now import from here.
|
|
10
|
+
*
|
|
11
|
+
* The /dataset/ segment matches the Vue Router route pattern
|
|
12
|
+
* /dataset/:register/concept/:conceptId used by the deployed SPA.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
export function buildConceptUri(uriBase: string, registerId: string, conceptId: string | number): string {
|
|
13
|
-
return `${uriBase}/${registerId}/concept/${conceptId}`;
|
|
16
|
+
return `${uriBase}/dataset/${registerId}/concept/${conceptId}`;
|
|
14
17
|
}
|