@glossarist/concept-browser 0.7.51 → 0.7.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/index.mjs +32 -0
- package/env.d.ts +15 -0
- package/package.json +12 -2
- package/scripts/__tests__/doctor.test.mjs +147 -0
- package/scripts/doctor.mjs +327 -0
- package/scripts/generate-data.mjs +136 -0
- package/scripts/generate-ontology-data.mjs +3 -3
- package/scripts/generate-ontology-schema.mjs +3 -3
- package/scripts/lib/agents-turtle.mjs +64 -0
- package/scripts/lib/bibliography-turtle.mjs +54 -0
- package/scripts/lib/build-activity-turtle.mjs +92 -0
- package/scripts/lib/build-cache.mjs +70 -0
- package/scripts/lib/dataset-turtle.mjs +79 -0
- package/scripts/lib/turtle-escape.mjs +0 -0
- package/scripts/lib/version-turtle.mjs +56 -0
- package/scripts/lib/vocab-turtle.mjs +64 -0
- package/scripts/normalize-yaml.mjs +99 -0
- package/scripts/sync-concept-model.mjs +86 -0
- package/scripts/validate-shacl.mjs +194 -0
- package/src/App.vue +2 -0
- package/src/__fixtures__/concept-shape.ttl +20 -0
- package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
- package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
- package/src/__fixtures__/shacl/good/concept.ttl +8 -0
- package/src/__tests__/__fixtures__/concepts.ts +221 -0
- package/src/__tests__/adapters/concept-identity.test.ts +76 -0
- package/src/__tests__/components/error-boundary.test.ts +109 -0
- package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
- package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
- package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
- package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
- package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
- package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
- package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
- package/src/__tests__/concept-rdf/differential.test.ts +96 -0
- package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
- package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
- package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
- package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
- package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
- package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
- package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
- package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
- package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
- package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
- package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
- package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
- package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
- package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
- package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
- package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
- package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
- package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
- package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
- package/src/__tests__/concept-rdf-view.test.ts +136 -0
- package/src/__tests__/config/group-renderers.test.ts +35 -0
- package/src/__tests__/config/group-types.test.ts +76 -0
- package/src/__tests__/dataset-style.test.ts +12 -7
- package/src/__tests__/errors/errors.test.ts +142 -0
- package/src/__tests__/format-downloads.test.ts +47 -65
- package/src/__tests__/markdown-lite.test.ts +19 -0
- package/src/__tests__/perf/bundle-layout.test.ts +50 -0
- package/src/__tests__/perf/serialization-perf.test.ts +121 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
- package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
- package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
- package/src/__tests__/scripts/build-cache.test.ts +78 -0
- package/src/__tests__/scripts/build-integration.test.ts +134 -0
- package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
- package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
- package/src/__tests__/scripts/stryker-config.test.ts +33 -0
- package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
- package/src/__tests__/scripts/version-turtle.test.ts +72 -0
- package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
- package/src/__tests__/use-format-registry.test.ts +125 -0
- package/src/__tests__/utils/bcp47.test.ts +166 -0
- package/src/__tests__/utils/color-theme.test.ts +143 -0
- package/src/__tests__/utils/url-safety.test.ts +65 -0
- package/src/__tests__/validate-shacl.test.ts +100 -0
- package/src/adapters/DatasetAdapter.ts +11 -5
- package/src/adapters/GraphDataSource.ts +2 -1
- package/src/adapters/UriRouter.ts +2 -1
- package/src/adapters/concept-identity.ts +69 -0
- package/src/adapters/factory.ts +3 -2
- package/src/adapters/model-bridge.ts +2 -1
- package/src/adapters/non-verbal/glossarist-augment.d.ts +7 -0
- package/src/adapters/non-verbal-resolver.ts +2 -1
- package/src/components/AppSidebar.vue +189 -93
- package/src/components/ConceptDetail.vue +8 -0
- package/src/components/ConceptEditionRail.vue +222 -0
- package/src/components/ConceptRdfView.vue +37 -377
- package/src/components/DatasetSeriesCard.vue +270 -0
- package/src/components/ErrorBoundary.vue +95 -0
- package/src/components/FormatDownloads.vue +17 -13
- package/src/components/HomeSeriesSection.vue +277 -0
- package/src/components/RelationSphere.vue +1672 -0
- package/src/components/SidebarSeriesSection.vue +239 -0
- package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
- package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
- package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
- package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
- package/src/components/concept-rdf/agents-emitter.ts +82 -0
- package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
- package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
- package/src/components/concept-rdf/concept-emitter.ts +443 -0
- package/src/components/concept-rdf/dataset-emitter.ts +95 -0
- package/src/components/concept-rdf/group-emitter.ts +69 -0
- package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
- package/src/components/concept-rdf/jsonld-writer.ts +82 -0
- package/src/components/concept-rdf/predicates.ts +261 -0
- package/src/components/concept-rdf/provenance.ts +80 -0
- package/src/components/concept-rdf/rdf-graph.ts +211 -0
- package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
- package/src/components/concept-rdf/sections-builder.ts +62 -0
- package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
- package/src/components/concept-rdf/turtle-writer.ts +116 -0
- package/src/components/concept-rdf/use-rdf-document.ts +72 -0
- package/src/components/concept-rdf/version-emitter.ts +65 -0
- package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
- package/src/components/groups/DatasetGroupRenderer.vue +32 -0
- package/src/components/groups/DefaultGroupSidebar.vue +50 -0
- package/src/components/groups/LineageGroupSidebar.vue +75 -0
- package/src/composables/use-color-theme.ts +82 -0
- package/src/composables/use-format-registry.ts +42 -0
- package/src/composables/useDatasetSeries.ts +258 -0
- package/src/composables/useSphereProjection.ts +125 -0
- package/src/config/group-renderers.ts +27 -0
- package/src/config/group-types.ts +92 -0
- package/src/config/types.ts +81 -2
- package/src/config/use-site-config.ts +2 -1
- package/src/errors.ts +136 -0
- package/src/i18n/locales/eng.yml +24 -0
- package/src/i18n/locales/fra.yml +24 -0
- package/src/stores/vocabulary.ts +3 -1
- package/src/style.css +17 -2
- package/src/types/agents-version-turtle.d.ts +27 -0
- package/src/types/bibliography-turtle.d.ts +12 -0
- package/src/types/build-activity-turtle.d.ts +16 -0
- package/src/types/build-cache.d.ts +20 -0
- package/src/types/dataset-turtle.d.ts +32 -0
- package/src/types/normalize-yaml.d.ts +16 -0
- package/src/types/turtle-escape.d.ts +6 -0
- package/src/types/vocab-turtle.d.ts +13 -0
- package/src/utils/asciidoc-lite.ts +11 -6
- package/src/utils/bcp47.ts +141 -0
- package/src/utils/color-theme-integration.ts +11 -0
- package/src/utils/color-theme.ts +129 -0
- package/src/utils/dataset-style.ts +31 -6
- package/src/utils/locale.ts +6 -14
- package/src/utils/markdown-lite.ts +6 -1
- package/src/utils/relation-sphere-styling.ts +63 -0
- package/src/utils/relationship-categories.ts +30 -0
- package/src/utils/url-safety.ts +30 -0
- package/src/views/ConceptView.vue +187 -9
- package/src/views/DatasetView.vue +6 -0
- package/src/views/HomeView.vue +5 -0
- package/vite.config.ts +7 -0
|
@@ -6,6 +6,12 @@ import { loadSiteConfig } from './load-site-config.mjs';
|
|
|
6
6
|
import { getGroups } from './lib/concept-groups.mjs';
|
|
7
7
|
import { consumeDatasetEntities } from './lib/build/non-verbal-consumer.mjs';
|
|
8
8
|
import { copyImageAssets } from './lib/build/image-assets.mjs';
|
|
9
|
+
import { buildDatasetTurtle } from './lib/dataset-turtle.mjs';
|
|
10
|
+
import { buildActivityTurtle } from './lib/build-activity-turtle.mjs';
|
|
11
|
+
import { buildVocabularyTurtle } from './lib/vocab-turtle.mjs';
|
|
12
|
+
import { buildAgentsTurtle } from './lib/agents-turtle.mjs';
|
|
13
|
+
import { buildVersionHistoryTurtle } from './lib/version-turtle.mjs';
|
|
14
|
+
import { buildBibliographyTurtle } from './lib/bibliography-turtle.mjs';
|
|
9
15
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
10
16
|
const ROOT = process.cwd();
|
|
11
17
|
const PUBLIC = path.join(ROOT, 'public');
|
|
@@ -84,6 +90,83 @@ function writeJson(filePath, data) {
|
|
|
84
90
|
fs.writeFileSync(filePath, JSON.stringify(data));
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
94
|
+
const uriBase = refMaps?.uriBase ?? 'https://glossarist.org';
|
|
95
|
+
const datasetIri = `${uriBase}/${register}/`;
|
|
96
|
+
const topConceptUris = concepts
|
|
97
|
+
.slice(0, 32)
|
|
98
|
+
.map(c => `${uriBase}/${register}/concept/${c.id}`);
|
|
99
|
+
|
|
100
|
+
const sections = (manifest.sections ?? []).map(section => {
|
|
101
|
+
const sectionId = section.id ?? section.slug ?? section.title;
|
|
102
|
+
return {
|
|
103
|
+
collectionIri: `${uriBase}/${register}/section/${sectionId}`,
|
|
104
|
+
title: section.title ?? section.name ?? sectionId,
|
|
105
|
+
memberUris: (section.members ?? []).map(id => `${uriBase}/${register}/concept/${id}`),
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const distributions = [
|
|
110
|
+
{
|
|
111
|
+
id: `${register}-ttl`,
|
|
112
|
+
title: 'Turtle distribution',
|
|
113
|
+
mediaType: 'text/turtle',
|
|
114
|
+
downloadUrl: `${uriBase}/data/${register}/${register}.ttl`,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: `${register}-jsonld`,
|
|
118
|
+
title: 'JSON-LD distribution',
|
|
119
|
+
mediaType: 'application/ld+json',
|
|
120
|
+
downloadUrl: `${uriBase}/data/${register}/${register}.jsonld`,
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const ttl = buildDatasetTurtle({
|
|
125
|
+
datasetIri,
|
|
126
|
+
registerId: register,
|
|
127
|
+
title: manifest.title ?? register,
|
|
128
|
+
description: manifest.description,
|
|
129
|
+
modified: manifest.lastUpdated ?? new Date().toISOString().slice(0, 10),
|
|
130
|
+
languages: opts.languages ?? ['eng'],
|
|
131
|
+
distributions,
|
|
132
|
+
topConceptUris,
|
|
133
|
+
sections,
|
|
134
|
+
sourceRepoUrl: manifest.sourceRepoUrl,
|
|
135
|
+
publisherIri: manifest.publisher,
|
|
136
|
+
contactIri: manifest.contactPoint,
|
|
137
|
+
});
|
|
138
|
+
fs.writeFileSync(path.join(DATA, register, `${register}.ttl`), ttl);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
142
|
+
const runId = process.env.GITHUB_RUN_ID
|
|
143
|
+
? `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT ?? '1'}`
|
|
144
|
+
: `local-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
145
|
+
const startedAt = process.env.BUILD_STARTED_AT ?? new Date(Date.now() - 60_000).toISOString();
|
|
146
|
+
const endedAt = new Date().toISOString();
|
|
147
|
+
const gitSha = process.env.GITHUB_SHA ?? null;
|
|
148
|
+
const gitBranch = process.env.GITHUB_REF_NAME ?? null;
|
|
149
|
+
const pkgVersion = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
150
|
+
const agentIri = process.env.CI_BOT_AGENT_IRI ?? null;
|
|
151
|
+
|
|
152
|
+
const ttl = buildActivityTurtle({
|
|
153
|
+
runId,
|
|
154
|
+
startedAt,
|
|
155
|
+
endedAt,
|
|
156
|
+
gitSha,
|
|
157
|
+
gitBranch,
|
|
158
|
+
toolId: 'concept-browser',
|
|
159
|
+
toolVersion: pkgVersion,
|
|
160
|
+
datasetRegisters,
|
|
161
|
+
conceptCount,
|
|
162
|
+
associatedAgentIri: agentIri,
|
|
163
|
+
});
|
|
164
|
+
const activityDir = path.join(DATA, 'activity');
|
|
165
|
+
fs.mkdirSync(activityDir, { recursive: true });
|
|
166
|
+
fs.writeFileSync(path.join(activityDir, `${runId}.ttl`), ttl);
|
|
167
|
+
console.log(`Emitted build activity record: data/activity/${runId}.ttl`);
|
|
168
|
+
}
|
|
169
|
+
|
|
87
170
|
function termToDesignation(term) {
|
|
88
171
|
const typeMap = {
|
|
89
172
|
expression: 'gl:Expression',
|
|
@@ -1007,6 +1090,9 @@ async function processDataset(dir, register, opts) {
|
|
|
1007
1090
|
if (opts.sections && opts.sections.length > 0) manifest.sections = opts.sections;
|
|
1008
1091
|
writeJson(path.join(DATA, register, 'manifest.json'), manifest);
|
|
1009
1092
|
|
|
1093
|
+
// Dataset-level RDF (WS J2/J5): dcat:Dataset + skos:ConceptScheme + skos:Collection per section.
|
|
1094
|
+
writeDatasetRdf(register, manifest, concepts, refMaps, opts);
|
|
1095
|
+
|
|
1010
1096
|
// Copy bibliography.yaml → bibliography.json
|
|
1011
1097
|
const bibPath = path.join(sourceRoot, 'bibliography.yaml');
|
|
1012
1098
|
if (fs.existsSync(bibPath)) {
|
|
@@ -1522,3 +1608,53 @@ console.log(`\nDone! Generated data for ${total} concepts across ${registry.leng
|
|
|
1522
1608
|
for (const [id, count] of Object.entries(counts)) {
|
|
1523
1609
|
console.log(` ${id}: ${count} concepts`);
|
|
1524
1610
|
}
|
|
1611
|
+
|
|
1612
|
+
writeBuildActivity(total, registry.map(r => r.id));
|
|
1613
|
+
|
|
1614
|
+
fs.writeFileSync(path.join(DATA, '_vocab.ttl'), buildVocabularyTurtle());
|
|
1615
|
+
console.log('Emitted vocabulary graph: data/_vocab.ttl');
|
|
1616
|
+
|
|
1617
|
+
const contributors = config.contributors ?? [];
|
|
1618
|
+
if (contributors.length > 0) {
|
|
1619
|
+
fs.writeFileSync(path.join(DATA, 'agents.ttl'), buildAgentsTurtle(contributors));
|
|
1620
|
+
console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Bibliography aggregation (K5): one bib.ttl per register from bibliography.json
|
|
1624
|
+
for (const ds of registry) {
|
|
1625
|
+
const bibPath = path.join(DATA, ds.id, 'bibliography.json');
|
|
1626
|
+
if (fs.existsSync(bibPath)) {
|
|
1627
|
+
const bibJson = JSON.parse(fs.readFileSync(bibPath, 'utf8'));
|
|
1628
|
+
const bibTtl = buildBibliographyTurtle(ds.id, bibJson);
|
|
1629
|
+
fs.writeFileSync(path.join(DATA, ds.id, 'bib.ttl'), bibTtl);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const datasetVersions = registry.map(ds => ({
|
|
1634
|
+
registerId: ds.id,
|
|
1635
|
+
datasetIri: `${refMaps.uriBase}/${ds.id}/`,
|
|
1636
|
+
versions: [
|
|
1637
|
+
{
|
|
1638
|
+
version: pkgVersionForVersions(),
|
|
1639
|
+
generatedAt: new Date().toISOString(),
|
|
1640
|
+
changeSummary: `Build ${new Date().toISOString().slice(0, 10)}`,
|
|
1641
|
+
},
|
|
1642
|
+
],
|
|
1643
|
+
}));
|
|
1644
|
+
|
|
1645
|
+
if (datasetVersions.length > 0) {
|
|
1646
|
+
const versionTtl = datasetVersions.map(v =>
|
|
1647
|
+
buildVersionHistoryTurtle({
|
|
1648
|
+
registerId: v.registerId,
|
|
1649
|
+
datasetIri: v.datasetIri,
|
|
1650
|
+
versions: v.versions,
|
|
1651
|
+
associatedAgentIri: process.env.CI_BOT_AGENT_IRI ?? null,
|
|
1652
|
+
}),
|
|
1653
|
+
).join('\n');
|
|
1654
|
+
fs.writeFileSync(path.join(DATA, 'versions.ttl'), versionTtl);
|
|
1655
|
+
console.log(`Emitted versions graph: data/versions.ttl (${datasetVersions.length} datasets)`);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function pkgVersionForVersions() {
|
|
1659
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
1660
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Extract SKOS taxonomy data from concept-model TTL files into JSON
|
|
4
4
|
* for browser consumption via the OntologyRegistry.
|
|
5
5
|
*
|
|
6
|
-
* Reads from:
|
|
6
|
+
* Reads from: data/concept-model/taxonomies/*.ttl (vendored from glossarist/concept-model)
|
|
7
7
|
* Writes to: src/data/taxonomies.json
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'url';
|
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const ROOT = resolve(__dirname, '..');
|
|
16
|
-
const TAXONOMY_DIR = resolve(ROOT, '
|
|
16
|
+
const TAXONOMY_DIR = resolve(ROOT, 'data', 'concept-model', 'taxonomies');
|
|
17
17
|
const OUTPUT = resolve(ROOT, 'src', 'data', 'taxonomies.json');
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -151,7 +151,7 @@ const TAXONOMY_MAP = {
|
|
|
151
151
|
function main() {
|
|
152
152
|
if (!existsSync(TAXONOMY_DIR)) {
|
|
153
153
|
console.error(`Taxonomy directory not found: ${TAXONOMY_DIR}`);
|
|
154
|
-
console.error('
|
|
154
|
+
console.error('Run `npm run sync:model` to vendor concept-model data.');
|
|
155
155
|
process.exit(1);
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Parse the glossarist OWL ontology (TTL) into a structured JSON schema
|
|
4
4
|
* for the Ontospy-style browser view.
|
|
5
5
|
*
|
|
6
|
-
* Reads:
|
|
6
|
+
* Reads: data/concept-model/glossarist.ttl (vendored from glossarist/concept-model)
|
|
7
7
|
* Writes: src/data/ontology-schema.json
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'url';
|
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const ROOT = resolve(__dirname, '..');
|
|
16
|
-
const CONCEPT_MODEL = resolve(ROOT, '
|
|
16
|
+
const CONCEPT_MODEL = resolve(ROOT, 'data', 'concept-model');
|
|
17
17
|
const ONTOLOGY_TTL = resolve(CONCEPT_MODEL, 'glossarist.ttl');
|
|
18
18
|
const SHACL_TTL = resolve(CONCEPT_MODEL, 'shapes', 'glossarist.shacl.ttl');
|
|
19
19
|
const OUTPUT = resolve(ROOT, 'src', 'data', 'ontology-schema.json');
|
|
@@ -537,7 +537,7 @@ function parseAnnotationProperties(ttlText) {
|
|
|
537
537
|
function main() {
|
|
538
538
|
if (!existsSync(ONTOLOGY_TTL)) {
|
|
539
539
|
console.error(`Ontology file not found: ${ONTOLOGY_TTL}`);
|
|
540
|
-
console.error('
|
|
540
|
+
console.error('Run `npm run sync:model` to vendor from glossarist/concept-model.');
|
|
541
541
|
process.exit(1);
|
|
542
542
|
}
|
|
543
543
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function slugify(input) {
|
|
2
|
+
return String(input)
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.normalize('NFKD')
|
|
5
|
+
.replace(/[̀-ͯ]/g, '')
|
|
6
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
7
|
+
.replace(/^-+|-+$/g, '');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ttlLit(s) {
|
|
11
|
+
if (s == null) return '""';
|
|
12
|
+
const escaped = String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
13
|
+
return `"${escaped}"`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ttlPrefixed(qname) {
|
|
17
|
+
const colonIdx = qname.indexOf(':');
|
|
18
|
+
if (colonIdx < 0) return qname;
|
|
19
|
+
const local = qname.slice(colonIdx + 1);
|
|
20
|
+
const escaped = local.replace(/([/])/g, '\\$1');
|
|
21
|
+
return `${qname.slice(0, colonIdx + 1)}${escaped}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildAgentsTurtle(contributors, agentBase = 'https://glossarist.org/agent') {
|
|
25
|
+
const lines = [];
|
|
26
|
+
lines.push('@prefix foaf: <http://xmlns.com/foaf/0.1/> .');
|
|
27
|
+
lines.push('@prefix prov: <http://www.w3.org/ns/prov#> .');
|
|
28
|
+
lines.push('@prefix dcterms: <http://purl.org/dc/terms/> .');
|
|
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
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ttlLit, ttlIri } from './turtle-escape.mjs';
|
|
2
|
+
|
|
3
|
+
function entryFromV3(e, fallbackId) {
|
|
4
|
+
return {
|
|
5
|
+
id: e.id ?? fallbackId ?? '',
|
|
6
|
+
reference: e.reference ?? '',
|
|
7
|
+
title: e.title,
|
|
8
|
+
link: e.link,
|
|
9
|
+
type: e.type,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeBibliographyData(raw) {
|
|
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
|
+
];
|
|
33
|
+
|
|
34
|
+
const datasetIri = `${baseUri}/${register}/`;
|
|
35
|
+
const entries = normalizeBibliographyData(bibliographyJson);
|
|
36
|
+
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (!entry.id || !entry.reference) continue;
|
|
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
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const PREFIXES = [
|
|
2
|
+
['prov', 'http://www.w3.org/ns/prov#'],
|
|
3
|
+
['dcterms', 'http://purl.org/dc/terms/'],
|
|
4
|
+
['foaf', 'http://xmlns.com/foaf/0.1/'],
|
|
5
|
+
['rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'],
|
|
6
|
+
['rdfs', 'http://www.w3.org/2000/01/rdf-schema#'],
|
|
7
|
+
['xsd', 'http://www.w3.org/2001/XMLSchema#'],
|
|
8
|
+
['gloss', 'https://www.glossarist.org/ontologies/'],
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function ttlLit(s) {
|
|
12
|
+
if (s == null) return '""';
|
|
13
|
+
const escaped = String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
14
|
+
return `"${escaped}"`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildActivityTurtle(input) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
for (const [prefix, iri] of PREFIXES) {
|
|
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';
|
|
92
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} BuildCacheEntry
|
|
8
|
+
* @property {string} hash
|
|
9
|
+
* @property {unknown} value
|
|
10
|
+
* @property {string} storedAt
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class BuildCache {
|
|
14
|
+
/** @param {string} cacheDir */
|
|
15
|
+
constructor(cacheDir) {
|
|
16
|
+
this.cacheDir = cacheDir;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @template T @param {string} key @returns {Promise<BuildCacheEntry<T> | null>} */
|
|
20
|
+
async get(key) {
|
|
21
|
+
const path = this.pathFor(key);
|
|
22
|
+
try {
|
|
23
|
+
const text = await readFile(path, 'utf8');
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @template T
|
|
32
|
+
* @param {string} key
|
|
33
|
+
* @param {T} value
|
|
34
|
+
* @param {string} hash
|
|
35
|
+
*/
|
|
36
|
+
async set(key, value, hash) {
|
|
37
|
+
const path = this.pathFor(key);
|
|
38
|
+
await mkdir(dirname(path), { recursive: true });
|
|
39
|
+
const entry = { hash, value, storedAt: new Date().toISOString() };
|
|
40
|
+
await writeFile(path, JSON.stringify(entry));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @template T
|
|
45
|
+
* @param {string} key
|
|
46
|
+
* @param {string} hash
|
|
47
|
+
* @param {() => Promise<T>} producer
|
|
48
|
+
* @returns {Promise<{ value: T; hit: boolean }>}
|
|
49
|
+
*/
|
|
50
|
+
async readThrough(key, hash, producer) {
|
|
51
|
+
const cached = await this.get(key);
|
|
52
|
+
if (cached && cached.hash === hash) {
|
|
53
|
+
return { value: cached.value, hit: true };
|
|
54
|
+
}
|
|
55
|
+
const value = await producer();
|
|
56
|
+
await this.set(key, value, hash);
|
|
57
|
+
return { value, hit: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @param {string} key @returns {string} */
|
|
61
|
+
pathFor(key) {
|
|
62
|
+
const safe = key.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
63
|
+
return `${this.cacheDir}/${safe}.json`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @param {string} input @returns {string} */
|
|
67
|
+
static hash(input) {
|
|
68
|
+
return createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const PREFIXES = [
|
|
2
|
+
['dcat', 'http://www.w3.org/ns/dcat#'],
|
|
3
|
+
['skos', 'http://www.w3.org/2004/02/skos/core#'],
|
|
4
|
+
['dcterms', 'http://purl.org/dc/terms/'],
|
|
5
|
+
['rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'],
|
|
6
|
+
['rdfs', 'http://www.w3.org/2000/01/rdf-schema#'],
|
|
7
|
+
['prov', 'http://www.w3.org/ns/prov#'],
|
|
8
|
+
['xsd', 'http://www.w3.org/2001/XMLSchema#'],
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function ttlLit(s) {
|
|
12
|
+
if (s == null) return '""';
|
|
13
|
+
const escaped = String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
14
|
+
return `"${escaped}"`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildDatasetTurtle(input) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
for (const [prefix, iri] of PREFIXES) {
|
|
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';
|
|
79
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const PREFIXES = [
|
|
2
|
+
['prov', 'http://www.w3.org/ns/prov#'],
|
|
3
|
+
['dcterms', 'http://purl.org/dc/terms/'],
|
|
4
|
+
['rdfs', 'http://www.w3.org/2000/01/rdf-schema#'],
|
|
5
|
+
['xsd', 'http://www.w3.org/2001/XMLSchema#'],
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
function ttlLit(s) {
|
|
9
|
+
if (s == null) return '""';
|
|
10
|
+
const escaped = String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
11
|
+
return `"${escaped}"`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildVersionTurtle(input) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
for (const [prefix, iri] of PREFIXES) {
|
|
17
|
+
lines.push(`@prefix ${prefix}: <${iri}> .`);
|
|
18
|
+
}
|
|
19
|
+
lines.push('');
|
|
20
|
+
|
|
21
|
+
lines.push(`<${input.versionIri}> a prov:Entity ;`);
|
|
22
|
+
lines.push(` rdfs:label ${ttlLit(`${input.registerId} version ${input.version}`)} ;`);
|
|
23
|
+
lines.push(` dcterms:isVersionOf <${input.datasetIri}> ;`);
|
|
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';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildVersionHistoryTurtle(input) {
|
|
39
|
+
let previousIri;
|
|
40
|
+
const blocks = [];
|
|
41
|
+
for (const v of input.versions ?? []) {
|
|
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
|
+
}
|