@glossarist/concept-browser 0.7.60 → 0.7.62
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 +1 -1
- package/package.json +2 -2
- package/scripts/generate-data.mjs +10 -10
- package/scripts/lib/agents-turtle.mjs +22 -60
- package/scripts/lib/bibliography-turtle.mjs +22 -47
- package/scripts/lib/build-activity-turtle.mjs +23 -89
- package/scripts/lib/dataset-turtle.mjs +30 -76
- package/scripts/lib/version-turtle.mjs +24 -51
- package/src/__tests__/scripts/agents-turtle.test.ts +11 -11
- package/src/__tests__/scripts/bibliography-turtle.test.ts +11 -11
- package/src/__tests__/scripts/build-activity-turtle.test.ts +10 -10
- package/src/__tests__/scripts/build-integration.test.ts +10 -10
- package/src/__tests__/scripts/dataset-turtle.test.ts +10 -10
- package/src/__tests__/scripts/version-turtle.test.ts +9 -9
- package/src/config/group-types.ts +1 -1
- package/src/views/DatasetView.vue +13 -1
- package/src/views/HomeView.vue +10 -1
package/README.md
CHANGED
|
@@ -432,7 +432,7 @@ Sites currently powered by `@glossarist/concept-browser`. When cutting a release
|
|
|
432
432
|
| [`geolexica/osgeo.geolexica.org`](https://github.com/geolexica/osgeo.geolexica.org) | https://osgeo.geolexica.org |
|
|
433
433
|
| [`oimlsmart/vocab`](https://github.com/oimlsmart/vocab) | https://www.oimlsmart.org/vocab/ (VIML)¹ |
|
|
434
434
|
| [`metanorma/oiml-terms`](https://github.com/metanorma/oiml-terms) | https://metanorma.github.io/oiml-terms/ |
|
|
435
|
-
| [`
|
|
435
|
+
| [`glossarist/iala-vocab`](https://github.com/glossarist/iala-vocab) | https://glossarist.github.io/iala-vocab/ |
|
|
436
436
|
| [`metanorma/iso-10303-2-vocab`](https://github.com/metanorma/iso-10303-2-vocab) | https://metanorma.github.io/iso-10303-2-vocab/ |
|
|
437
437
|
|
|
438
438
|
To add a new deployment here, open a PR against this README.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.62",
|
|
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": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"autoprefixer": "^10.4.21",
|
|
32
32
|
"d3": "^7.9.0",
|
|
33
33
|
"favicons": "^7.2.0",
|
|
34
|
-
"glossarist": "^0.4.
|
|
34
|
+
"glossarist": "^0.4.10",
|
|
35
35
|
"js-yaml": "^4.1.0",
|
|
36
36
|
"jszip": "^3.10.1",
|
|
37
37
|
"pinia": "^2.3.1",
|
|
@@ -91,7 +91,7 @@ function writeJson(filePath, data) {
|
|
|
91
91
|
fs.writeFileSync(filePath, JSON.stringify(data));
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
94
|
+
async function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
95
95
|
const uriBase = refMaps?.uriBase ?? 'https://glossarist.org';
|
|
96
96
|
const datasetIri = `${uriBase}/${register}/`;
|
|
97
97
|
const topConceptUris = concepts
|
|
@@ -122,7 +122,7 @@ function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
|
122
122
|
},
|
|
123
123
|
];
|
|
124
124
|
|
|
125
|
-
const ttl = buildDatasetTurtle({
|
|
125
|
+
const ttl = await buildDatasetTurtle({
|
|
126
126
|
datasetIri,
|
|
127
127
|
registerId: register,
|
|
128
128
|
title: manifest.title ?? register,
|
|
@@ -139,7 +139,7 @@ function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
|
139
139
|
fs.writeFileSync(path.join(DATA, register, `${register}.ttl`), ttl);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
142
|
+
async function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
143
143
|
const runId = process.env.GITHUB_RUN_ID
|
|
144
144
|
? `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT ?? '1'}`
|
|
145
145
|
: `local-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
@@ -150,7 +150,7 @@ function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
|
150
150
|
const pkgVersion = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
151
151
|
const agentIri = process.env.CI_BOT_AGENT_IRI ?? null;
|
|
152
152
|
|
|
153
|
-
const ttl = buildActivityTurtle({
|
|
153
|
+
const ttl = await buildActivityTurtle({
|
|
154
154
|
runId,
|
|
155
155
|
startedAt,
|
|
156
156
|
endedAt,
|
|
@@ -1092,7 +1092,7 @@ async function processDataset(dir, register, opts) {
|
|
|
1092
1092
|
writeJson(path.join(DATA, register, 'manifest.json'), manifest);
|
|
1093
1093
|
|
|
1094
1094
|
// Dataset-level RDF (WS J2/J5): dcat:Dataset + skos:ConceptScheme + skos:Collection per section.
|
|
1095
|
-
writeDatasetRdf(register, manifest, concepts, refMaps, opts);
|
|
1095
|
+
await writeDatasetRdf(register, manifest, concepts, refMaps, opts);
|
|
1096
1096
|
|
|
1097
1097
|
// Copy bibliography.yaml → bibliography.json
|
|
1098
1098
|
const bibPath = path.join(sourceRoot, 'bibliography.yaml');
|
|
@@ -1610,14 +1610,14 @@ for (const [id, count] of Object.entries(counts)) {
|
|
|
1610
1610
|
console.log(` ${id}: ${count} concepts`);
|
|
1611
1611
|
}
|
|
1612
1612
|
|
|
1613
|
-
writeBuildActivity(total, registry.map(r => r.id));
|
|
1613
|
+
await writeBuildActivity(total, registry.map(r => r.id));
|
|
1614
1614
|
|
|
1615
1615
|
fs.writeFileSync(path.join(DATA, '_vocab.ttl'), await buildVocabularyTurtle());
|
|
1616
1616
|
console.log('Emitted vocabulary graph: data/_vocab.ttl');
|
|
1617
1617
|
|
|
1618
1618
|
const contributors = config.contributors ?? [];
|
|
1619
1619
|
if (contributors.length > 0) {
|
|
1620
|
-
fs.writeFileSync(path.join(DATA, 'agents.ttl'), buildAgentsTurtle(contributors));
|
|
1620
|
+
fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors));
|
|
1621
1621
|
console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
|
|
1622
1622
|
}
|
|
1623
1623
|
|
|
@@ -1626,7 +1626,7 @@ for (const ds of registry) {
|
|
|
1626
1626
|
const bibPath = path.join(DATA, ds.id, 'bibliography.json');
|
|
1627
1627
|
if (fs.existsSync(bibPath)) {
|
|
1628
1628
|
const bibJson = JSON.parse(fs.readFileSync(bibPath, 'utf8'));
|
|
1629
|
-
const bibTtl = buildBibliographyTurtle(ds.id, bibJson);
|
|
1629
|
+
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson);
|
|
1630
1630
|
fs.writeFileSync(path.join(DATA, ds.id, 'bib.ttl'), bibTtl);
|
|
1631
1631
|
}
|
|
1632
1632
|
}
|
|
@@ -1644,14 +1644,14 @@ const datasetVersions = registry.map(ds => ({
|
|
|
1644
1644
|
}));
|
|
1645
1645
|
|
|
1646
1646
|
if (datasetVersions.length > 0) {
|
|
1647
|
-
const versionTtl = datasetVersions.map(v =>
|
|
1647
|
+
const versionTtl = (await Promise.all(datasetVersions.map(v =>
|
|
1648
1648
|
buildVersionHistoryTurtle({
|
|
1649
1649
|
registerId: v.registerId,
|
|
1650
1650
|
datasetIri: v.datasetIri,
|
|
1651
1651
|
versions: v.versions,
|
|
1652
1652
|
associatedAgentIri: process.env.CI_BOT_AGENT_IRI ?? null,
|
|
1653
1653
|
}),
|
|
1654
|
-
).join('\n');
|
|
1654
|
+
))).join('\n');
|
|
1655
1655
|
fs.writeFileSync(path.join(DATA, 'versions.ttl'), versionTtl);
|
|
1656
1656
|
console.log(`Emitted versions graph: data/versions.ttl (${datasetVersions.length} datasets)`);
|
|
1657
1657
|
}
|
|
@@ -1,64 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.replace(/[̀-ͯ]/g, '')
|
|
6
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
7
|
-
.replace(/^-+|-+$/g, '');
|
|
8
|
-
}
|
|
1
|
+
// Agents turtle builder — emits foaf:Person / prov:Organization from
|
|
2
|
+
// contributor declarations.
|
|
3
|
+
//
|
|
4
|
+
// Delegates to glossarist-js's agents emitter + writeTurtle.
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
import {
|
|
7
|
+
agentsFromContributors,
|
|
8
|
+
agentsToQuads,
|
|
9
|
+
collectQuads,
|
|
10
|
+
writeTurtle,
|
|
11
|
+
} from 'glossarist/rdf';
|
|
15
12
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
function writerPrefixes() {
|
|
14
|
+
return {
|
|
15
|
+
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
16
|
+
prov: 'http://www.w3.org/ns/prov#',
|
|
17
|
+
dcterms: 'http://purl.org/dc/terms/',
|
|
18
|
+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
19
|
+
};
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
export function buildAgentsTurtle(contributors, agentBase = 'https://glossarist.org/agent') {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
lines.push('@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .');
|
|
30
|
-
lines.push('');
|
|
31
|
-
|
|
32
|
-
const orgSlugs = new Set();
|
|
33
|
-
for (const c of contributors ?? []) {
|
|
34
|
-
const slug = slugify(c.name);
|
|
35
|
-
const iri = `${agentBase}/${slug}`;
|
|
36
|
-
const personLines = [
|
|
37
|
-
`<${iri}> a foaf:Person, prov:Person, prov:Agent ;`,
|
|
38
|
-
` foaf:name ${ttlLit(c.name)} ;`,
|
|
39
|
-
];
|
|
40
|
-
if (c.email) personLines.push(` foaf:mbox <mailto:${c.email}> ;`);
|
|
41
|
-
if (c.url) personLines.push(` rdfs:seeAlso <${c.url}> ;`);
|
|
42
|
-
if (c.role) personLines.push(` dcterms:description ${ttlLit(c.role)} ;`);
|
|
43
|
-
personLines[personLines.length - 1] = personLines[personLines.length - 1].replace(/ ;$/, ' .');
|
|
44
|
-
lines.push(...personLines);
|
|
45
|
-
|
|
46
|
-
if (c.organization) {
|
|
47
|
-
const orgSlug = slugify(c.organization);
|
|
48
|
-
const orgIri = `https://glossarist.org/org/${orgSlug}`;
|
|
49
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/\.$/, ` ;\n prov:actedOnBehalfOf <${orgIri}> .`);
|
|
50
|
-
|
|
51
|
-
if (!orgSlugs.has(orgSlug)) {
|
|
52
|
-
orgSlugs.add(orgSlug);
|
|
53
|
-
const orgLines = [
|
|
54
|
-
``,
|
|
55
|
-
`<${orgIri}> a foaf:Organization, prov:Organization, prov:Agent ;`,
|
|
56
|
-
` foaf:name ${ttlLit(c.organization)} .`,
|
|
57
|
-
];
|
|
58
|
-
lines.push(...orgLines);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
lines.push('');
|
|
62
|
-
}
|
|
63
|
-
return lines.join('\n');
|
|
64
|
-
}
|
|
22
|
+
export async function buildAgentsTurtle(contributors, agentBase = 'https://glossarist.org/agent') {
|
|
23
|
+
const agents = agentsFromContributors(contributors ?? [], agentBase);
|
|
24
|
+
const quads = collectQuads(agentsToQuads(agents));
|
|
25
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
26
|
+
}
|
|
@@ -1,54 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
// Bibliography turtle builder — emits dcterms:BibliographicResource
|
|
2
|
+
// per bibliography entry.
|
|
3
|
+
//
|
|
4
|
+
// Delegates to glossarist-js's bibliography emitter + writeTurtle.
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
import {
|
|
7
|
+
bibliographyToQuads,
|
|
8
|
+
normalizeBibliographyData,
|
|
9
|
+
collectQuads,
|
|
10
|
+
writeTurtle,
|
|
11
|
+
} from 'glossarist/rdf';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_BASE_URI = 'https://glossarist.org';
|
|
14
|
+
|
|
15
|
+
function writerPrefixes() {
|
|
4
16
|
return {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
link: e.link,
|
|
9
|
-
type: e.type,
|
|
17
|
+
dcterms: 'http://purl.org/dc/terms/',
|
|
18
|
+
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
19
|
+
gloss: 'https://www.glossarist.org/ontologies/',
|
|
10
20
|
};
|
|
11
21
|
}
|
|
12
22
|
|
|
13
|
-
export
|
|
14
|
-
if (!raw || typeof raw !== 'object') return [];
|
|
15
|
-
if (Array.isArray(raw.bibliography)) {
|
|
16
|
-
return raw.bibliography.map(e => entryFromV3(e));
|
|
17
|
-
}
|
|
18
|
-
const entries = [];
|
|
19
|
-
for (const [id, value] of Object.entries(raw)) {
|
|
20
|
-
if (!value || typeof value !== 'object') continue;
|
|
21
|
-
entries.push(entryFromV3(value, id));
|
|
22
|
-
}
|
|
23
|
-
return entries;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function buildBibliographyTurtle(register, bibliographyJson, baseUri = 'https://glossarist.org') {
|
|
27
|
-
const lines = [
|
|
28
|
-
'@prefix dcterms: <http://purl.org/dc/terms/> .',
|
|
29
|
-
'@prefix foaf: <http://xmlns.com/foaf/0.1/> .',
|
|
30
|
-
'@prefix gloss: <https://www.glossarist.org/ontologies/> .',
|
|
31
|
-
'',
|
|
32
|
-
];
|
|
23
|
+
export { normalizeBibliographyData };
|
|
33
24
|
|
|
34
|
-
|
|
25
|
+
export async function buildBibliographyTurtle(register, bibliographyJson, baseUri = DEFAULT_BASE_URI) {
|
|
35
26
|
const entries = normalizeBibliographyData(bibliographyJson);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/* Percent-encode the bib id when embedding it in an IRI — many
|
|
40
|
-
bibliography ids contain spaces or other reserved chars
|
|
41
|
-
(e.g. "ISO/IEC 17000:2020") which are forbidden in raw IRI form. */
|
|
42
|
-
const bibIri = `${datasetIri}bib/${encodeURIComponent(entry.id)}`;
|
|
43
|
-
lines.push(`${ttlIri(bibIri)} a dcterms:BibliographicResource ;`);
|
|
44
|
-
lines.push(` dcterms:identifier ${ttlLit(entry.id)} ;`);
|
|
45
|
-
lines.push(` dcterms:bibliographicCitation ${ttlLit(entry.reference)} ;`);
|
|
46
|
-
if (entry.title) lines.push(` dcterms:title ${ttlLit(entry.title)} ;`);
|
|
47
|
-
if (entry.link) lines.push(` foaf:page ${ttlIri(entry.link)} ;`);
|
|
48
|
-
if (entry.type) lines.push(` dcterms:type ${ttlIri(`${baseUri}/${register}/bibtype/${entry.type}`)} ;`);
|
|
49
|
-
lines.push(` dcterms:isPartOf ${ttlIri(datasetIri)} .`);
|
|
50
|
-
lines.push('');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return lines.join('\n');
|
|
54
|
-
}
|
|
27
|
+
const quads = collectQuads(bibliographyToQuads({ registerId: register, entries, baseUri }));
|
|
28
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
29
|
+
}
|
|
@@ -1,92 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// Build activity turtle builder — emits prov:Activity per CI build run.
|
|
2
|
+
//
|
|
3
|
+
// Delegates to glossarist-js's build-activity emitter + writeTurtle.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildActivityToQuads,
|
|
7
|
+
collectQuads,
|
|
8
|
+
writeTurtle,
|
|
9
|
+
} from 'glossarist/rdf';
|
|
10
|
+
|
|
11
|
+
function writerPrefixes() {
|
|
12
|
+
return {
|
|
13
|
+
prov: 'http://www.w3.org/ns/prov#',
|
|
14
|
+
dcterms: 'http://purl.org/dc/terms/',
|
|
15
|
+
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
16
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
17
|
+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
18
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
19
|
+
gloss: 'https://www.glossarist.org/ontologies/',
|
|
20
|
+
};
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
export function buildActivityTurtle(input) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
lines.push(`@prefix ${prefix}: <${iri}> .`);
|
|
21
|
-
}
|
|
22
|
-
lines.push('');
|
|
23
|
-
|
|
24
|
-
const activityIri = `https://glossarist.org/activity/build/${input.runId}`;
|
|
25
|
-
const usedEntities = [];
|
|
26
|
-
|
|
27
|
-
if (input.gitSha) {
|
|
28
|
-
usedEntities.push({
|
|
29
|
-
iri: `https://glossarist.org/commit/${input.gitSha}`,
|
|
30
|
-
types: ['prov:Entity'],
|
|
31
|
-
label: input.gitSha,
|
|
32
|
-
extras: input.gitBranch ? [`dcterms:description ${ttlLit(`branch: ${input.gitBranch}`)}`] : [],
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
usedEntities.push({
|
|
37
|
-
iri: `https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`,
|
|
38
|
-
types: ['prov:Entity', 'prov:SoftwareAgent'],
|
|
39
|
-
label: `${input.toolId} ${input.toolVersion}`,
|
|
40
|
-
extras: [
|
|
41
|
-
`dcterms:identifier ${ttlLit(input.toolVersion)}`,
|
|
42
|
-
`prov:version ${ttlLit(input.toolVersion)}`,
|
|
43
|
-
],
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
for (const register of input.datasetRegisters ?? []) {
|
|
47
|
-
usedEntities.push({
|
|
48
|
-
iri: `https://glossarist.org/${register}/`,
|
|
49
|
-
types: ['prov:Entity'],
|
|
50
|
-
label: register,
|
|
51
|
-
extras: [],
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const activityLines = [
|
|
56
|
-
`<${activityIri}> a prov:Activity ;`,
|
|
57
|
-
` rdfs:label ${ttlLit(`build ${input.runId}`)} ;`,
|
|
58
|
-
` prov:generatedAtTime "${input.endedAt}"^^xsd:dateTime ;`,
|
|
59
|
-
];
|
|
60
|
-
for (const ent of usedEntities) {
|
|
61
|
-
activityLines.push(` prov:used <${ent.iri}> ;`);
|
|
62
|
-
}
|
|
63
|
-
activityLines.push(` gloss:conceptCount "${input.conceptCount}"^^xsd:integer ;`);
|
|
64
|
-
if (input.associatedAgentIri) {
|
|
65
|
-
activityLines.push(` prov:wasAssociatedWith <${input.associatedAgentIri}> ;`);
|
|
66
|
-
}
|
|
67
|
-
activityLines[activityLines.length - 1] = activityLines[activityLines.length - 1].replace(/ ;$/, ' .');
|
|
68
|
-
lines.push(...activityLines);
|
|
69
|
-
lines.push('');
|
|
70
|
-
|
|
71
|
-
for (const ent of usedEntities) {
|
|
72
|
-
const entLines = [
|
|
73
|
-
`<${ent.iri}> a ${ent.types.join(', ')} ;`,
|
|
74
|
-
` rdfs:label ${ttlLit(ent.label)}${ent.extras.length > 0 ? ' ;' : ' .'}`,
|
|
75
|
-
];
|
|
76
|
-
for (let i = 0; i < ent.extras.length; i++) {
|
|
77
|
-
const last = i === ent.extras.length - 1;
|
|
78
|
-
entLines.push(` ${ent.extras[i]}${last ? ' .' : ' ;'}`);
|
|
79
|
-
}
|
|
80
|
-
lines.push(...entLines);
|
|
81
|
-
lines.push('');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (input.associatedAgentIri) {
|
|
85
|
-
const label = input.associatedAgentIri.split('/').pop() ?? 'agent';
|
|
86
|
-
lines.push(`<${input.associatedAgentIri}> a prov:Agent, foaf:Person ;`);
|
|
87
|
-
lines.push(` rdfs:label ${ttlLit(label)} .`);
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return lines.join('\n') + '\n';
|
|
23
|
+
export async function buildActivityTurtle(input) {
|
|
24
|
+
const quads = collectQuads(buildActivityToQuads(input));
|
|
25
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
92
26
|
}
|
|
@@ -1,79 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// Dataset turtle builder — emits dcat:Dataset + skos:ConceptScheme per
|
|
2
|
+
// register.
|
|
3
|
+
//
|
|
4
|
+
// Delegates to glossarist-js's dataset emitter + writeTurtle so the
|
|
5
|
+
// canonical prefix list, CURIE handling, and serialization stay in one
|
|
6
|
+
// place.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
datasetToQuads,
|
|
10
|
+
collectQuads,
|
|
11
|
+
writeTurtle,
|
|
12
|
+
PREFIXES,
|
|
13
|
+
} from 'glossarist/rdf';
|
|
14
|
+
|
|
15
|
+
function writerPrefixes() {
|
|
16
|
+
const out = {};
|
|
17
|
+
// Only the prefixes this emitter actually uses — keeps the output
|
|
18
|
+
// header focused.
|
|
19
|
+
for (const k of ['dcat', 'skos', 'dcterms', 'prov', 'rdf', 'rdfs', 'xsd']) {
|
|
20
|
+
if (PREFIXES[k]) out[k] = PREFIXES[k];
|
|
21
|
+
}
|
|
22
|
+
// dcat/prov are absent from glossarist-js's PREFIXES (which is
|
|
23
|
+
// generated from the JSON-LD context). Add them locally — they're
|
|
24
|
+
// in concept-model/prefixes.ttl.
|
|
25
|
+
out.dcat ??= 'http://www.w3.org/ns/dcat#';
|
|
26
|
+
out.prov ??= 'http://www.w3.org/ns/prov#';
|
|
27
|
+
return out;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
export function buildDatasetTurtle(input) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
lines.push(`@prefix ${prefix}: <${iri}> .`);
|
|
21
|
-
}
|
|
22
|
-
lines.push('');
|
|
23
|
-
|
|
24
|
-
lines.push(`<${input.datasetIri}> a dcat:Dataset, skos:ConceptScheme ;`);
|
|
25
|
-
lines.push(` dcterms:title ${ttlLit(input.title)} ;`);
|
|
26
|
-
if (input.description) {
|
|
27
|
-
lines.push(` dcterms:description ${ttlLit(input.description)} ;`);
|
|
28
|
-
}
|
|
29
|
-
lines.push(` dcterms:modified "${input.modified}"^^xsd:date ;`);
|
|
30
|
-
lines.push(` dcterms:identifier ${ttlLit(input.registerId)} ;`);
|
|
31
|
-
|
|
32
|
-
for (const lang of input.languages ?? []) {
|
|
33
|
-
lines.push(` dcterms:language <http://id.loc.gov/vocabulary/iso639-1/${lang}> ;`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const dist of input.distributions ?? []) {
|
|
37
|
-
lines.push(' dcat:distribution [');
|
|
38
|
-
lines.push(` a dcat:Distribution ;`);
|
|
39
|
-
lines.push(` dcterms:title ${ttlLit(dist.title)} ;`);
|
|
40
|
-
lines.push(` dcat:mediaType ${ttlLit(dist.mediaType)} ;`);
|
|
41
|
-
lines.push(` dcat:downloadURL <${dist.downloadUrl}> ;`);
|
|
42
|
-
if (dist.byteSize != null) {
|
|
43
|
-
lines.push(` dcat:byteSize "${dist.byteSize}"^^xsd:integer ;`);
|
|
44
|
-
}
|
|
45
|
-
lines.push(' ] ;');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const concept of input.topConceptUris ?? []) {
|
|
49
|
-
lines.push(` skos:hasTopConcept <${concept}> ;`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (input.sourceRepoUrl) {
|
|
53
|
-
lines.push(` prov:wasDerivedFrom <${input.sourceRepoUrl}> ;`);
|
|
54
|
-
}
|
|
55
|
-
if (input.publisherIri) {
|
|
56
|
-
lines.push(` dcterms:publisher <${input.publisherIri}> ;`);
|
|
57
|
-
}
|
|
58
|
-
if (input.contactIri) {
|
|
59
|
-
lines.push(` dcat:contactPoint <${input.contactIri}> ;`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
63
|
-
|
|
64
|
-
for (const section of input.sections ?? []) {
|
|
65
|
-
lines.push('');
|
|
66
|
-
lines.push(`<${section.collectionIri}> a skos:Collection ;`);
|
|
67
|
-
lines.push(` dcterms:title ${ttlLit(section.title)} ;`);
|
|
68
|
-
for (const member of section.memberUris) {
|
|
69
|
-
lines.push(` skos:member <${member}> ;`);
|
|
70
|
-
}
|
|
71
|
-
if (section.memberUris.length === 0) {
|
|
72
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
73
|
-
} else {
|
|
74
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return lines.join('\n') + '\n';
|
|
30
|
+
export async function buildDatasetTurtle(input) {
|
|
31
|
+
const quads = collectQuads(datasetToQuads(input));
|
|
32
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
79
33
|
}
|
|
@@ -1,56 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
['rdfs', 'http://www.w3.org/2000/01/rdf-schema#'],
|
|
5
|
-
['xsd', 'http://www.w3.org/2001/XMLSchema#'],
|
|
6
|
-
];
|
|
1
|
+
// Version turtle builder — emits prov:Entity version chain per dataset.
|
|
2
|
+
//
|
|
3
|
+
// Delegates to glossarist-js's version emitter + writeTurtle.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import {
|
|
6
|
+
versionToQuads,
|
|
7
|
+
versionHistoryToQuads,
|
|
8
|
+
collectQuads,
|
|
9
|
+
writeTurtle,
|
|
10
|
+
} from 'glossarist/rdf';
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
function writerPrefixes() {
|
|
13
|
+
return {
|
|
14
|
+
prov: 'http://www.w3.org/ns/prov#',
|
|
15
|
+
dcterms: 'http://purl.org/dc/terms/',
|
|
16
|
+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
17
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (input.previousVersionIri) {
|
|
25
|
-
lines.push(` prov:wasRevisionOf <${input.previousVersionIri}> ;`);
|
|
26
|
-
}
|
|
27
|
-
lines.push(` prov:generatedAtTime "${input.generatedAt}"^^xsd:dateTime ;`);
|
|
28
|
-
if (input.changeSummary) {
|
|
29
|
-
lines.push(` dcterms:description ${ttlLit(input.changeSummary)} ;`);
|
|
30
|
-
}
|
|
31
|
-
if (input.associatedAgentIri) {
|
|
32
|
-
lines.push(` prov:wasAssociatedWith <${input.associatedAgentIri}> ;`);
|
|
33
|
-
}
|
|
34
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
35
|
-
return lines.join('\n') + '\n';
|
|
21
|
+
export async function buildVersionTurtle(input) {
|
|
22
|
+
const quads = collectQuads(versionToQuads(input));
|
|
23
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
36
24
|
}
|
|
37
25
|
|
|
38
|
-
export function buildVersionHistoryTurtle(input) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const versionIri = `${input.datasetIri}versions/${v.version}`;
|
|
43
|
-
blocks.push(buildVersionTurtle({
|
|
44
|
-
registerId: input.registerId,
|
|
45
|
-
version: v.version,
|
|
46
|
-
versionIri,
|
|
47
|
-
datasetIri: input.datasetIri,
|
|
48
|
-
generatedAt: v.generatedAt,
|
|
49
|
-
previousVersionIri: previousIri,
|
|
50
|
-
changeSummary: v.changeSummary,
|
|
51
|
-
associatedAgentIri: input.associatedAgentIri,
|
|
52
|
-
}));
|
|
53
|
-
previousIri = versionIri;
|
|
54
|
-
}
|
|
55
|
-
return blocks.join('\n');
|
|
56
|
-
}
|
|
26
|
+
export async function buildVersionHistoryTurtle(input) {
|
|
27
|
+
const quads = collectQuads(versionHistoryToQuads(input));
|
|
28
|
+
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
29
|
+
}
|
|
@@ -15,14 +15,14 @@ const DCTERMS = 'http://purl.org/dc/terms/';
|
|
|
15
15
|
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
16
16
|
|
|
17
17
|
describe('buildAgentsTurtle (mjs)', () => {
|
|
18
|
-
it('parses without errors', () => {
|
|
19
|
-
const ttl = buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
18
|
+
it('parses without errors', async () => {
|
|
19
|
+
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
20
20
|
const store = parse(ttl);
|
|
21
21
|
expect(store.size).toBeGreaterThan(0);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it('types each person as foaf:Person, prov:Person, prov:Agent', () => {
|
|
25
|
-
const ttl = buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
24
|
+
it('types each person as foaf:Person, prov:Person, prov:Agent', async () => {
|
|
25
|
+
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
26
26
|
const store = parse(ttl);
|
|
27
27
|
const types = store.getObjects('https://glossarist.org/agent/ada-lovelace', RDF_TYPE, null).map(q => q.value);
|
|
28
28
|
expect(types).toContain(`${FOAF}Person`);
|
|
@@ -30,8 +30,8 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
30
30
|
expect(types).toContain(`${PROV}Agent`);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('records name, mailto mbox, role, and seeAlso', () => {
|
|
34
|
-
const ttl = buildAgentsTurtle([
|
|
33
|
+
it('records name, mailto mbox, role, and seeAlso', async () => {
|
|
34
|
+
const ttl = await buildAgentsTurtle([
|
|
35
35
|
{ name: 'Ada Lovelace', email: 'ada@example.org', role: 'Editor', url: 'https://example.org/ada' },
|
|
36
36
|
]);
|
|
37
37
|
const store = parse(ttl);
|
|
@@ -42,8 +42,8 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
42
42
|
expect(store.getObjects(iri, 'http://www.w3.org/2000/01/rdf-schema#seeAlso', null).map(q => q.value)).toContain('https://example.org/ada');
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it('creates a single prov:Organization per unique organization', () => {
|
|
46
|
-
const ttl = buildAgentsTurtle([
|
|
45
|
+
it('creates a single prov:Organization per unique organization', async () => {
|
|
46
|
+
const ttl = await buildAgentsTurtle([
|
|
47
47
|
{ name: 'Alice', organization: 'ISO' },
|
|
48
48
|
{ name: 'Bob', organization: 'ISO' },
|
|
49
49
|
]);
|
|
@@ -53,9 +53,9 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
53
53
|
expect(types).toContain(`${PROV}Organization`);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it('emits only prefix declarations for an empty input', () => {
|
|
57
|
-
const ttl = buildAgentsTurtle([]);
|
|
56
|
+
it('emits only prefix declarations for an empty input', async () => {
|
|
57
|
+
const ttl = await buildAgentsTurtle([]);
|
|
58
58
|
expect(ttl).toContain('@prefix foaf:');
|
|
59
59
|
expect(ttl).not.toContain('<https://glossarist.org/agent/');
|
|
60
60
|
});
|
|
61
|
-
});
|
|
61
|
+
});
|
|
@@ -14,21 +14,21 @@ function parse(turtle: string): Store {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
describe('buildBibliographyTurtle (mjs)', () => {
|
|
17
|
-
it('parses without errors', () => {
|
|
18
|
-
const ttl = buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
17
|
+
it('parses without errors', async () => {
|
|
18
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
19
19
|
const store = parse(ttl);
|
|
20
20
|
expect(store.size).toBeGreaterThan(0);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('types each entry as dcterms:BibliographicResource', () => {
|
|
24
|
-
const ttl = buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
23
|
+
it('types each entry as dcterms:BibliographicResource', async () => {
|
|
24
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
25
25
|
const store = parse(ttl);
|
|
26
26
|
const types = store.getObjects('https://glossarist.org/iso-geodetic/bib/iso704', RDF_TYPE, null).map(q => q.value);
|
|
27
27
|
expect(types).toContain(`${DCTERMS}BibliographicResource`);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it('emits identifier, bibliographicCitation, and title', () => {
|
|
31
|
-
const ttl = buildBibliographyTurtle('iso-geodetic', {
|
|
30
|
+
it('emits identifier, bibliographicCitation, and title', async () => {
|
|
31
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
32
32
|
iso704: { reference: 'ISO 704', title: 'Terminology work — Principles and methods' },
|
|
33
33
|
});
|
|
34
34
|
const store = parse(ttl);
|
|
@@ -38,8 +38,8 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
38
38
|
expect(store.getObjects(iri, `${DCTERMS}title`, null).map(q => q.value)).toContain('Terminology work — Principles and methods');
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('emits foaf:page when link is provided', () => {
|
|
42
|
-
const ttl = buildBibliographyTurtle('iso-geodetic', {
|
|
41
|
+
it('emits foaf:page when link is provided', async () => {
|
|
42
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
43
43
|
ref: { reference: 'X', link: 'https://example.org/x' },
|
|
44
44
|
});
|
|
45
45
|
const store = parse(ttl);
|
|
@@ -47,8 +47,8 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
47
47
|
expect(store.getObjects(iri, `${FOAF}page`, null).map(q => q.value)).toContain('https://example.org/x');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('emits one resource per bibliography entry', () => {
|
|
51
|
-
const ttl = buildBibliographyTurtle('iso-geodetic', {
|
|
50
|
+
it('emits one resource per bibliography entry', async () => {
|
|
51
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
52
52
|
a: { reference: 'A' },
|
|
53
53
|
b: { reference: 'B' },
|
|
54
54
|
});
|
|
@@ -56,4 +56,4 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
56
56
|
expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/a', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('A');
|
|
57
57
|
expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/b', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('B');
|
|
58
58
|
});
|
|
59
|
-
});
|
|
59
|
+
});
|
|
@@ -36,39 +36,39 @@ const expand = (v: string): string => {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
describe('buildActivityTurtle (mjs)', () => {
|
|
39
|
-
it('parses without errors', () => {
|
|
40
|
-
const ttl = buildActivityTurtle(makeInput());
|
|
39
|
+
it('parses without errors', async () => {
|
|
40
|
+
const ttl = await buildActivityTurtle(makeInput());
|
|
41
41
|
const store = parse(ttl);
|
|
42
42
|
expect(store.size).toBeGreaterThan(0);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it('types the activity as prov:Activity', () => {
|
|
45
|
+
it('types the activity as prov:Activity', async () => {
|
|
46
46
|
const input = makeInput();
|
|
47
|
-
const store = parse(buildActivityTurtle(input));
|
|
47
|
+
const store = parse(await buildActivityTurtle(input));
|
|
48
48
|
const types = store.getObjects(ACTIVITY_IRI(input.runId), expand('rdf:type'), null).map(q => q.value);
|
|
49
49
|
expect(types).toContain(expand('prov:Activity'));
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('records git commit and tool as prov:used', () => {
|
|
52
|
+
it('records git commit and tool as prov:used', async () => {
|
|
53
53
|
const input = makeInput();
|
|
54
|
-
const store = parse(buildActivityTurtle(input));
|
|
54
|
+
const store = parse(await buildActivityTurtle(input));
|
|
55
55
|
const used = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:used'), null).map(q => q.value);
|
|
56
56
|
expect(used).toContain(`https://glossarist.org/commit/${input.gitSha}`);
|
|
57
57
|
expect(used).toContain(`https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('references every dataset register via prov:used', () => {
|
|
60
|
+
it('references every dataset register via prov:used', async () => {
|
|
61
61
|
const input = makeInput({ datasetRegisters: ['a', 'b', 'c'] });
|
|
62
|
-
const store = parse(buildActivityTurtle(input));
|
|
62
|
+
const store = parse(await buildActivityTurtle(input));
|
|
63
63
|
const used = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:used'), null).map(q => q.value);
|
|
64
64
|
for (const r of input.datasetRegisters) {
|
|
65
65
|
expect(used).toContain(`https://glossarist.org/${r}/`);
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
it('associates the CI agent when provided', () => {
|
|
69
|
+
it('associates the CI agent when provided', async () => {
|
|
70
70
|
const input = makeInput();
|
|
71
|
-
const store = parse(buildActivityTurtle(input));
|
|
71
|
+
const store = parse(await buildActivityTurtle(input));
|
|
72
72
|
const agents = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:wasAssociatedWith'), null).map(q => q.value);
|
|
73
73
|
expect(agents).toContain(input.associatedAgentIri);
|
|
74
74
|
});
|
|
@@ -27,7 +27,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
27
27
|
it('dataset-turtle.mjs produces parseable dcat:Dataset', () => {
|
|
28
28
|
const out = runInline(`
|
|
29
29
|
import { buildDatasetTurtle } from './scripts/lib/dataset-turtle.mjs';
|
|
30
|
-
console.log(buildDatasetTurtle({
|
|
30
|
+
console.log(await buildDatasetTurtle({
|
|
31
31
|
datasetIri: 'https://glossarist.org/test/',
|
|
32
32
|
registerId: 'test', title: 'Test', modified: '2026-06-28',
|
|
33
33
|
languages: ['eng'], distributions: [], topConceptUris: [], sections: [],
|
|
@@ -45,7 +45,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
45
45
|
it('build-activity-turtle.mjs produces parseable prov:Activity', () => {
|
|
46
46
|
const out = runInline(`
|
|
47
47
|
import { buildActivityTurtle } from './scripts/lib/build-activity-turtle.mjs';
|
|
48
|
-
console.log(buildActivityTurtle({
|
|
48
|
+
console.log(await buildActivityTurtle({
|
|
49
49
|
runId: 'test', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z',
|
|
50
50
|
toolId: 'cb', toolVersion: '0.7.52', datasetRegisters: [], conceptCount: 0,
|
|
51
51
|
}));
|
|
@@ -61,7 +61,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
61
61
|
it('agents-turtle.mjs produces parseable foaf:Person', () => {
|
|
62
62
|
const out = runInline(`
|
|
63
63
|
import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
|
|
64
|
-
console.log(buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }]));
|
|
64
|
+
console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }]));
|
|
65
65
|
`);
|
|
66
66
|
const store = parseTurtle(out);
|
|
67
67
|
const persons = [...store].filter(q =>
|
|
@@ -74,7 +74,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
74
74
|
it('bibliography-turtle.mjs produces parseable dcterms:BibliographicResource', () => {
|
|
75
75
|
const out = runInline(`
|
|
76
76
|
import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
|
|
77
|
-
console.log(buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }));
|
|
77
|
+
console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }));
|
|
78
78
|
`);
|
|
79
79
|
const store = parseTurtle(out);
|
|
80
80
|
const bibs = [...store].filter(q =>
|
|
@@ -87,7 +87,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
87
87
|
it('version-turtle.mjs produces parseable prov:Entity chain', () => {
|
|
88
88
|
const out = runInline(`
|
|
89
89
|
import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
|
|
90
|
-
console.log(buildVersionHistoryTurtle({
|
|
90
|
+
console.log(await buildVersionHistoryTurtle({
|
|
91
91
|
registerId: 'test', datasetIri: 'https://glossarist.org/test/',
|
|
92
92
|
versions: [
|
|
93
93
|
{ version: '1.0', generatedAt: '2024-01-01T00:00:00Z' },
|
|
@@ -120,11 +120,11 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
120
120
|
import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
|
|
121
121
|
const parts = [
|
|
122
122
|
await buildVocabularyTurtle(),
|
|
123
|
-
buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
|
|
124
|
-
buildActivityTurtle({ runId: 'r', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', toolId: 'cb', toolVersion: '0', datasetRegisters: [], conceptCount: 0 }),
|
|
125
|
-
buildAgentsTurtle([{ name: 'Ada' }]),
|
|
126
|
-
buildBibliographyTurtle('test', { x: { reference: 'X' } }),
|
|
127
|
-
buildVersionHistoryTurtle({ registerId: 't', datasetIri: 'https://glossarist.org/t/', versions: [{ version: '1', generatedAt: '2026-01-01' }] }),
|
|
123
|
+
await buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
|
|
124
|
+
await buildActivityTurtle({ runId: 'r', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', toolId: 'cb', toolVersion: '0', datasetRegisters: [], conceptCount: 0 }),
|
|
125
|
+
await buildAgentsTurtle([{ name: 'Ada' }]),
|
|
126
|
+
await buildBibliographyTurtle('test', { x: { reference: 'X' } }),
|
|
127
|
+
await buildVersionHistoryTurtle({ registerId: 't', datasetIri: 'https://glossarist.org/t/', versions: [{ version: '1', generatedAt: '2026-01-01' }] }),
|
|
128
128
|
];
|
|
129
129
|
console.log(parts.join('\\n'));
|
|
130
130
|
`);
|
|
@@ -50,23 +50,23 @@ const expand = (v: string): string => {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
describe('buildDatasetTurtle (mjs)', () => {
|
|
53
|
-
it('parses without errors and produces a non-empty graph', () => {
|
|
54
|
-
const ttl = buildDatasetTurtle(makeInput());
|
|
53
|
+
it('parses without errors and produces a non-empty graph', async () => {
|
|
54
|
+
const ttl = await buildDatasetTurtle(makeInput());
|
|
55
55
|
const store = parse(ttl);
|
|
56
56
|
expect(store.size).toBeGreaterThan(0);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it('types the dataset as dcat:Dataset and skos:ConceptScheme', () => {
|
|
59
|
+
it('types the dataset as dcat:Dataset and skos:ConceptScheme', async () => {
|
|
60
60
|
const input = makeInput();
|
|
61
|
-
const store = parse(buildDatasetTurtle(input));
|
|
61
|
+
const store = parse(await buildDatasetTurtle(input));
|
|
62
62
|
const types = store.getObjects(input.datasetIri, expand('rdf:type'), null).map(q => q.value);
|
|
63
63
|
expect(types).toContain(expand('dcat:Dataset'));
|
|
64
64
|
expect(types).toContain(expand('skos:ConceptScheme'));
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
it('emits a dcat:Distribution blank per distribution', () => {
|
|
67
|
+
it('emits a dcat:Distribution blank per distribution', async () => {
|
|
68
68
|
const input = makeInput();
|
|
69
|
-
const store = parse(buildDatasetTurtle(input));
|
|
69
|
+
const store = parse(await buildDatasetTurtle(input));
|
|
70
70
|
const dists = store.getObjects(input.datasetIri, expand('dcat:distribution'), null);
|
|
71
71
|
expect(dists.length).toBe(1);
|
|
72
72
|
const mediaTypes = store.getObjects(dists[0], expand('dcat:mediaType'), null).map(q => q.value);
|
|
@@ -75,9 +75,9 @@ describe('buildDatasetTurtle (mjs)', () => {
|
|
|
75
75
|
expect(byteSizes).toContain('12345');
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it('emits skos:Collection per section with skos:member entries', () => {
|
|
78
|
+
it('emits skos:Collection per section with skos:member entries', async () => {
|
|
79
79
|
const input = makeInput();
|
|
80
|
-
const store = parse(buildDatasetTurtle(input));
|
|
80
|
+
const store = parse(await buildDatasetTurtle(input));
|
|
81
81
|
const section = input.sections[0];
|
|
82
82
|
const types = store.getObjects(section.collectionIri, expand('rdf:type'), null).map(q => q.value);
|
|
83
83
|
expect(types).toContain(expand('skos:Collection'));
|
|
@@ -85,9 +85,9 @@ describe('buildDatasetTurtle (mjs)', () => {
|
|
|
85
85
|
expect(members).toContain(`${BASE}/test/concept/1`);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it('escapes double-quotes and backslashes in titles', () => {
|
|
88
|
+
it('escapes double-quotes and backslashes in titles', async () => {
|
|
89
89
|
const input = makeInput({ title: 'has "quotes" and back\\slash' });
|
|
90
|
-
const store = parse(buildDatasetTurtle(input));
|
|
90
|
+
const store = parse(await buildDatasetTurtle(input));
|
|
91
91
|
const titles = store.getObjects(input.datasetIri, expand('dcterms:title'), null).map(q => q.value);
|
|
92
92
|
expect(titles).toContain('has "quotes" and back\\slash');
|
|
93
93
|
});
|
|
@@ -14,8 +14,8 @@ const DCTERMS = 'http://purl.org/dc/terms/';
|
|
|
14
14
|
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
15
15
|
|
|
16
16
|
describe('buildVersionTurtle (mjs)', () => {
|
|
17
|
-
it('parses without errors', () => {
|
|
18
|
-
const ttl = buildVersionTurtle({
|
|
17
|
+
it('parses without errors', async () => {
|
|
18
|
+
const ttl = await buildVersionTurtle({
|
|
19
19
|
registerId: 'test',
|
|
20
20
|
version: '1.0',
|
|
21
21
|
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
@@ -26,8 +26,8 @@ describe('buildVersionTurtle (mjs)', () => {
|
|
|
26
26
|
expect(store.size).toBeGreaterThan(0);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it('types the version as prov:Entity', () => {
|
|
30
|
-
const ttl = buildVersionTurtle({
|
|
29
|
+
it('types the version as prov:Entity', async () => {
|
|
30
|
+
const ttl = await buildVersionTurtle({
|
|
31
31
|
registerId: 'test',
|
|
32
32
|
version: '1.0',
|
|
33
33
|
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
@@ -39,8 +39,8 @@ describe('buildVersionTurtle (mjs)', () => {
|
|
|
39
39
|
expect(types).toContain(`${PROV}Entity`);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('emits prov:wasRevisionOf when a previous version is provided', () => {
|
|
43
|
-
const ttl = buildVersionTurtle({
|
|
42
|
+
it('emits prov:wasRevisionOf when a previous version is provided', async () => {
|
|
43
|
+
const ttl = await buildVersionTurtle({
|
|
44
44
|
registerId: 'test',
|
|
45
45
|
version: '1.0',
|
|
46
46
|
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
@@ -55,8 +55,8 @@ describe('buildVersionTurtle (mjs)', () => {
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
describe('buildVersionHistoryTurtle (mjs)', () => {
|
|
58
|
-
it('emits a chain with revision links', () => {
|
|
59
|
-
const ttl = buildVersionHistoryTurtle({
|
|
58
|
+
it('emits a chain with revision links', async () => {
|
|
59
|
+
const ttl = await buildVersionHistoryTurtle({
|
|
60
60
|
registerId: 'test',
|
|
61
61
|
datasetIri: 'https://glossarist.org/test/',
|
|
62
62
|
versions: [
|
|
@@ -69,4 +69,4 @@ describe('buildVersionHistoryTurtle (mjs)', () => {
|
|
|
69
69
|
const prev = store.getObjects(iri11, `${PROV}wasRevisionOf`, null).map(q => q.value);
|
|
70
70
|
expect(prev).toContain('https://glossarist.org/test/versions/1.0');
|
|
71
71
|
});
|
|
72
|
-
});
|
|
72
|
+
});
|
|
@@ -33,7 +33,7 @@ export const GROUP_TYPES: Record<DatasetGroupKind, GroupTypeMeta> = {
|
|
|
33
33
|
kind: 'lineage',
|
|
34
34
|
label: 'Edition series',
|
|
35
35
|
description: 'Same vocabulary, different editions over time',
|
|
36
|
-
glyph: '
|
|
36
|
+
glyph: '⟶',
|
|
37
37
|
ordered: true,
|
|
38
38
|
supersession: true,
|
|
39
39
|
sameVocabulary: true,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, ref, watch, onMounted, onUnmounted, onBeforeUnmount } from 'vue';
|
|
3
3
|
import { useRoute, useRouter } from 'vue-router';
|
|
4
4
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
5
|
+
import { useUiStore } from '../stores/ui';
|
|
5
6
|
import { useDsStyle } from '../utils/dataset-style';
|
|
6
7
|
import { useDatasetLoader } from '../composables/use-dataset-loader';
|
|
7
8
|
import { FORMAT_LABELS } from '../config/types';
|
|
@@ -17,6 +18,7 @@ import { formatSectionLabel } from '../utils/section-display';
|
|
|
17
18
|
const props = defineProps<{ registerId: string }>();
|
|
18
19
|
|
|
19
20
|
const store = useVocabularyStore();
|
|
21
|
+
const uiStore = useUiStore();
|
|
20
22
|
const { getStyle } = useDsStyle();
|
|
21
23
|
const { ensureLoaded, loading, localError } = useDatasetLoader(() => props.registerId);
|
|
22
24
|
const { t } = useI18n();
|
|
@@ -30,6 +32,16 @@ const localizedDescription = computed(() => localizedDatasetField(props.register
|
|
|
30
32
|
const adapter = computed(() => store.datasets.get(props.registerId));
|
|
31
33
|
const chunkLoading = ref(false);
|
|
32
34
|
|
|
35
|
+
// Concept-count badge: tinted bg + always-readable text in either mode.
|
|
36
|
+
// Previously used `.light` for bg and `.dark` for text — but `.dark` is
|
|
37
|
+
// just `.light` with alpha, so text was invisible against the bg.
|
|
38
|
+
const conceptCountBadgeStyle = computed(() => {
|
|
39
|
+
const s = getStyle(props.registerId);
|
|
40
|
+
return uiStore.isDark
|
|
41
|
+
? { backgroundColor: s.darkAlpha(0.25), color: s.light }
|
|
42
|
+
: { backgroundColor: s.light, color: '#ffffff' };
|
|
43
|
+
});
|
|
44
|
+
|
|
33
45
|
// Background chunk preloading via requestIdleCallback
|
|
34
46
|
let idlePreloadHandle: ReturnType<typeof requestIdleCallback> | ReturnType<typeof setTimeout> | null = null;
|
|
35
47
|
|
|
@@ -310,7 +322,7 @@ function clearSection() {
|
|
|
310
322
|
<h1 class="font-serif text-3xl text-ink-800 mb-2">{{ localizedTitle }}</h1>
|
|
311
323
|
<p class="text-ink-400 leading-relaxed max-w-2xl">{{ localizedDescription }}</p>
|
|
312
324
|
<div class="flex flex-wrap gap-2 mt-4">
|
|
313
|
-
<span class="badge" :style="
|
|
325
|
+
<span class="badge" :style="conceptCountBadgeStyle">{{ manifest.conceptCount.toLocaleString() }} {{ t('dataset.concepts') }}</span>
|
|
314
326
|
<span class="badge badge-gray">{{ manifest.languages.length }} {{ t('dataset.languages') }}</span>
|
|
315
327
|
<span class="badge badge-green">{{ manifest.owner }}</span>
|
|
316
328
|
<router-link :to="{ name: 'stats', params: { registerId } }" class="badge badge-blue hover:opacity-80 transition-opacity">
|
package/src/views/HomeView.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue';
|
|
3
3
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
4
|
+
import { useUiStore } from '../stores/ui';
|
|
4
5
|
import { useRouter } from 'vue-router';
|
|
5
6
|
import { useDsStyle } from '../utils/dataset-style';
|
|
6
7
|
import { useSiteConfig } from '../config/use-site-config';
|
|
@@ -8,10 +9,18 @@ import { useI18n } from '../i18n';
|
|
|
8
9
|
import HomeSeriesSection from '../components/HomeSeriesSection.vue';
|
|
9
10
|
|
|
10
11
|
const store = useVocabularyStore();
|
|
12
|
+
const uiStore = useUiStore();
|
|
11
13
|
const router = useRouter();
|
|
12
14
|
const { getStyle } = useDsStyle();
|
|
13
15
|
const { config: siteConfig, localizedTitle, localizedSubtitle, localizedDescription, localizedDatasetField } = useSiteConfig();
|
|
14
16
|
const { t } = useI18n();
|
|
17
|
+
|
|
18
|
+
function tagBadgeStyle(dsId: string) {
|
|
19
|
+
const s = getStyle(dsId);
|
|
20
|
+
return uiStore.isDark
|
|
21
|
+
? { backgroundColor: s.darkAlpha(0.25), color: s.light }
|
|
22
|
+
: { backgroundColor: s.light, color: '#ffffff' };
|
|
23
|
+
}
|
|
15
24
|
const exploring = ref(false);
|
|
16
25
|
|
|
17
26
|
async function exploreRandom() {
|
|
@@ -181,7 +190,7 @@ function goToGraph() { router.push({ name: 'graph' }); }
|
|
|
181
190
|
</div>
|
|
182
191
|
|
|
183
192
|
<div class="flex flex-wrap gap-1.5 pl-[22px] mb-3">
|
|
184
|
-
<span v-for="tag in (ds.manifest.tags ?? []).slice(0, 3)" :key="tag" class="badge text-[10px]" :style="
|
|
193
|
+
<span v-for="tag in (ds.manifest.tags ?? []).slice(0, 3)" :key="tag" class="badge text-[10px]" :style="tagBadgeStyle(ds.id)">
|
|
185
194
|
{{ tag }}
|
|
186
195
|
</span>
|
|
187
196
|
</div>
|