glossarist 0.4.18 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/concept-model/shapes/glossarist.shacl.ttl +151 -1
- package/package.json +1 -1
- package/src/concept-diff.js +13 -0
- package/src/concept-parser.js +52 -2
- package/src/concept-serializer.js +11 -0
- package/src/diff/canonical-json.js +31 -0
- package/src/diff/change.js +28 -0
- package/src/diff/collection-diff.js +57 -25
- package/src/diff/concept-diff.js +100 -60
- package/src/diff/diff-patch.js +49 -48
- package/src/diff/diff-renderer.js +34 -0
- package/src/diff/identity.js +22 -0
- package/src/diff/index.d.ts +64 -1
- package/src/diff/index.js +2 -0
- package/src/diff/similarity.js +24 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/migration/partitive-relation-migrator.js +125 -0
- package/src/models/completeness.js +24 -0
- package/src/models/concept-date.js +8 -0
- package/src/models/concept-ref.js +4 -0
- package/src/models/concept-source.js +21 -2
- package/src/models/concept.js +55 -0
- package/src/models/designation.js +15 -0
- package/src/models/detailed-definition.js +11 -0
- package/src/models/enum.js +42 -0
- package/src/models/hyperedge-colors.js +71 -0
- package/src/models/index.d.ts +61 -0
- package/src/models/index.js +39 -0
- package/src/models/localized-concept.js +29 -0
- package/src/models/member-certainty.js +21 -0
- package/src/models/partitive-enumeration.js +23 -0
- package/src/models/partitive-hyperedge.js +175 -0
- package/src/models/partitive-member.js +79 -0
- package/src/models/partitive-relation.js +158 -0
- package/src/models/plurality-marker.js +24 -0
- package/src/models/related-concept.js +38 -1
- package/src/models/relation-categories.js +6 -0
- package/src/models/type-shared-plurality.js +71 -0
- package/src/rdf/gloss-concept.js +26 -0
- package/src/rdf/gloss-hyperedge.js +90 -0
- package/src/rdf/gloss-partitive-relation.js +109 -0
- package/src/rdf/gloss-source.js +1 -1
- package/src/rdf/index.d.ts +37 -0
- package/src/rdf/index.js +2 -0
- package/src/rdf/predicates.d.ts +54 -0
- package/src/rdf/predicates.js +27 -0
- package/src/reference-resolver.js +49 -0
- package/src/validators/binary-has-part-redundancy-rule.js +74 -0
- package/src/validators/external-concept-shape-rule.js +60 -0
- package/src/validators/index.js +19 -1
- package/src/validators/partitive-drift-rule.js +51 -0
- package/src/validators/partitive-hyperedge-rule.js +75 -0
- package/src/validators/partitive-relation-coherence-rule.js +107 -0
- package/src/validators/sourced-from-locality-rule.js +53 -0
|
@@ -25,6 +25,11 @@ export const RELATIONSHIP_TYPES = Object.freeze([
|
|
|
25
25
|
// Associative (ISO 10241-1 / ISO 25964)
|
|
26
26
|
'see', 'related_concept', 'related_concept_broader', 'related_concept_narrower',
|
|
27
27
|
'references',
|
|
28
|
+
// ExternalConcept resolution (Glossarist extension — v2 partitive-relation)
|
|
29
|
+
// Direction: real concept `provides` substance for an external concept;
|
|
30
|
+
// external concept `provided_by` real concept. Stored on the
|
|
31
|
+
// ExternalConcept. See TODO.partitive-relation-v2 item 07.
|
|
32
|
+
'provides', 'provided_by',
|
|
28
33
|
// Comparative (ISO 10241-1)
|
|
29
34
|
'compare', 'contrast',
|
|
30
35
|
// Spatiotemporal (ISO 25964 / TBX)
|
|
@@ -37,12 +42,21 @@ export class RelatedConcept extends GlossaristModel {
|
|
|
37
42
|
constructor(data = {}) {
|
|
38
43
|
super();
|
|
39
44
|
this.type = data.type ?? 'see';
|
|
40
|
-
this.content = data.content
|
|
45
|
+
this.content = normalizeContent(data.content);
|
|
41
46
|
this.ref = data.ref
|
|
42
47
|
? (data.ref instanceof ConceptRef ? data.ref : new ConceptRef(data.ref))
|
|
43
48
|
: null;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
// Convenience reader: returns the first available string value
|
|
52
|
+
// from the localized hash, or null if absent. Mirrors
|
|
53
|
+
// PartitiveHyperedge#contentString.
|
|
54
|
+
get contentString() {
|
|
55
|
+
if (!this.content) return null;
|
|
56
|
+
const values = Object.values(this.content);
|
|
57
|
+
return values.length > 0 ? values[0] : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
toJSON() {
|
|
47
61
|
const obj = { type: this.type };
|
|
48
62
|
if (this.content != null) obj.content = this.content;
|
|
@@ -50,7 +64,30 @@ export class RelatedConcept extends GlossaristModel {
|
|
|
50
64
|
return obj;
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
static identityOf(value) {
|
|
68
|
+
const v = value ?? {};
|
|
69
|
+
const ref = v?.ref;
|
|
70
|
+
return `${v.type ?? ''}|${ref?.source ?? ''}|${ref?.id ?? ''}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
identity() {
|
|
74
|
+
return RelatedConcept.identityOf(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
static fromJSON(data) {
|
|
54
78
|
return new RelatedConcept(data);
|
|
55
79
|
}
|
|
56
80
|
}
|
|
81
|
+
|
|
82
|
+
// Content is a localized string (language → text). Plain strings on
|
|
83
|
+
// input are normalized to `{ default: '...' }` so callers always see
|
|
84
|
+
// the same shape. Mirrors PartitiveHyperedge's normalizeContent.
|
|
85
|
+
function normalizeContent(value) {
|
|
86
|
+
if (value == null) return null;
|
|
87
|
+
if (typeof value === 'string') return { default: value };
|
|
88
|
+
if (typeof value === 'object') {
|
|
89
|
+
const entries = Object.entries(value).filter(([, v]) => typeof v === 'string');
|
|
90
|
+
return entries.length > 0 ? Object.fromEntries(entries) : null;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
@@ -96,6 +96,11 @@ export const RELATION_CATEGORIES = Object.freeze({
|
|
|
96
96
|
// (a version IS a definitional variant of the same concept identity).
|
|
97
97
|
// Renaming aligns glossarist-js with the canonical taxonomy at
|
|
98
98
|
// concept-browser:src/data/taxonomies.json.
|
|
99
|
+
//
|
|
100
|
+
// `provides` / `provided_by` (Glossarist v2 extension) live here too:
|
|
101
|
+
// they record ExternalConcept resolution — a definitional linkage
|
|
102
|
+
// between a placeholder and the concept that fills its substance.
|
|
103
|
+
// See TODO.partitive-relation-v2 item 07.
|
|
99
104
|
definitional: Object.freeze({
|
|
100
105
|
label: 'Definitional',
|
|
101
106
|
description: 'ISO 19135 concept-to-concept relations: definition, part, instance, inheritance, versioning.',
|
|
@@ -107,6 +112,7 @@ export const RELATION_CATEGORIES = Object.freeze({
|
|
|
107
112
|
'inherits', 'inherited_by',
|
|
108
113
|
'has_version', 'version_of',
|
|
109
114
|
'current_version', 'current_version_of',
|
|
115
|
+
'provides', 'provided_by',
|
|
110
116
|
]),
|
|
111
117
|
}),
|
|
112
118
|
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// TypeSharedPlurality — semantic claims encoded by ISO 704's
|
|
2
|
+
// close-set double line and broken line notation, promoted from
|
|
3
|
+
// diagram-notation flags to structured data.
|
|
4
|
+
//
|
|
5
|
+
// is_shared — ISO 704 close-set double line. True when several
|
|
6
|
+
// partitives of a given type are involved.
|
|
7
|
+
// is_uncertain — ISO 704 broken line. True when the type-shared
|
|
8
|
+
// plurality is uncertain (asserted to exist but its
|
|
9
|
+
// boundaries are not firmly established).
|
|
10
|
+
// Defaults to false when omitted.
|
|
11
|
+
// shared_type — Glossarist extension. The type the partitives
|
|
12
|
+
// share, when known. ISO notation does not encode
|
|
13
|
+
// this; left for the reader to infer. Optional even
|
|
14
|
+
// when is_shared is true.
|
|
15
|
+
//
|
|
16
|
+
// A PartitiveRelation has at most one TypeSharedPlurality block.
|
|
17
|
+
// Absent means: no type-shared plurality claim is being made.
|
|
18
|
+
//
|
|
19
|
+
// Replaces the v1 `markers: [double, dashed]` field per
|
|
20
|
+
// TODO.partitive-relation-v2 item 03.
|
|
21
|
+
|
|
22
|
+
import { GlossaristModel } from './base.js';
|
|
23
|
+
import { ConceptRef } from './concept-ref.js';
|
|
24
|
+
|
|
25
|
+
export class TypeSharedPlurality extends GlossaristModel {
|
|
26
|
+
constructor(data = {}) {
|
|
27
|
+
super();
|
|
28
|
+
if (typeof data.isShared !== 'boolean' && typeof data.is_shared !== 'boolean') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'TypeSharedPlurality requires isShared (boolean); got: ' +
|
|
31
|
+
`${JSON.stringify(data.isShared ?? data.is_shared)}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
this.isShared = data.isShared ?? data.is_shared;
|
|
35
|
+
this.isUncertain = data.isUncertain ?? data.is_uncertain ?? false;
|
|
36
|
+
this.sharedType = data.sharedType
|
|
37
|
+
? (data.sharedType instanceof ConceptRef ? data.sharedType : new ConceptRef(data.sharedType))
|
|
38
|
+
: null;
|
|
39
|
+
|
|
40
|
+
_validate(this);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hasSharedType() {
|
|
44
|
+
return this.sharedType != null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toJSON() {
|
|
48
|
+
const obj = { is_shared: this.isShared };
|
|
49
|
+
if (this.isUncertain) obj.is_uncertain = this.isUncertain;
|
|
50
|
+
if (this.sharedType != null) obj.shared_type = this.sharedType.toJSON();
|
|
51
|
+
return obj;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static fromJSON(data) {
|
|
55
|
+
return new TypeSharedPlurality(data);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _validate(plurality) {
|
|
60
|
+
if (typeof plurality.isShared !== 'boolean') {
|
|
61
|
+
throw new Error('TypeSharedPlurality.isShared must be boolean');
|
|
62
|
+
}
|
|
63
|
+
if (typeof plurality.isUncertain !== 'boolean') {
|
|
64
|
+
throw new Error('TypeSharedPlurality.isUncertain must be boolean');
|
|
65
|
+
}
|
|
66
|
+
// ISO 704 semantic check: a broken line qualifies the close-set
|
|
67
|
+
// double-line plurality claim. is_uncertain without is_shared is
|
|
68
|
+
// semantically odd. The model accepts it (data is data); the
|
|
69
|
+
// PartitiveRelationCoherenceRule surfaces it as a warning so
|
|
70
|
+
// reviewers can decide whether the data shape is intentional.
|
|
71
|
+
}
|
package/src/rdf/gloss-concept.js
CHANGED
|
@@ -7,6 +7,8 @@ import { PRED } from './predicates.js';
|
|
|
7
7
|
import { WELL_KNOWN } from './prefixes.js';
|
|
8
8
|
import { localizedConceptToQuads } from './gloss-localized-concept.js';
|
|
9
9
|
import { conceptSourceToQuads } from './gloss-source.js';
|
|
10
|
+
import { partitiveRelationToQuads, partitiveRelationSubjectUri } from './gloss-partitive-relation.js';
|
|
11
|
+
import { hyperedgeToQuads, hyperedgeSubjectUri } from './gloss-hyperedge.js';
|
|
10
12
|
import { namedNode, literal, quad } from './terms.js';
|
|
11
13
|
|
|
12
14
|
// Kind tag → URI segment. Each NonVerbalReference subtype has a
|
|
@@ -69,4 +71,28 @@ export function* conceptToQuads(concept, options) {
|
|
|
69
71
|
yield* conceptSourceToQuads(source, { subjectUri, index: srcIndex });
|
|
70
72
|
srcIndex += 1;
|
|
71
73
|
}
|
|
74
|
+
|
|
75
|
+
// v2 canonical: concept.partitiveRelations. v1 alias
|
|
76
|
+
// (concept.partitiveHyperedges) points at the same array — both
|
|
77
|
+
// work here.
|
|
78
|
+
const relations = concept.partitiveRelations ?? concept.partitiveHyperedges ?? [];
|
|
79
|
+
let relIdx = 0;
|
|
80
|
+
for (const relation of relations) {
|
|
81
|
+
// Skip v1 PartitiveHyperedge instances — they have their own
|
|
82
|
+
// emitter (gloss-hyperedge.js) and would double-emit. The parser
|
|
83
|
+
// always produces v2 PartitiveRelation instances; only direct
|
|
84
|
+
// callers using the v1 model would see this branch.
|
|
85
|
+
const isV2 = !relation.enumeration && !relation.markers && !relation.parts
|
|
86
|
+
|| (relation.completeness != null || relation.partitives != null);
|
|
87
|
+
if (!isV2) {
|
|
88
|
+
const heSubject = hyperedgeSubjectUri(subjectUri, relation, relIdx);
|
|
89
|
+
yield quad(s, namedNode(PRED.gloss.hasPartitiveHyperedge), namedNode(heSubject));
|
|
90
|
+
yield* hyperedgeToQuads(relation, { parentUri: subjectUri, index: relIdx });
|
|
91
|
+
} else {
|
|
92
|
+
const relSubject = partitiveRelationSubjectUri(subjectUri, relation, relIdx);
|
|
93
|
+
yield quad(s, namedNode(PRED.gloss.hasPartitiveRelation), namedNode(relSubject));
|
|
94
|
+
yield* partitiveRelationToQuads(relation, { parentUri: subjectUri, index: relIdx });
|
|
95
|
+
}
|
|
96
|
+
relIdx += 1;
|
|
97
|
+
}
|
|
72
98
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// PartitiveHyperedge → RDF quads.
|
|
2
|
+
//
|
|
3
|
+
// Shape (per concept-model ontology, v3.1.0):
|
|
4
|
+
// <base>/hyperedge/<carrying-id>/<comprehensive-id>
|
|
5
|
+
// rdf:type gloss:PartitiveHyperedge
|
|
6
|
+
// gloss:comprehensive <base>/concept/<comprehensive-id>
|
|
7
|
+
// gloss:hasPart <base>/concept/<part-id> (one per part)
|
|
8
|
+
// gloss:enumeration "closed"|"open" (literal)
|
|
9
|
+
// gloss:hasPluralityMarker "double"|"dashed" (literal, one per marker)
|
|
10
|
+
// gloss:hyperedgeContent "..." (literal, omitted if absent)
|
|
11
|
+
//
|
|
12
|
+
// The carrying concept links to this hyperedge via `gloss:hasPartitiveHyperedge`
|
|
13
|
+
// (emitted by gloss-concept.js#conceptToQuads).
|
|
14
|
+
//
|
|
15
|
+
// CROSS-REPO STATUS (validated 2026-07-22):
|
|
16
|
+
// - Subject URI scheme: matches Ruby (`hyperedge/<c>/<comp>`).
|
|
17
|
+
// - `comprehensive`, `enumeration`, `hasPluralityMarker`, `hyperedgeContent`:
|
|
18
|
+
// predicates + object types match Ruby.
|
|
19
|
+
// - `hasPart`: JS follows the concept-model ontology; Ruby emits
|
|
20
|
+
// `gloss:part` instead. This is a Ruby bug — tracked as a cross-repo
|
|
21
|
+
// follow-up. Once Ruby aligns with the ontology, output will be
|
|
22
|
+
// byte-equivalent (modulo serialization order).
|
|
23
|
+
|
|
24
|
+
import { PRED } from './predicates.js';
|
|
25
|
+
import { WELL_KNOWN } from './prefixes.js';
|
|
26
|
+
import { namedNode, literal, quad } from './terms.js';
|
|
27
|
+
|
|
28
|
+
export function* hyperedgeToQuads(hyperedge, { parentUri, index }) {
|
|
29
|
+
const heSubject = hyperedgeSubjectUri(parentUri, hyperedge, index);
|
|
30
|
+
const s = namedNode(heSubject);
|
|
31
|
+
|
|
32
|
+
yield quad(s, namedNode(WELL_KNOWN.rdfType), namedNode(PRED.gloss.PartitiveHyperedge));
|
|
33
|
+
|
|
34
|
+
const comp = hyperedge.comprehensive;
|
|
35
|
+
if (comp && (comp.source || comp.id)) {
|
|
36
|
+
yield quad(s, namedNode(PRED.gloss.comprehensive),
|
|
37
|
+
namedNode(conceptRefUri(comp, parentUri)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parts = Array.isArray(hyperedge.parts) ? hyperedge.parts : [];
|
|
41
|
+
for (const p of parts) {
|
|
42
|
+
if (p && (p.source || p.id)) {
|
|
43
|
+
yield quad(s, namedNode(PRED.gloss.hasPart),
|
|
44
|
+
namedNode(conceptRefUri(p, parentUri)));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (hyperedge.enumeration) {
|
|
49
|
+
yield quad(s, namedNode(PRED.gloss.enumeration), literal(hyperedge.enumeration));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const marker of Array.isArray(hyperedge.markers) ? hyperedge.markers : []) {
|
|
53
|
+
yield quad(s, namedNode(PRED.gloss.hasPluralityMarker), literal(marker));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (hyperedge.content) {
|
|
57
|
+
const content = hyperedge.content;
|
|
58
|
+
if (typeof content === 'string') {
|
|
59
|
+
yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(content));
|
|
60
|
+
} else if (typeof content === 'object') {
|
|
61
|
+
for (const [lang, text] of Object.entries(content)) {
|
|
62
|
+
if (typeof text !== 'string') continue;
|
|
63
|
+
yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(text, lang));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Subject URI scheme: `<base>/hyperedge/<carrying-id>/<comprehensive-id>`.
|
|
70
|
+
// Matches Ruby's `subject { |h| "hyperedge/#{h.identifier}/#{h.comprehensive_id}" }`.
|
|
71
|
+
// Falls back to an index suffix if comprehensive has no id, so two
|
|
72
|
+
// hyperedges on the same concept never collide.
|
|
73
|
+
export function hyperedgeSubjectUri(parentUri, hyperedge, index) {
|
|
74
|
+
const base = parentUri.replace(/\/concept\/[^/]+$/, '');
|
|
75
|
+
const carrierId = parentUri.split('/concept/')[1] ?? `carrier-${index}`;
|
|
76
|
+
const compId = hyperedge?.comprehensive?.id ?? `comp-${index}`;
|
|
77
|
+
return `${base}/hyperedge/${carrierId}/${compId}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ConceptRef URI: `<base>/concept/<id>`. Matches Ruby's
|
|
81
|
+
// `concept_to_gloss_transform.rb#hyperedge_concept_uri`. Source is
|
|
82
|
+
// dropped from the URI; it stays on the ConceptRef object if needed.
|
|
83
|
+
function conceptRefUri(ref, parentUri) {
|
|
84
|
+
const id = ref?.id;
|
|
85
|
+
if (id) {
|
|
86
|
+
const base = parentUri.split('/concept/')[0];
|
|
87
|
+
return `${base}/concept/${id}`;
|
|
88
|
+
}
|
|
89
|
+
return `urn:glossarist:${ref?.source ?? 'unknown'}:`;
|
|
90
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// PartitiveRelation → RDF quads.
|
|
2
|
+
//
|
|
3
|
+
// Shape (per concept-model ontology, v3.2.0):
|
|
4
|
+
//
|
|
5
|
+
// <base>/partitive-relation/<carrying-id>/<comprehensive-id>
|
|
6
|
+
// rdf:type gloss:PartitiveRelation
|
|
7
|
+
// gloss:comprehensive <base>/concept/<comprehensive-id> (named node)
|
|
8
|
+
// gloss:hasPartitive <base>/concept/<partitive-id> (named node, one per member)
|
|
9
|
+
// gloss:completeness "complete"|"partial" (literal)
|
|
10
|
+
// gloss:hasPlurality <plurality bnode> (when plurality present)
|
|
11
|
+
// gloss:criterion "..."@lang (one per language, when present)
|
|
12
|
+
//
|
|
13
|
+
// Plurality bnode (when present):
|
|
14
|
+
//
|
|
15
|
+
// _:b<n>
|
|
16
|
+
// rdf:type gloss:TypeSharedPlurality
|
|
17
|
+
// gloss:isShared true|false (literal)
|
|
18
|
+
// gloss:isUncertain true|false (literal, when non-default)
|
|
19
|
+
// gloss:sharedType <base>/concept/<shared-type-id> (when shared_type present)
|
|
20
|
+
//
|
|
21
|
+
// The carrying concept links via `gloss:hasPartitiveRelation`.
|
|
22
|
+
//
|
|
23
|
+
// Replaces gloss-hyperedge.js (the v1 emitter) per TODO.partitive-relation-v2 item 01.
|
|
24
|
+
// Cross-repo alignment with glossarist-ruby's Rdf::GlossPartitiveRelation.
|
|
25
|
+
|
|
26
|
+
import { PRED } from './predicates.js';
|
|
27
|
+
import { WELL_KNOWN } from './prefixes.js';
|
|
28
|
+
import { deterministicBnode } from './deterministic-id.js';
|
|
29
|
+
import { namedNode, literal, quad } from './terms.js';
|
|
30
|
+
|
|
31
|
+
export function* partitiveRelationToQuads(relation, { parentUri, index }) {
|
|
32
|
+
const subjectUri = partitiveRelationSubjectUri(parentUri, relation, index);
|
|
33
|
+
const s = namedNode(subjectUri);
|
|
34
|
+
|
|
35
|
+
yield quad(s, namedNode(WELL_KNOWN.rdfType), namedNode(PRED.gloss.PartitiveRelation));
|
|
36
|
+
|
|
37
|
+
const comp = relation.comprehensive;
|
|
38
|
+
if (comp && (comp.source || comp.id)) {
|
|
39
|
+
yield quad(s, namedNode(PRED.gloss.comprehensive),
|
|
40
|
+
namedNode(conceptRefUri(comp, parentUri)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const members = Array.isArray(relation.partitives) ? relation.partitives : [];
|
|
44
|
+
for (const member of members) {
|
|
45
|
+
const ref = member?.ref ?? member;
|
|
46
|
+
if (!ref || (!ref.source && !ref.id)) continue;
|
|
47
|
+
yield quad(s, namedNode(PRED.gloss.hasPartitive),
|
|
48
|
+
namedNode(conceptRefUri(ref, parentUri)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (relation.completeness) {
|
|
52
|
+
yield quad(s, namedNode(PRED.gloss.completeness), literal(relation.completeness));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (relation.plurality != null) {
|
|
56
|
+
const pSubject = deterministicBnode(subjectUri, 'plurality', 0);
|
|
57
|
+
yield quad(s, namedNode(PRED.gloss.hasPlurality), namedNode(pSubject));
|
|
58
|
+
yield* pluralityToQuads(relation.plurality, pSubject);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (relation.criterion && typeof relation.criterion === 'object') {
|
|
62
|
+
for (const [lang, text] of Object.entries(relation.criterion)) {
|
|
63
|
+
if (typeof text !== 'string') continue;
|
|
64
|
+
yield quad(s, namedNode(PRED.gloss.criterion), literal(text, lang));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function* pluralityToQuads(plurality, subjectUri) {
|
|
70
|
+
const s = namedNode(subjectUri);
|
|
71
|
+
yield quad(s, namedNode(WELL_KNOWN.rdfType), namedNode(PRED.gloss.TypeSharedPlurality));
|
|
72
|
+
yield quad(s, namedNode(PRED.gloss.isShared), literal(plurality.isShared ? 'true' : 'false', 'xsd:boolean'));
|
|
73
|
+
if (plurality.isUncertain) {
|
|
74
|
+
yield quad(s, namedNode(PRED.gloss.isUncertain), literal('true', 'xsd:boolean'));
|
|
75
|
+
}
|
|
76
|
+
if (plurality.sharedType) {
|
|
77
|
+
// sharedType is a ConceptRef-like object; emit as a concept URI.
|
|
78
|
+
// We don't have parentUri here, but sharedType typically points at
|
|
79
|
+
// an external concept. Fall back to urn form.
|
|
80
|
+
const st = plurality.sharedType;
|
|
81
|
+
const uri = st.id
|
|
82
|
+
? `urn:glossarist:${st.source ?? 'unknown'}:${st.id}`
|
|
83
|
+
: `urn:glossarist:${st.source ?? 'unknown'}:`;
|
|
84
|
+
yield quad(s, namedNode(PRED.gloss.sharedType), namedNode(uri));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Subject URI scheme: `<base>/partitive-relation/<carrying-id>/<comprehensive-id>`.
|
|
89
|
+
// Matches the Ruby emitter's scheme. Falls back to an index suffix if
|
|
90
|
+
// the comprehensive has no id, so two relations on the same concept
|
|
91
|
+
// never collide.
|
|
92
|
+
export function partitiveRelationSubjectUri(parentUri, relation, index) {
|
|
93
|
+
const base = parentUri.replace(/\/concept\/[^/]+$/, '');
|
|
94
|
+
const carrierId = parentUri.split('/concept/')[1] ?? `carrier-${index}`;
|
|
95
|
+
const compId = relation?.comprehensive?.id ?? `comp-${index}`;
|
|
96
|
+
return `${base}/partitive-relation/${carrierId}/${compId}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ConceptRef URI resolution: `<base>/concept/<id>` for any ref with an
|
|
100
|
+
// id. Source is dropped (matches Ruby). Falls back to a stable urn:/
|
|
101
|
+
// form for refs with only a source (rare).
|
|
102
|
+
function conceptRefUri(ref, parentUri) {
|
|
103
|
+
const id = ref?.id;
|
|
104
|
+
if (id) {
|
|
105
|
+
const base = parentUri.split('/concept/')[0];
|
|
106
|
+
return `${base}/concept/${id}`;
|
|
107
|
+
}
|
|
108
|
+
return `urn:glossarist:${ref?.source ?? 'unknown'}:`;
|
|
109
|
+
}
|
package/src/rdf/gloss-source.js
CHANGED
|
@@ -30,7 +30,7 @@ export function* conceptSourceToQuads(source, { subjectUri, index }) {
|
|
|
30
30
|
yield* citationToQuads(source.origin, { subjectUri: srcSubject, predicate: PRED.gloss.sourceOrigin, bnodeKey: 'origin' });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const sourcedFrom = source.sourced_from ?? [];
|
|
33
|
+
const sourcedFrom = source.sourcedFrom ?? source.sourced_from ?? [];
|
|
34
34
|
for (let i = 0; i < sourcedFrom.length; i++) {
|
|
35
35
|
yield* citationToQuads(sourcedFrom[i], { subjectUri: srcSubject, predicate: PRED.gloss.sourcedFrom, bnodeKey: 'sourced_from', index: i });
|
|
36
36
|
}
|
package/src/rdf/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Designation } from '../models/index';
|
|
|
11
11
|
import type { DetailedDefinition } from '../models/index';
|
|
12
12
|
import type { ConceptSource } from '../models/index';
|
|
13
13
|
import type { NonVerbRep, Figure, Table, Formula } from '../models/index';
|
|
14
|
+
import type { PartitiveHyperedge, PartitiveRelation } from '../models/index';
|
|
14
15
|
|
|
15
16
|
export { PRED, PREFIXES } from './predicates';
|
|
16
17
|
export type PredicateMap = typeof PRED;
|
|
@@ -77,6 +78,42 @@ export interface ConceptSourceEmitOptions {
|
|
|
77
78
|
|
|
78
79
|
export declare function conceptSourceToQuads(source: ConceptSource, options: ConceptSourceEmitOptions): Generator<Quad, void, unknown>;
|
|
79
80
|
|
|
81
|
+
// ── Hyperedge emitter ─────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
export interface HyperedgeEmitOptions {
|
|
84
|
+
parentUri: string;
|
|
85
|
+
index: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export declare function hyperedgeToQuads(
|
|
89
|
+
hyperedge: PartitiveHyperedge,
|
|
90
|
+
options: HyperedgeEmitOptions,
|
|
91
|
+
): Generator<Quad, void, unknown>;
|
|
92
|
+
|
|
93
|
+
export declare function hyperedgeSubjectUri(
|
|
94
|
+
parentUri: string,
|
|
95
|
+
hyperedge: PartitiveHyperedge,
|
|
96
|
+
index: number,
|
|
97
|
+
): string;
|
|
98
|
+
|
|
99
|
+
// ── PartitiveRelation emitter (v2) ────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export interface PartitiveRelationEmitOptions {
|
|
102
|
+
parentUri: string;
|
|
103
|
+
index: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export declare function partitiveRelationToQuads(
|
|
107
|
+
relation: PartitiveRelation,
|
|
108
|
+
options: PartitiveRelationEmitOptions,
|
|
109
|
+
): Generator<Quad, void, unknown>;
|
|
110
|
+
|
|
111
|
+
export declare function partitiveRelationSubjectUri(
|
|
112
|
+
parentUri: string,
|
|
113
|
+
relation: PartitiveRelation,
|
|
114
|
+
index: number,
|
|
115
|
+
): string;
|
|
116
|
+
|
|
80
117
|
// ── Non-verbal representation emitters ─────────────────────────────────
|
|
81
118
|
|
|
82
119
|
export interface NonVerbalRepEmitOptions {
|
package/src/rdf/index.js
CHANGED
|
@@ -26,6 +26,8 @@ export {
|
|
|
26
26
|
} from './gloss-designation.js';
|
|
27
27
|
export { detailedDefinitionToQuads } from './gloss-detailed-definition.js';
|
|
28
28
|
export { conceptSourceToQuads } from './gloss-source.js';
|
|
29
|
+
export { hyperedgeToQuads, hyperedgeSubjectUri } from './gloss-hyperedge.js';
|
|
30
|
+
export { partitiveRelationToQuads, partitiveRelationSubjectUri } from './gloss-partitive-relation.js';
|
|
29
31
|
export {
|
|
30
32
|
nonVerbalRepToQuads,
|
|
31
33
|
nonVerbalEntityToQuads,
|
package/src/rdf/predicates.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export declare const PRED = {
|
|
|
23
23
|
"ConceptRef": "https://www.glossarist.org/ontologies/ConceptRef",
|
|
24
24
|
"Reference": "https://www.glossarist.org/ontologies/Reference",
|
|
25
25
|
"RelatedConcept": "https://www.glossarist.org/ontologies/RelatedConcept",
|
|
26
|
+
"PartitiveHyperedge": "https://www.glossarist.org/ontologies/PartitiveHyperedge",
|
|
27
|
+
"PartitiveEnumeration": "https://www.glossarist.org/ontologies/PartitiveEnumeration",
|
|
28
|
+
"PluralityMarker": "https://www.glossarist.org/ontologies/PluralityMarker",
|
|
26
29
|
"DesignationRelationship": "https://www.glossarist.org/ontologies/DesignationRelationship",
|
|
27
30
|
"ConceptDate": "https://www.glossarist.org/ontologies/ConceptDate",
|
|
28
31
|
"NonVerbalRepresentation": "https://www.glossarist.org/ontologies/NonVerbalRepresentation",
|
|
@@ -42,6 +45,7 @@ export declare const PRED = {
|
|
|
42
45
|
"isLocalizationOf": "https://www.glossarist.org/ontologies/isLocalizationOf",
|
|
43
46
|
"hasStatus": "https://www.glossarist.org/ontologies/hasStatus",
|
|
44
47
|
"hasDomain": "https://www.glossarist.org/ontologies/hasDomain",
|
|
48
|
+
"tag": "https://www.glossarist.org/ontologies/tag",
|
|
45
49
|
"hasRelatedConcept": "https://www.glossarist.org/ontologies/hasRelatedConcept",
|
|
46
50
|
"hasDate": "https://www.glossarist.org/ontologies/hasDate",
|
|
47
51
|
"hasSource": "https://www.glossarist.org/ontologies/hasSource",
|
|
@@ -93,7 +97,30 @@ export declare const PRED = {
|
|
|
93
97
|
"relationshipType": "https://www.glossarist.org/ontologies/relationshipType",
|
|
94
98
|
"relationshipContent": "https://www.glossarist.org/ontologies/relationshipContent",
|
|
95
99
|
"relationshipRef": "https://www.glossarist.org/ontologies/relationshipRef",
|
|
100
|
+
"relatedConceptBroader": "https://www.glossarist.org/ontologies/relatedConceptBroader",
|
|
101
|
+
"relatedConceptNarrower": "https://www.glossarist.org/ontologies/relatedConceptNarrower",
|
|
102
|
+
"hasPartitiveHyperedge": "https://www.glossarist.org/ontologies/hasPartitiveHyperedge",
|
|
103
|
+
"comprehensive": "https://www.glossarist.org/ontologies/comprehensive",
|
|
104
|
+
"hasPart": "https://www.glossarist.org/ontologies/hasPart",
|
|
105
|
+
"enumeration": "https://www.glossarist.org/ontologies/enumeration",
|
|
106
|
+
"hasPluralityMarker": "https://www.glossarist.org/ontologies/hasPluralityMarker",
|
|
107
|
+
"hyperedgeContent": "https://www.glossarist.org/ontologies/hyperedgeContent",
|
|
108
|
+
"PartitiveRelation": "https://www.glossarist.org/ontologies/PartitiveRelation",
|
|
109
|
+
"PartitiveMember": "https://www.glossarist.org/ontologies/PartitiveMember",
|
|
110
|
+
"TypeSharedPlurality": "https://www.glossarist.org/ontologies/TypeSharedPlurality",
|
|
111
|
+
"hasPartitiveRelation": "https://www.glossarist.org/ontologies/hasPartitiveRelation",
|
|
112
|
+
"hasPartitive": "https://www.glossarist.org/ontologies/hasPartitive",
|
|
113
|
+
"completeness": "https://www.glossarist.org/ontologies/completeness",
|
|
114
|
+
"hasPlurality": "https://www.glossarist.org/ontologies/hasPlurality",
|
|
115
|
+
"isShared": "https://www.glossarist.org/ontologies/isShared",
|
|
116
|
+
"isUncertain": "https://www.glossarist.org/ontologies/isUncertain",
|
|
117
|
+
"sharedType": "https://www.glossarist.org/ontologies/sharedType",
|
|
118
|
+
"criterion": "https://www.glossarist.org/ontologies/criterion",
|
|
119
|
+
"certainty": "https://www.glossarist.org/ontologies/certainty",
|
|
120
|
+
"provides": "https://www.glossarist.org/ontologies/provides",
|
|
121
|
+
"providedBy": "https://www.glossarist.org/ontologies/providedBy",
|
|
96
122
|
"refType": "https://www.glossarist.org/ontologies/refType",
|
|
123
|
+
"source": "https://www.glossarist.org/ontologies/source",
|
|
97
124
|
"urn": "https://www.glossarist.org/ontologies/urn",
|
|
98
125
|
"term": "https://www.glossarist.org/ontologies/term",
|
|
99
126
|
"refId": "https://www.glossarist.org/ontologies/refId",
|
|
@@ -282,6 +309,9 @@ export type Predicate =
|
|
|
282
309
|
| "https://www.glossarist.org/ontologies/ConceptRef"
|
|
283
310
|
| "https://www.glossarist.org/ontologies/Reference"
|
|
284
311
|
| "https://www.glossarist.org/ontologies/RelatedConcept"
|
|
312
|
+
| "https://www.glossarist.org/ontologies/PartitiveHyperedge"
|
|
313
|
+
| "https://www.glossarist.org/ontologies/PartitiveEnumeration"
|
|
314
|
+
| "https://www.glossarist.org/ontologies/PluralityMarker"
|
|
285
315
|
| "https://www.glossarist.org/ontologies/DesignationRelationship"
|
|
286
316
|
| "https://www.glossarist.org/ontologies/ConceptDate"
|
|
287
317
|
| "https://www.glossarist.org/ontologies/NonVerbalRepresentation"
|
|
@@ -301,6 +331,7 @@ export type Predicate =
|
|
|
301
331
|
| "https://www.glossarist.org/ontologies/isLocalizationOf"
|
|
302
332
|
| "https://www.glossarist.org/ontologies/hasStatus"
|
|
303
333
|
| "https://www.glossarist.org/ontologies/hasDomain"
|
|
334
|
+
| "https://www.glossarist.org/ontologies/tag"
|
|
304
335
|
| "https://www.glossarist.org/ontologies/hasRelatedConcept"
|
|
305
336
|
| "https://www.glossarist.org/ontologies/hasDate"
|
|
306
337
|
| "https://www.glossarist.org/ontologies/hasSource"
|
|
@@ -352,7 +383,30 @@ export type Predicate =
|
|
|
352
383
|
| "https://www.glossarist.org/ontologies/relationshipType"
|
|
353
384
|
| "https://www.glossarist.org/ontologies/relationshipContent"
|
|
354
385
|
| "https://www.glossarist.org/ontologies/relationshipRef"
|
|
386
|
+
| "https://www.glossarist.org/ontologies/relatedConceptBroader"
|
|
387
|
+
| "https://www.glossarist.org/ontologies/relatedConceptNarrower"
|
|
388
|
+
| "https://www.glossarist.org/ontologies/hasPartitiveHyperedge"
|
|
389
|
+
| "https://www.glossarist.org/ontologies/comprehensive"
|
|
390
|
+
| "https://www.glossarist.org/ontologies/hasPart"
|
|
391
|
+
| "https://www.glossarist.org/ontologies/enumeration"
|
|
392
|
+
| "https://www.glossarist.org/ontologies/hasPluralityMarker"
|
|
393
|
+
| "https://www.glossarist.org/ontologies/hyperedgeContent"
|
|
394
|
+
| "https://www.glossarist.org/ontologies/PartitiveRelation"
|
|
395
|
+
| "https://www.glossarist.org/ontologies/PartitiveMember"
|
|
396
|
+
| "https://www.glossarist.org/ontologies/TypeSharedPlurality"
|
|
397
|
+
| "https://www.glossarist.org/ontologies/hasPartitiveRelation"
|
|
398
|
+
| "https://www.glossarist.org/ontologies/hasPartitive"
|
|
399
|
+
| "https://www.glossarist.org/ontologies/completeness"
|
|
400
|
+
| "https://www.glossarist.org/ontologies/hasPlurality"
|
|
401
|
+
| "https://www.glossarist.org/ontologies/isShared"
|
|
402
|
+
| "https://www.glossarist.org/ontologies/isUncertain"
|
|
403
|
+
| "https://www.glossarist.org/ontologies/sharedType"
|
|
404
|
+
| "https://www.glossarist.org/ontologies/criterion"
|
|
405
|
+
| "https://www.glossarist.org/ontologies/certainty"
|
|
406
|
+
| "https://www.glossarist.org/ontologies/provides"
|
|
407
|
+
| "https://www.glossarist.org/ontologies/providedBy"
|
|
355
408
|
| "https://www.glossarist.org/ontologies/refType"
|
|
409
|
+
| "https://www.glossarist.org/ontologies/source"
|
|
356
410
|
| "https://www.glossarist.org/ontologies/urn"
|
|
357
411
|
| "https://www.glossarist.org/ontologies/term"
|
|
358
412
|
| "https://www.glossarist.org/ontologies/refId"
|
package/src/rdf/predicates.js
CHANGED
|
@@ -23,6 +23,9 @@ export const PRED = {
|
|
|
23
23
|
"ConceptRef": "https://www.glossarist.org/ontologies/ConceptRef",
|
|
24
24
|
"Reference": "https://www.glossarist.org/ontologies/Reference",
|
|
25
25
|
"RelatedConcept": "https://www.glossarist.org/ontologies/RelatedConcept",
|
|
26
|
+
"PartitiveHyperedge": "https://www.glossarist.org/ontologies/PartitiveHyperedge",
|
|
27
|
+
"PartitiveEnumeration": "https://www.glossarist.org/ontologies/PartitiveEnumeration",
|
|
28
|
+
"PluralityMarker": "https://www.glossarist.org/ontologies/PluralityMarker",
|
|
26
29
|
"DesignationRelationship": "https://www.glossarist.org/ontologies/DesignationRelationship",
|
|
27
30
|
"ConceptDate": "https://www.glossarist.org/ontologies/ConceptDate",
|
|
28
31
|
"NonVerbalRepresentation": "https://www.glossarist.org/ontologies/NonVerbalRepresentation",
|
|
@@ -42,6 +45,7 @@ export const PRED = {
|
|
|
42
45
|
"isLocalizationOf": "https://www.glossarist.org/ontologies/isLocalizationOf",
|
|
43
46
|
"hasStatus": "https://www.glossarist.org/ontologies/hasStatus",
|
|
44
47
|
"hasDomain": "https://www.glossarist.org/ontologies/hasDomain",
|
|
48
|
+
"tag": "https://www.glossarist.org/ontologies/tag",
|
|
45
49
|
"hasRelatedConcept": "https://www.glossarist.org/ontologies/hasRelatedConcept",
|
|
46
50
|
"hasDate": "https://www.glossarist.org/ontologies/hasDate",
|
|
47
51
|
"hasSource": "https://www.glossarist.org/ontologies/hasSource",
|
|
@@ -93,7 +97,30 @@ export const PRED = {
|
|
|
93
97
|
"relationshipType": "https://www.glossarist.org/ontologies/relationshipType",
|
|
94
98
|
"relationshipContent": "https://www.glossarist.org/ontologies/relationshipContent",
|
|
95
99
|
"relationshipRef": "https://www.glossarist.org/ontologies/relationshipRef",
|
|
100
|
+
"relatedConceptBroader": "https://www.glossarist.org/ontologies/relatedConceptBroader",
|
|
101
|
+
"relatedConceptNarrower": "https://www.glossarist.org/ontologies/relatedConceptNarrower",
|
|
102
|
+
"hasPartitiveHyperedge": "https://www.glossarist.org/ontologies/hasPartitiveHyperedge",
|
|
103
|
+
"comprehensive": "https://www.glossarist.org/ontologies/comprehensive",
|
|
104
|
+
"hasPart": "https://www.glossarist.org/ontologies/hasPart",
|
|
105
|
+
"enumeration": "https://www.glossarist.org/ontologies/enumeration",
|
|
106
|
+
"hasPluralityMarker": "https://www.glossarist.org/ontologies/hasPluralityMarker",
|
|
107
|
+
"hyperedgeContent": "https://www.glossarist.org/ontologies/hyperedgeContent",
|
|
108
|
+
"PartitiveRelation": "https://www.glossarist.org/ontologies/PartitiveRelation",
|
|
109
|
+
"PartitiveMember": "https://www.glossarist.org/ontologies/PartitiveMember",
|
|
110
|
+
"TypeSharedPlurality": "https://www.glossarist.org/ontologies/TypeSharedPlurality",
|
|
111
|
+
"hasPartitiveRelation": "https://www.glossarist.org/ontologies/hasPartitiveRelation",
|
|
112
|
+
"hasPartitive": "https://www.glossarist.org/ontologies/hasPartitive",
|
|
113
|
+
"completeness": "https://www.glossarist.org/ontologies/completeness",
|
|
114
|
+
"hasPlurality": "https://www.glossarist.org/ontologies/hasPlurality",
|
|
115
|
+
"isShared": "https://www.glossarist.org/ontologies/isShared",
|
|
116
|
+
"isUncertain": "https://www.glossarist.org/ontologies/isUncertain",
|
|
117
|
+
"sharedType": "https://www.glossarist.org/ontologies/sharedType",
|
|
118
|
+
"criterion": "https://www.glossarist.org/ontologies/criterion",
|
|
119
|
+
"certainty": "https://www.glossarist.org/ontologies/certainty",
|
|
120
|
+
"provides": "https://www.glossarist.org/ontologies/provides",
|
|
121
|
+
"providedBy": "https://www.glossarist.org/ontologies/providedBy",
|
|
96
122
|
"refType": "https://www.glossarist.org/ontologies/refType",
|
|
123
|
+
"source": "https://www.glossarist.org/ontologies/source",
|
|
97
124
|
"urn": "https://www.glossarist.org/ontologies/urn",
|
|
98
125
|
"term": "https://www.glossarist.org/ontologies/term",
|
|
99
126
|
"refId": "https://www.glossarist.org/ontologies/refId",
|