glossarist 0.4.12 → 0.4.14
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 +3 -3
- package/src/models/bibliography-data.js +6 -1
- package/src/models/index.d.ts +2 -0
- package/src/models/register.js +21 -1
- package/src/rdf/agents-emitter.js +6 -5
- package/src/rdf/bibliography-emitter.js +7 -4
- package/src/rdf/bnode-id.js +35 -0
- package/src/rdf/build-activity-emitter.js +7 -6
- package/src/rdf/curie.js +45 -0
- package/src/rdf/dataset-emitter.js +13 -24
- package/src/rdf/group-emitter.js +7 -6
- package/src/rdf/image-variant-emitter.js +6 -5
- package/src/rdf/index.d.ts +40 -1
- package/src/rdf/index.js +11 -1
- package/src/rdf/provenance-emitter.js +87 -0
- package/src/rdf/sections-builder.js +177 -0
- package/src/rdf/shacl.js +6 -1
- package/src/rdf/version-emitter.js +5 -4
- package/src/rdf/vocabulary-emitter.js +3 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glossarist",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"description": "JavaScript SDK for Glossarist GCR packages — read, write, validate, and manage terminology concepts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -91,10 +91,10 @@
|
|
|
91
91
|
"@rdfjs/data-model": "^2.0.0",
|
|
92
92
|
"@rdfjs/dataset": "^2.0.0",
|
|
93
93
|
"js-yaml": "^5.0.0",
|
|
94
|
-
"jsonld": "^
|
|
94
|
+
"jsonld": "^9.0.0",
|
|
95
95
|
"jszip": "^3.10.1",
|
|
96
96
|
"n3": "^1.17.0",
|
|
97
|
-
"rdf-validate-shacl": "^0.
|
|
97
|
+
"rdf-validate-shacl": "^0.6.5"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@eslint/js": "^10.0.1",
|
|
@@ -5,7 +5,12 @@ import { BibliographyEntry } from './bibliography-entry.js';
|
|
|
5
5
|
export class BibliographyData extends GlossaristModel {
|
|
6
6
|
constructor(data = {}) {
|
|
7
7
|
super();
|
|
8
|
-
|
|
8
|
+
let entriesData;
|
|
9
|
+
if (Array.isArray(data)) {
|
|
10
|
+
entriesData = data;
|
|
11
|
+
} else {
|
|
12
|
+
entriesData = data?.bibliography ?? data?.entries ?? [];
|
|
13
|
+
}
|
|
9
14
|
this._rawEntries = Array.isArray(entriesData) ? entriesData : [];
|
|
10
15
|
this._entries = null;
|
|
11
16
|
}
|
package/src/models/index.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export const REGISTER_STATUSES: readonly string[];
|
|
|
58
58
|
export class Register extends GlossaristModel {
|
|
59
59
|
readonly schemaVersion: string;
|
|
60
60
|
readonly id: string | null;
|
|
61
|
+
readonly name: string | Record<string, string> | null;
|
|
61
62
|
readonly ref: string | null;
|
|
62
63
|
readonly refAliases: string[];
|
|
63
64
|
readonly year: number | null;
|
|
@@ -79,6 +80,7 @@ export class Register extends GlossaristModel {
|
|
|
79
80
|
readonly sections: Section[];
|
|
80
81
|
sectionById(id: string): Section | null;
|
|
81
82
|
sectionName(sectionId: string, lang: string): string | null;
|
|
83
|
+
displayName(lang: string): string | null;
|
|
82
84
|
static fromJSON(data: Record<string, unknown>): Register;
|
|
83
85
|
}
|
|
84
86
|
|
package/src/models/register.js
CHANGED
|
@@ -14,6 +14,7 @@ export class Register extends GlossaristModel {
|
|
|
14
14
|
super();
|
|
15
15
|
this.schemaVersion = data.schema_version ?? data.schemaVersion ?? '3';
|
|
16
16
|
this.id = data.id ?? null;
|
|
17
|
+
this.name = data.name ?? null;
|
|
17
18
|
this.ref = data.ref ?? null;
|
|
18
19
|
this.refAliases = data.refAliases ?? data.ref_aliases ?? [];
|
|
19
20
|
this.year = data.year ?? null;
|
|
@@ -42,7 +43,7 @@ export class Register extends GlossaristModel {
|
|
|
42
43
|
_extractRaw(data) {
|
|
43
44
|
const known = new Set([
|
|
44
45
|
'schema_version', 'schemaVersion',
|
|
45
|
-
'id', 'ref', 'refAliases', 'ref_aliases',
|
|
46
|
+
'id', 'name', 'ref', 'refAliases', 'ref_aliases',
|
|
46
47
|
'year', 'urn', 'urnAliases', 'urn_aliases',
|
|
47
48
|
'status', 'supersedes', 'owner',
|
|
48
49
|
'sourceRepo', 'source_repo', 'tags',
|
|
@@ -138,6 +139,24 @@ export class Register extends GlossaristModel {
|
|
|
138
139
|
return section ? section.name(lang) : null;
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
// Localized display name. Returns the best-matching name string for
|
|
143
|
+
// the requested language, falling back to English, then to the first
|
|
144
|
+
// available language, then to null. Accepts three input shapes:
|
|
145
|
+
// - null/undefined → null
|
|
146
|
+
// - string → returned as-is for every language
|
|
147
|
+
// - { eng, fra, ... } → language-resolved
|
|
148
|
+
displayName(lang) {
|
|
149
|
+
if (this.name == null) return null;
|
|
150
|
+
if (typeof this.name === 'string') return this.name;
|
|
151
|
+
if (typeof this.name === 'object') {
|
|
152
|
+
return this.name[lang]
|
|
153
|
+
?? this.name.eng
|
|
154
|
+
?? Object.values(this.name)[0]
|
|
155
|
+
?? null;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
141
160
|
// Returns the hex color for the requested UI mode, falling back to
|
|
142
161
|
// the single hex when the spec is a string. Returns null when no
|
|
143
162
|
// color is set or when the requested mode is missing.
|
|
@@ -148,6 +167,7 @@ export class Register extends GlossaristModel {
|
|
|
148
167
|
toJSON() {
|
|
149
168
|
const obj = { ...this._raw, schema_version: this.schemaVersion };
|
|
150
169
|
if (this.id != null) obj.id = this.id;
|
|
170
|
+
if (this.name != null) obj.name = this.name;
|
|
151
171
|
if (this.ref != null) obj.ref = this.ref;
|
|
152
172
|
if (this.refAliases.length > 0) obj.refAliases = [...this.refAliases];
|
|
153
173
|
if (this.year != null) obj.year = this.year;
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
// prov:Organization, and the person is linked via prov:actedOnBehalfOf.
|
|
8
8
|
|
|
9
9
|
import { namedNode, literal, quad } from './terms.js';
|
|
10
|
+
import { PREFIXES } from './predicates.js';
|
|
11
|
+
import { RDF_TYPE } from './curie.js';
|
|
10
12
|
|
|
11
|
-
const PROV_NS
|
|
12
|
-
const FOAF_NS
|
|
13
|
-
const DCTERMS_NS =
|
|
14
|
-
const RDFS_NS
|
|
15
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
13
|
+
const PROV_NS = PREFIXES.prov;
|
|
14
|
+
const FOAF_NS = PREFIXES.foaf ?? 'http://xmlns.com/foaf/0.1/';
|
|
15
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
16
|
+
const RDFS_NS = PREFIXES.rdfs;
|
|
16
17
|
|
|
17
18
|
const PROV = {
|
|
18
19
|
Person: `${PROV_NS}Person`,
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
// - dcterms:isPartOf (parent dataset)
|
|
11
11
|
|
|
12
12
|
import { namedNode, literal, quad } from './terms.js';
|
|
13
|
+
import { PREFIXES } from './predicates.js';
|
|
14
|
+
import { RDF_TYPE } from './curie.js';
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
// Namespace IRIs from canonical PREFIXES. foaf is absent from the
|
|
17
|
+
// JSON-LD context (therefore not in PREFIXES) — declare locally.
|
|
18
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
19
|
+
const FOAF_NS = PREFIXES.foaf ?? 'http://xmlns.com/foaf/0.1/';
|
|
20
|
+
const GLOSS_NS = PREFIXES.gloss;
|
|
18
21
|
|
|
19
22
|
const DCTERMS = {
|
|
20
23
|
BibliographicResource: `${DCTERMS_NS}BibliographicResource`,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Deterministic blank node ID strategy.
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for bnode IDs across all emitters. Previously
|
|
4
|
+
// each emitter had its own local FNV-1a hash, producing different IDs
|
|
5
|
+
// for the same logical bnode. This module replaces those with one
|
|
6
|
+
// strategy: MD5 (via node:crypto) when available, FNV-1a fallback for
|
|
7
|
+
// browser bundles.
|
|
8
|
+
//
|
|
9
|
+
// Cross-process stability matches glossarist-ruby's deterministic-id
|
|
10
|
+
// when node:crypto is available. Within a single process, FNV-1a is
|
|
11
|
+
// also stable.
|
|
12
|
+
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
|
|
15
|
+
function fnv1a(seed) {
|
|
16
|
+
let h = 0x811c9dc5;
|
|
17
|
+
for (let i = 0; i < seed.length; i++) {
|
|
18
|
+
h ^= seed.charCodeAt(i);
|
|
19
|
+
h = Math.imul(h, 0x01000193);
|
|
20
|
+
}
|
|
21
|
+
return (h >>> 0).toString(16).padStart(8, '0');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function deterministicBnodeId(...parts) {
|
|
25
|
+
const seed = parts.filter(p => p != null && p !== '').join('|');
|
|
26
|
+
if (!seed) return `b${fnv1a('empty')}`;
|
|
27
|
+
// MD5 matches glossarist-ruby's deterministic-id for cross-process
|
|
28
|
+
// stability. Truncated to 12 hex chars — enough collision resistance
|
|
29
|
+
// for bnode IDs within a single document.
|
|
30
|
+
try {
|
|
31
|
+
return createHash('md5').update(seed).digest('hex').slice(0, 12);
|
|
32
|
+
} catch {
|
|
33
|
+
return `b${fnv1a(seed)}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
// - prov:wasAssociatedWith for the CI bot agent
|
|
10
10
|
|
|
11
11
|
import { namedNode, literal, quad } from './terms.js';
|
|
12
|
+
import { PREFIXES } from './predicates.js';
|
|
13
|
+
import { RDF_TYPE } from './curie.js';
|
|
12
14
|
|
|
13
|
-
const PROV_NS
|
|
14
|
-
const DCTERMS_NS =
|
|
15
|
-
const FOAF_NS
|
|
16
|
-
const GLOSS_NS
|
|
17
|
-
const XSD_NS
|
|
18
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
15
|
+
const PROV_NS = PREFIXES.prov;
|
|
16
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
17
|
+
const FOAF_NS = PREFIXES.foaf ?? 'http://xmlns.com/foaf/0.1/';
|
|
18
|
+
const GLOSS_NS = PREFIXES.gloss;
|
|
19
|
+
const XSD_NS = PREFIXES.xsd;
|
|
19
20
|
|
|
20
21
|
const PROV = {
|
|
21
22
|
Activity: `${PROV_NS}Activity`,
|
package/src/rdf/curie.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// CURIE helpers — resolve/compact IRIs against the canonical PREFIXES.
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for CURIE ↔ IRI conversion. Previously
|
|
4
|
+
// `resolveIri` was private to vocabulary-emitter and reimplemented
|
|
5
|
+
// (incorrectly) in other emitters. Callers now import from here.
|
|
6
|
+
|
|
7
|
+
import { PREFIXES } from './prefixes.js';
|
|
8
|
+
|
|
9
|
+
const ABSOLUTE_SCHEMES = new Set(['http', 'https', 'urn', 'file', 'mailto', 'ftp']);
|
|
10
|
+
|
|
11
|
+
export function isAbsoluteIri(s) {
|
|
12
|
+
if (typeof s !== 'string' || s.length === 0) return false;
|
|
13
|
+
if (s.startsWith('urn:')) return true;
|
|
14
|
+
const colon = s.indexOf(':');
|
|
15
|
+
if (colon < 1) return false;
|
|
16
|
+
return ABSOLUTE_SCHEMES.has(s.slice(0, colon));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveIri(iri, prefixes = PREFIXES) {
|
|
20
|
+
if (isAbsoluteIri(iri)) return iri;
|
|
21
|
+
const colon = iri.indexOf(':');
|
|
22
|
+
if (colon < 1) return iri;
|
|
23
|
+
const prefix = iri.slice(0, colon);
|
|
24
|
+
const local = iri.slice(colon + 1);
|
|
25
|
+
const base = prefixes[prefix];
|
|
26
|
+
return base ? `${base}${local}` : iri;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function compactIri(iri, prefixes = PREFIXES) {
|
|
30
|
+
let best = null;
|
|
31
|
+
let bestLen = 0;
|
|
32
|
+
for (const [prefix, base] of Object.entries(prefixes)) {
|
|
33
|
+
if (iri.startsWith(base) && base.length > bestLen) {
|
|
34
|
+
best = `${prefix}:${iri.slice(base.length)}`;
|
|
35
|
+
bestLen = base.length;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return best ?? iri;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Convenience constants — used by every emitter. Centralized here so
|
|
42
|
+
// the IRI string appears in exactly one place.
|
|
43
|
+
export const RDF_TYPE = `${PREFIXES.rdf}type`;
|
|
44
|
+
export const RDF_VALUE = `${PREFIXES.rdf}value`;
|
|
45
|
+
export const RDFS_LABEL = `${PREFIXES.rdfs}label`;
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
// for cascading section membership).
|
|
12
12
|
|
|
13
13
|
import { namedNode, blankNode, literal, quad } from './terms.js';
|
|
14
|
+
import { PREFIXES } from './predicates.js';
|
|
15
|
+
import { RDF_TYPE } from './curie.js';
|
|
16
|
+
import { deterministicBnodeId } from './bnode-id.js';
|
|
14
17
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const PROV_NS = 'http://www.w3.org/ns/prov#';
|
|
23
|
-
const GLOSS_NS = 'https://www.glossarist.org/ontologies/';
|
|
24
|
-
const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
|
|
18
|
+
// Namespace IRIs from the canonical PREFIXES — single source of truth.
|
|
19
|
+
const DCAT_NS = PREFIXES.dcat ?? 'http://www.w3.org/ns/dcat#';
|
|
20
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
21
|
+
const SKOS_NS = PREFIXES.skos;
|
|
22
|
+
const PROV_NS = PREFIXES.prov;
|
|
23
|
+
const GLOSS_NS = PREFIXES.gloss;
|
|
24
|
+
const XSD_NS = PREFIXES.xsd;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @typedef {Object} DatasetDistribution
|
|
@@ -87,7 +87,6 @@ const GLOSS = {
|
|
|
87
87
|
};
|
|
88
88
|
const XSD_DATE = `${XSD_NS}date`;
|
|
89
89
|
const XSD_INTEGER = `${XSD_NS}integer`;
|
|
90
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
91
90
|
|
|
92
91
|
/**
|
|
93
92
|
* Emits dcat:Dataset + skos:ConceptScheme quads for one register's
|
|
@@ -141,7 +140,7 @@ function* distributionToQuads(ds, dist) {
|
|
|
141
140
|
// outside their parent dataset. Using blankNode() (not namedNode with
|
|
142
141
|
// a "_:..." string) so the n3 writer serializes them as _:bXXXX
|
|
143
142
|
// rather than <_:bXXXX> which would be an invalid IRI.
|
|
144
|
-
const distSubject = blankNode(`b${
|
|
143
|
+
const distSubject = blankNode(`b${deterministicBnodeId('dist', dist.id, dist.downloadUrl)}`);
|
|
145
144
|
yield quad(ds, namedNode(DCAT.distribution), distSubject);
|
|
146
145
|
yield quad(distSubject, namedNode(RDF_TYPE), namedNode(DCAT.Distribution));
|
|
147
146
|
yield quad(distSubject, namedNode(DCTERMS.title), literal(dist.title));
|
|
@@ -167,15 +166,5 @@ function* sectionToQuads(section) {
|
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
// Local deterministic-id helper
|
|
171
|
-
//
|
|
172
|
-
// pattern using a small hash. Repeated calls within one process are
|
|
173
|
-
// stable; cross-process stability isn't required for blank node IDs.
|
|
174
|
-
function deterministicId(...parts) {
|
|
175
|
-
const seed = parts.filter(p => p != null).join('|');
|
|
176
|
-
let h = 0;
|
|
177
|
-
for (let i = 0; i < seed.length; i++) {
|
|
178
|
-
h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
|
|
179
|
-
}
|
|
180
|
-
return (h >>> 0).toString(16).padStart(8, '0');
|
|
181
|
-
}
|
|
169
|
+
// Local deterministic-id helper is no longer needed — using the shared
|
|
170
|
+
// deterministicBnodeId from bnode-id.js for cross-emitter consistency.
|
package/src/rdf/group-emitter.js
CHANGED
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
// Both kinds are also typed as skos:ConceptScheme for SKOS interop.
|
|
13
13
|
|
|
14
14
|
import { namedNode, literal, quad } from './terms.js';
|
|
15
|
+
import { PREFIXES } from './predicates.js';
|
|
16
|
+
import { RDF_TYPE } from './curie.js';
|
|
15
17
|
|
|
16
|
-
//
|
|
17
|
-
const DCAT_NS
|
|
18
|
-
const DCTERMS_NS =
|
|
19
|
-
const SKOS_NS
|
|
20
|
-
const PROV_NS
|
|
21
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
18
|
+
// Namespace IRIs from canonical PREFIXES.
|
|
19
|
+
const DCAT_NS = PREFIXES.dcat ?? 'http://www.w3.org/ns/dcat#';
|
|
20
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
21
|
+
const SKOS_NS = PREFIXES.skos;
|
|
22
|
+
const PROV_NS = PREFIXES.prov;
|
|
22
23
|
|
|
23
24
|
const DCAT = {
|
|
24
25
|
DatasetSeries: `${DCAT_NS}DatasetSeries`,
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
// - dcat:downloadURL (canonical download)
|
|
9
9
|
|
|
10
10
|
import { namedNode, literal, quad } from './terms.js';
|
|
11
|
+
import { PREFIXES } from './predicates.js';
|
|
12
|
+
import { RDF_TYPE } from './curie.js';
|
|
11
13
|
|
|
12
|
-
const FOAF_NS
|
|
13
|
-
const DCTERMS_NS =
|
|
14
|
-
const DCAT_NS
|
|
15
|
-
const XSD_NS
|
|
16
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
14
|
+
const FOAF_NS = PREFIXES.foaf ?? 'http://xmlns.com/foaf/0.1/';
|
|
15
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
16
|
+
const DCAT_NS = PREFIXES.dcat ?? 'http://www.w3.org/ns/dcat#';
|
|
17
|
+
const XSD_NS = PREFIXES.xsd;
|
|
17
18
|
|
|
18
19
|
const FOAF = {
|
|
19
20
|
Image: `${FOAF_NS}Image`,
|
package/src/rdf/index.d.ts
CHANGED
|
@@ -107,7 +107,46 @@ export interface VocabScheme {
|
|
|
107
107
|
label: string;
|
|
108
108
|
terms: readonly VocabTerm[];
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
|
|
111
|
+
// ── CURIE + bnode helpers ─────────────────────────────────────────────
|
|
112
|
+
export declare const RDF_TYPE: string;
|
|
113
|
+
export declare const RDF_VALUE: string;
|
|
114
|
+
export declare const RDFS_LABEL: string;
|
|
115
|
+
export declare function isAbsoluteIri(s: string): boolean;
|
|
116
|
+
export declare function resolveIri(iri: string, prefixes?: Record<string, string>): string;
|
|
117
|
+
export declare function compactIri(iri: string, prefixes?: Record<string, string>): string;
|
|
118
|
+
export declare function deterministicBnodeId(...parts: Array<string | number | null | undefined>): string;
|
|
119
|
+
|
|
120
|
+
// ── Sections builder (Quad[] → ClassInstance[] for UI) ────────────────
|
|
121
|
+
export interface PropValue {
|
|
122
|
+
predicate: string;
|
|
123
|
+
values: string[];
|
|
124
|
+
nested?: boolean;
|
|
125
|
+
}
|
|
126
|
+
export interface ClassInstance {
|
|
127
|
+
classId: string;
|
|
128
|
+
classLabel: string;
|
|
129
|
+
label: string;
|
|
130
|
+
props: PropValue[];
|
|
131
|
+
}
|
|
132
|
+
export interface SectionsBuilderOptions {
|
|
133
|
+
language?: string;
|
|
134
|
+
}
|
|
135
|
+
export declare function quadSectionsToClassInstances(
|
|
136
|
+
quads: readonly Quad[],
|
|
137
|
+
options?: SectionsBuilderOptions,
|
|
138
|
+
): ClassInstance[];
|
|
139
|
+
|
|
140
|
+
// ── Provenance emitter (subject → prov:wasGeneratedBy + Activity) ─────
|
|
141
|
+
export interface ProvenanceInput {
|
|
142
|
+
subjectUri: string;
|
|
143
|
+
serializer: string;
|
|
144
|
+
serializerVersion: string;
|
|
145
|
+
generatedAt: string;
|
|
146
|
+
canonicalUri?: string;
|
|
147
|
+
}
|
|
148
|
+
export declare function provenanceToQuads(input: ProvenanceInput): Generator<Quad, void, unknown>;
|
|
149
|
+
|
|
111
150
|
export declare function vocabularySchemeToQuads(scheme: VocabScheme): Generator<Quad, void, unknown>;
|
|
112
151
|
export declare function vocabularyToQuads(schemes: readonly VocabScheme[]): Generator<Quad, void, unknown>;
|
|
113
152
|
|
package/src/rdf/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// Public RDF layer. Mirrors lib/glossarist/rdf/ in glossarist-ruby.
|
|
2
2
|
export { PRED, PREFIXES, SKOSXL, WELL_KNOWN } from './prefixes.js';
|
|
3
|
+
export {
|
|
4
|
+
resolveIri,
|
|
5
|
+
compactIri,
|
|
6
|
+
isAbsoluteIri,
|
|
7
|
+
RDF_TYPE,
|
|
8
|
+
RDF_VALUE,
|
|
9
|
+
RDFS_LABEL,
|
|
10
|
+
} from './curie.js';
|
|
11
|
+
export { deterministicBnodeId } from './bnode-id.js';
|
|
3
12
|
export { deterministicId, deterministicBnode } from './deterministic-id.js';
|
|
4
13
|
export { namedNode, blankNode, literal, defaultGraph, quad } from './terms.js';
|
|
5
14
|
export {
|
|
@@ -25,7 +34,6 @@ export {
|
|
|
25
34
|
export {
|
|
26
35
|
vocabularySchemeToQuads,
|
|
27
36
|
vocabularyToQuads,
|
|
28
|
-
resolveIri,
|
|
29
37
|
} from './vocabulary-emitter.js';
|
|
30
38
|
export { datasetToQuads } from './dataset-emitter.js';
|
|
31
39
|
export { groupToQuads } from './group-emitter.js';
|
|
@@ -43,3 +51,5 @@ export {
|
|
|
43
51
|
} from './document-writer.js';
|
|
44
52
|
export { writeTurtleSync } from './write-turtle-sync.js';
|
|
45
53
|
export { validateShacl, loadShapes, clearShapesCache, quadsToDataset } from './shacl.js';
|
|
54
|
+
export { quadSectionsToClassInstances } from './sections-builder.js';
|
|
55
|
+
export { provenanceToQuads } from './provenance-emitter.js';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Provenance emitter — emits prov:wasGeneratedBy + prov:Activity quads
|
|
2
|
+
// for any resource that needs build/view-time provenance.
|
|
3
|
+
//
|
|
4
|
+
// Replaces concept-browser's src/components/concept-rdf/provenance.ts
|
|
5
|
+
// (runtime provenance at view-time). Same output shape, but operates
|
|
6
|
+
// on standard RDF/JS Quad[] (no RdfGraph abstraction).
|
|
7
|
+
|
|
8
|
+
import { namedNode, blankNode, literal, quad } from './terms.js';
|
|
9
|
+
import { PREFIXES } from './predicates.js';
|
|
10
|
+
import { RDF_TYPE, RDFS_LABEL } from './curie.js';
|
|
11
|
+
import { deterministicBnodeId } from './bnode-id.js';
|
|
12
|
+
|
|
13
|
+
const PROV_NS = PREFIXES.prov;
|
|
14
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
15
|
+
const XSD_NS = PREFIXES.xsd;
|
|
16
|
+
|
|
17
|
+
const PROV = {
|
|
18
|
+
Activity: `${PROV_NS}Activity`,
|
|
19
|
+
Entity: `${PROV_NS}Entity`,
|
|
20
|
+
SoftwareAgent: `${PROV_NS}SoftwareAgent`,
|
|
21
|
+
Agent: `${PROV_NS}Agent`,
|
|
22
|
+
wasGeneratedBy: `${PROV_NS}wasGeneratedBy`,
|
|
23
|
+
wasAssociatedWith: `${PROV_NS}wasAssociatedWith`,
|
|
24
|
+
generatedAtTime: `${PROV_NS}generatedAtTime`,
|
|
25
|
+
startedAtTime: `${PROV_NS}startedAtTime`,
|
|
26
|
+
endedAtTime: `${PROV_NS}endedAtTime`,
|
|
27
|
+
};
|
|
28
|
+
const DCTERMS = {
|
|
29
|
+
isVersionOf: `${DCTERMS_NS}isVersionOf`,
|
|
30
|
+
title: `${DCTERMS_NS}title`,
|
|
31
|
+
};
|
|
32
|
+
const FOAF_NS = PREFIXES.foaf ?? 'http://xmlns.com/foaf/0.1/';
|
|
33
|
+
const XSD_DATETIME = `${XSD_NS}dateTime`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} ProvenanceInput
|
|
37
|
+
* @property {string} subjectUri — the resource being provenance-tagged
|
|
38
|
+
* @property {string} serializer — e.g. "concept-browser"
|
|
39
|
+
* @property {string} serializerVersion — e.g. "0.7.66"
|
|
40
|
+
* @property {string} generatedAt — ISO 8601 date-time
|
|
41
|
+
* @property {string} [canonicalUri] — for dcterms:isVersionOf (versions)
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Emits provenance quads linking subjectUri to a prov:Activity
|
|
46
|
+
* performed by a prov:SoftwareAgent. The activity IRI is relative
|
|
47
|
+
* (under the document base): `activity/serializers/{serializer}/{version}`.
|
|
48
|
+
*
|
|
49
|
+
* Idempotent: same input always produces the same bnode IDs and the
|
|
50
|
+
* same set of quads.
|
|
51
|
+
*
|
|
52
|
+
* @param {ProvenanceInput} input
|
|
53
|
+
* @returns {Generator<import('@rdfjs/dataset').Quad, void, unknown>}
|
|
54
|
+
*/
|
|
55
|
+
export function* provenanceToQuads(input) {
|
|
56
|
+
if (!input?.subjectUri) throw new Error('provenanceToQuads: subjectUri is required');
|
|
57
|
+
if (!input?.serializer) throw new Error('provenanceToQuads: serializer is required');
|
|
58
|
+
if (!input?.serializerVersion) throw new Error('provenanceToQuads: serializerVersion is required');
|
|
59
|
+
if (!input?.generatedAt) throw new Error('provenanceToQuads: generatedAt is required');
|
|
60
|
+
|
|
61
|
+
const subject = namedNode(input.subjectUri);
|
|
62
|
+
const activityIri = namedNode(`activity/serializers/${input.serializer}/${input.serializerVersion}`);
|
|
63
|
+
|
|
64
|
+
// Subject is a prov:Entity, generated by the activity, at the timestamp
|
|
65
|
+
yield quad(subject, namedNode(RDF_TYPE), namedNode(PROV.Entity));
|
|
66
|
+
yield quad(subject, namedNode(PROV.wasGeneratedBy), activityIri);
|
|
67
|
+
yield quad(subject, namedNode(PROV.generatedAtTime), literal(input.generatedAt, namedNode(XSD_DATETIME)));
|
|
68
|
+
|
|
69
|
+
if (input.canonicalUri && input.canonicalUri !== input.subjectUri) {
|
|
70
|
+
yield quad(subject, namedNode(DCTERMS.isVersionOf), namedNode(input.canonicalUri));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Activity is a prov:Activity, associated with the software agent
|
|
74
|
+
yield quad(activityIri, namedNode(RDF_TYPE), namedNode(PROV.Activity));
|
|
75
|
+
yield quad(activityIri, namedNode(RDFS_LABEL), literal(`${input.serializer} ${input.serializerVersion}`));
|
|
76
|
+
|
|
77
|
+
// Software agent as a deterministic blank node — same input always
|
|
78
|
+
// produces the same bnode ID so multiple subjects that share a
|
|
79
|
+
// serializer/version collapse to one agent.
|
|
80
|
+
const agentId = deterministicBnodeId('agent', input.serializer, input.serializerVersion);
|
|
81
|
+
const agent = blankNode(`b${agentId}`);
|
|
82
|
+
yield quad(activityIri, namedNode(PROV.wasAssociatedWith), agent);
|
|
83
|
+
yield quad(agent, namedNode(RDF_TYPE), namedNode(PROV.SoftwareAgent));
|
|
84
|
+
yield quad(agent, namedNode(RDF_TYPE), namedNode(PROV.Agent));
|
|
85
|
+
yield quad(agent, namedNode(RDFS_LABEL), literal(input.serializer));
|
|
86
|
+
yield quad(agent, namedNode(`${FOAF_NS}name`), literal(input.serializer));
|
|
87
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Sections builder — transforms a Quad[] into the ClassInstance[]
|
|
2
|
+
// shape consumed by Vue UI components (RdfInstanceSection,
|
|
3
|
+
// RdfInstanceHeader, RdfSourcePanel).
|
|
4
|
+
//
|
|
5
|
+
// The grouping logic is RDF-structure-driven, not Vue-specific:
|
|
6
|
+
// 1. Group quads by subject (NamedNode or BlankNode)
|
|
7
|
+
// 2. For each subject:
|
|
8
|
+
// - rdf:type values → classId (first type wins, multi-type joined)
|
|
9
|
+
// - Derive classLabel from classId local name (CURIE local part)
|
|
10
|
+
// - Derive label from skos:prefLabel/rdfs:label/URI segment
|
|
11
|
+
// - Collect remaining predicates → props
|
|
12
|
+
// 3. For bnode objects, recursively nest their triples as a
|
|
13
|
+
// nested PropValue
|
|
14
|
+
// 4. Return ClassInstance[] sorted by subject IRI (insertion order
|
|
15
|
+
// preserved when subjects appear in canonical order)
|
|
16
|
+
//
|
|
17
|
+
// This module consumes standard RDF/JS Quad[] — no custom RdfGraph
|
|
18
|
+
// abstraction. Any consumer (Vue UI, CLI inspector, debug tool) can
|
|
19
|
+
// use it.
|
|
20
|
+
|
|
21
|
+
import { RDF_TYPE, RDFS_LABEL, compactIri } from './curie.js';
|
|
22
|
+
import { PREFIXES } from './predicates.js';
|
|
23
|
+
|
|
24
|
+
const SKOS_PREF_LABEL = `${PREFIXES.skos}prefLabel`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} PropValue
|
|
28
|
+
* @property {string} predicate
|
|
29
|
+
* @property {string[]} values
|
|
30
|
+
* @property {boolean} [nested]
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} ClassInstance
|
|
34
|
+
* @property {string} classId
|
|
35
|
+
* @property {string} classLabel
|
|
36
|
+
* @property {string} label
|
|
37
|
+
* @property {PropValue[]} props
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} SectionsBuilderOptions
|
|
41
|
+
* @property {string} [language='eng'] — preferred language for skos:prefLabel
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build ClassInstance[] from a Quad[]. Bnodes referenced as objects
|
|
46
|
+
* are recursively nested into the parent prop.
|
|
47
|
+
*
|
|
48
|
+
* @param {import('@rdfjs/dataset').Quad[]} quads
|
|
49
|
+
* @param {SectionsBuilderOptions} [options]
|
|
50
|
+
* @returns {ClassInstance[]}
|
|
51
|
+
*/
|
|
52
|
+
export function quadSectionsToClassInstances(quads, options = {}) {
|
|
53
|
+
const language = options.language ?? 'eng';
|
|
54
|
+
if (!quads || quads.length === 0) return [];
|
|
55
|
+
|
|
56
|
+
// Group quads by subject
|
|
57
|
+
const bySubject = new Map();
|
|
58
|
+
const bnodeQuads = new Map(); // separate bnode quads for nested lookup
|
|
59
|
+
const order = [];
|
|
60
|
+
|
|
61
|
+
for (const q of quads) {
|
|
62
|
+
const subj = q.subject;
|
|
63
|
+
const isBnode = subj.termType === 'BlankNode';
|
|
64
|
+
const key = subj.value;
|
|
65
|
+
const target = isBnode ? bnodeQuads : bySubject;
|
|
66
|
+
if (!target.has(key)) {
|
|
67
|
+
target.set(key, { subject: subj, quads: [] });
|
|
68
|
+
if (!isBnode) order.push(key);
|
|
69
|
+
}
|
|
70
|
+
target.get(key).quads.push(q);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result = [];
|
|
74
|
+
for (const key of order) {
|
|
75
|
+
const entry = bySubject.get(key);
|
|
76
|
+
result.push(resourceToClassInstance(entry.subject, entry.quads, bnodeQuads, language));
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resourceToClassInstance(subject, quads, bnodeQuads, language) {
|
|
82
|
+
const types = [];
|
|
83
|
+
const otherQuads = [];
|
|
84
|
+
let prefLabel = null;
|
|
85
|
+
let rdfsLabel = null;
|
|
86
|
+
|
|
87
|
+
for (const q of quads) {
|
|
88
|
+
if (q.predicate.value === RDF_TYPE) {
|
|
89
|
+
types.push(q.object.value);
|
|
90
|
+
} else if (q.predicate.value === SKOS_PREF_LABEL) {
|
|
91
|
+
// Prefer prefLabel in the requested language; first one wins per lang
|
|
92
|
+
if (!prefLabel || (q.object.language === language && prefLabel.language !== language)) {
|
|
93
|
+
prefLabel = { value: q.object.value, language: q.object.language };
|
|
94
|
+
}
|
|
95
|
+
} else if (q.predicate.value === RDFS_LABEL && !rdfsLabel) {
|
|
96
|
+
rdfsLabel = q.object.value;
|
|
97
|
+
} else {
|
|
98
|
+
otherQuads.push(q);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const subjectStr = subject.value;
|
|
103
|
+
// Compact classId and classLabel to CURIE form for UI consumers.
|
|
104
|
+
// The Vue components compare against CURIEs ('gloss:Concept') not
|
|
105
|
+
// absolute IRIs, so the builder is responsible for the conversion.
|
|
106
|
+
const rawClassId = types.length > 0 ? types[0] : '';
|
|
107
|
+
const classId = rawClassId ? compactIri(rawClassId) : '';
|
|
108
|
+
const classLabel = deriveClassLabel(rawClassId);
|
|
109
|
+
const label = prefLabel?.value ?? rdfsLabel ?? deriveSubjectLabel(subjectStr);
|
|
110
|
+
|
|
111
|
+
// Group predicates (preserve insertion order). Compact IRIs to CURIEs
|
|
112
|
+
// for display — UI consumers expect 'gloss:hasDefinition' rather than
|
|
113
|
+
// 'https://www.glossarist.org/ontologies/hasDefinition'.
|
|
114
|
+
const props = [];
|
|
115
|
+
const seenPredKeys = new Set();
|
|
116
|
+
|
|
117
|
+
for (const q of otherQuads) {
|
|
118
|
+
const isBnodeObj = q.object.termType === 'BlankNode';
|
|
119
|
+
const formatted = formatTerm(q.object, bnodeQuads);
|
|
120
|
+
if (formatted === '') continue;
|
|
121
|
+
const predicate = compactIri(q.predicate.value);
|
|
122
|
+
const dedupKey = `${predicate}#${isBnodeObj ? 'n' : 'f'}#${formatted}`;
|
|
123
|
+
if (seenPredKeys.has(dedupKey)) continue;
|
|
124
|
+
seenPredKeys.add(dedupKey);
|
|
125
|
+
|
|
126
|
+
const prop = { predicate, values: [formatted] };
|
|
127
|
+
if (isBnodeObj) prop.nested = true;
|
|
128
|
+
props.push(prop);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
classId,
|
|
133
|
+
classLabel,
|
|
134
|
+
label,
|
|
135
|
+
props,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function formatTerm(term, bnodeQuads) {
|
|
140
|
+
switch (term.termType) {
|
|
141
|
+
case 'NamedNode':
|
|
142
|
+
return compactIri(term.value);
|
|
143
|
+
case 'Literal':
|
|
144
|
+
return term.value;
|
|
145
|
+
case 'BlankNode': {
|
|
146
|
+
const entry = bnodeQuads.get(term.value);
|
|
147
|
+
if (!entry) return '';
|
|
148
|
+
const parts = entry.quads.map(q => {
|
|
149
|
+
const val = formatTerm(q.object, bnodeQuads);
|
|
150
|
+
return val ? `${compactIri(q.predicate.value)}: ${val}` : '';
|
|
151
|
+
}).filter(p => p);
|
|
152
|
+
return parts.join('; ');
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function deriveClassLabel(classId) {
|
|
160
|
+
if (!classId) return '';
|
|
161
|
+
// ClassId is now CURIE form (compacted). Just take the local part.
|
|
162
|
+
if (classId.includes(':') && !classId.includes('://')) {
|
|
163
|
+
return classId.slice(classId.indexOf(':') + 1);
|
|
164
|
+
}
|
|
165
|
+
// Fallback for absolute IRIs that didn't compact
|
|
166
|
+
const noFrag = classId.split('#').pop();
|
|
167
|
+
const last = noFrag.split('/').pop();
|
|
168
|
+
return last || classId;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function deriveSubjectLabel(subject) {
|
|
172
|
+
if (!subject) return '';
|
|
173
|
+
// Last path segment or fragment
|
|
174
|
+
const noFrag = subject.split('#').pop();
|
|
175
|
+
const last = noFrag.split('/').pop();
|
|
176
|
+
return last || subject;
|
|
177
|
+
}
|
package/src/rdf/shacl.js
CHANGED
|
@@ -76,9 +76,14 @@ export async function loadShapes({ shapesPath = SHAPES_PATH } = {}) {
|
|
|
76
76
|
// Options:
|
|
77
77
|
// - shapes: a pre-loaded Dataset to use instead of the default shapes
|
|
78
78
|
// - shapesPath: alternative path to load shapes from (cached per path)
|
|
79
|
+
//
|
|
80
|
+
// Note: rdf-validate-shacl 0.6+ requires a factory with clownface. The
|
|
81
|
+
// library's default env (from defaultEnv.js) provides one — we no longer
|
|
82
|
+
// pass a custom factory. Callers still control Dataset creation via
|
|
83
|
+
// quadsToDataset below.
|
|
79
84
|
export async function validateShacl(dataDataset, { shapes, shapesPath } = {}) {
|
|
80
85
|
const shapesDataset = shapes ?? await loadShapes({ shapesPath });
|
|
81
|
-
const validator = new ShaclValidatorCtor(shapesDataset
|
|
86
|
+
const validator = new ShaclValidatorCtor(shapesDataset);
|
|
82
87
|
return validator.validate(dataDataset);
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
// - prov:wasAssociatedWith (optional agent)
|
|
10
10
|
|
|
11
11
|
import { namedNode, literal, quad } from './terms.js';
|
|
12
|
+
import { PREFIXES } from './predicates.js';
|
|
13
|
+
import { RDF_TYPE } from './curie.js';
|
|
12
14
|
|
|
13
|
-
const PROV_NS
|
|
14
|
-
const DCTERMS_NS =
|
|
15
|
-
const XSD_NS
|
|
16
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
15
|
+
const PROV_NS = PREFIXES.prov;
|
|
16
|
+
const DCTERMS_NS = PREFIXES.dcterms;
|
|
17
|
+
const XSD_NS = PREFIXES.xsd;
|
|
17
18
|
|
|
18
19
|
const PROV = {
|
|
19
20
|
Entity: `${PROV_NS}Entity`,
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import { namedNode, literal, quad } from './terms.js';
|
|
16
16
|
import { PREFIXES } from './predicates.js';
|
|
17
|
+
import { resolveIri, RDF_TYPE, RDFS_LABEL } from './curie.js';
|
|
17
18
|
|
|
18
|
-
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
19
|
-
const RDF_LABEL = 'http://www.w3.org/2000/01/rdf-schema#label';
|
|
20
19
|
const SKOS_NS = PREFIXES.skos;
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -31,22 +30,6 @@ const SKOS_NS = PREFIXES.skos;
|
|
|
31
30
|
* @property {readonly VocabTerm[]} terms
|
|
32
31
|
*/
|
|
33
32
|
|
|
34
|
-
/**
|
|
35
|
-
* Resolves a CURIE (e.g. "gloss:status/valid") to an absolute IRI
|
|
36
|
-
* using PREFIXES. Pass-through if already absolute.
|
|
37
|
-
*/
|
|
38
|
-
function resolveIri(iri) {
|
|
39
|
-
if (iri.startsWith('http://') || iri.startsWith('https://') || iri.startsWith('urn:')) {
|
|
40
|
-
return iri;
|
|
41
|
-
}
|
|
42
|
-
const colonIdx = iri.indexOf(':');
|
|
43
|
-
if (colonIdx < 0) return iri;
|
|
44
|
-
const prefix = iri.slice(0, colonIdx);
|
|
45
|
-
const local = iri.slice(colonIdx + 1);
|
|
46
|
-
const base = PREFIXES[prefix];
|
|
47
|
-
return base ? `${base}${local}` : iri;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
33
|
/**
|
|
51
34
|
* Emits SKOS ConceptScheme + Concept quads for one scheme.
|
|
52
35
|
*
|
|
@@ -58,14 +41,14 @@ export function* vocabularySchemeToQuads(scheme) {
|
|
|
58
41
|
const schemeSubject = namedNode(schemeIri);
|
|
59
42
|
|
|
60
43
|
yield quad(schemeSubject, namedNode(RDF_TYPE), namedNode(`${SKOS_NS}ConceptScheme`));
|
|
61
|
-
yield quad(schemeSubject, namedNode(
|
|
44
|
+
yield quad(schemeSubject, namedNode(RDFS_LABEL), literal(scheme.label, 'en'));
|
|
62
45
|
|
|
63
46
|
for (const term of scheme.terms ?? []) {
|
|
64
47
|
const termIri = resolveIri(term.iri);
|
|
65
48
|
const termSubject = namedNode(termIri);
|
|
66
49
|
|
|
67
50
|
yield quad(termSubject, namedNode(RDF_TYPE), namedNode(`${SKOS_NS}Concept`));
|
|
68
|
-
yield quad(termSubject, namedNode(
|
|
51
|
+
yield quad(termSubject, namedNode(RDFS_LABEL), literal(term.label, 'en'));
|
|
69
52
|
yield quad(termSubject, namedNode(`${SKOS_NS}inScheme`), schemeSubject);
|
|
70
53
|
yield quad(schemeSubject, namedNode(`${SKOS_NS}hasTopConcept`), termSubject);
|
|
71
54
|
}
|
|
@@ -82,6 +65,3 @@ export function* vocabularyToQuads(schemes) {
|
|
|
82
65
|
yield* vocabularySchemeToQuads(scheme);
|
|
83
66
|
}
|
|
84
67
|
}
|
|
85
|
-
|
|
86
|
-
// Re-exported for callers that want to resolve CURIEs themselves.
|
|
87
|
-
export { resolveIri };
|