glossarist 0.4.42 → 0.4.44
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 +1 -1
- package/src/{compiled-format.js → compiled-format.ts} +1 -0
- package/src/{concept-collection.js → concept-collection.ts} +1 -0
- package/src/{concept-diff.js → concept-diff.ts} +1 -0
- package/src/{concept-parser.js → concept-parser.ts} +1 -0
- package/src/{concept-reader.js → concept-reader.ts} +1 -0
- package/src/{concept-serializer.js → concept-serializer.ts} +1 -0
- package/src/{concept-writer.js → concept-writer.ts} +1 -0
- package/src/{dataset-asset.js → dataset-asset.ts} +1 -0
- package/src/diff/change.ts +175 -0
- package/src/diff/{collection-diff.js → collection-diff.ts} +1 -0
- package/src/diff/{concept-diff.js → concept-diff.ts} +1 -0
- package/src/diff/{diff-patch.js → diff-patch.ts} +1 -0
- package/src/diff/{diff-renderer.js → diff-renderer.ts} +1 -0
- package/src/diff/{index.js → index.ts} +1 -0
- package/src/diff/list-diff.ts +231 -0
- package/src/{entity-directory.js → entity-directory.ts} +1 -0
- package/src/errors.ts +24 -0
- package/src/{gcr-reader.js → gcr-reader.ts} +1 -0
- package/src/{gcr-writer.js → gcr-writer.ts} +1 -0
- package/src/{index.js → index.ts} +1 -0
- package/src/{managed-concept-collection.js → managed-concept-collection.ts} +1 -0
- package/src/migration/hyperedge-migrator.ts +104 -0
- package/src/models/concept.ts +1 -1
- package/src/rdf/{agents-emitter.js → agents-emitter.ts} +1 -0
- package/src/rdf/{bibliography-emitter.js → bibliography-emitter.ts} +1 -0
- package/src/rdf/{bnode-id.js → bnode-id.ts} +1 -0
- package/src/rdf/{build-activity-emitter.js → build-activity-emitter.ts} +1 -0
- package/src/rdf/{curie.js → curie.ts} +1 -0
- package/src/rdf/{dataset-emitter.js → dataset-emitter.ts} +1 -0
- package/src/rdf/{deterministic-id.js → deterministic-id.ts} +1 -0
- package/src/rdf/{document-writer.js → document-writer.ts} +1 -0
- package/src/rdf/{gloss-concept.js → gloss-concept.ts} +1 -0
- package/src/rdf/{gloss-designation.js → gloss-designation.ts} +1 -0
- package/src/rdf/{gloss-detailed-definition.js → gloss-detailed-definition.ts} +1 -0
- package/src/rdf/{gloss-generic-hyperedge.js → gloss-generic-hyperedge.ts} +1 -0
- package/src/rdf/{gloss-localized-concept.js → gloss-localized-concept.ts} +1 -0
- package/src/rdf/{gloss-non-verbal-rep.js → gloss-non-verbal-rep.ts} +1 -0
- package/src/rdf/{gloss-partitive-member.js → gloss-partitive-member.ts} +1 -0
- package/src/rdf/{gloss-partitive-relation.js → gloss-partitive-relation.ts} +1 -0
- package/src/rdf/{gloss-source.js → gloss-source.ts} +1 -0
- package/src/rdf/{group-emitter.js → group-emitter.ts} +1 -0
- package/src/rdf/{image-variant-emitter.js → image-variant-emitter.ts} +1 -0
- package/src/rdf/{index.js → index.ts} +1 -0
- package/src/rdf/{normalize-enum.js → normalize-enum.ts} +1 -0
- package/src/rdf/{prefixes.js → prefixes.ts} +1 -0
- package/src/rdf/{provenance-emitter.js → provenance-emitter.ts} +1 -0
- package/src/rdf/{sections-builder.js → sections-builder.ts} +1 -0
- package/src/rdf/{shacl-entry.js → shacl-entry.ts} +1 -0
- package/src/rdf/{shacl.js → shacl.ts} +1 -0
- package/src/rdf/{terms.js → terms.ts} +1 -0
- package/src/rdf/{version-emitter.js → version-emitter.ts} +1 -0
- package/src/rdf/{vocabulary-emitter.js → vocabulary-emitter.ts} +1 -0
- package/src/rdf/{write-turtle-sync.js → write-turtle-sync.ts} +1 -0
- package/src/{reference-mention.js → reference-mention.ts} +1 -0
- package/src/{reference-resolver.js → reference-resolver.ts} +1 -0
- package/src/{render-classification.js → render-classification.ts} +1 -0
- package/src/{sort.js → sort.ts} +1 -4
- package/src/transforms/concept-to-gloss.transform.ts +81 -0
- package/src/{uuid.js → uuid.ts} +11 -7
- package/src/{v1-reader.js → v1-reader.ts} +1 -0
- package/src/validators/{asset-index.js → asset-index.ts} +1 -0
- package/src/validators/{binary-has-part-redundancy-rule.js → binary-has-part-redundancy-rule.ts} +1 -0
- package/src/validators/{concept-validator.js → concept-validator.ts} +1 -0
- package/src/validators/{delimiting-coherence-rule.js → delimiting-coherence-rule.ts} +1 -0
- package/src/validators/{external-concept-shape-rule.js → external-concept-shape-rule.ts} +1 -0
- package/src/validators/{gcr-validator.js → gcr-validator.ts} +1 -0
- package/src/validators/{hyperedge-coherence-rule.js → hyperedge-coherence-rule.ts} +1 -0
- package/src/validators/{index.js → index.ts} +1 -0
- package/src/validators/{partitive-relation-coherence-rule.js → partitive-relation-coherence-rule.ts} +1 -0
- package/src/validators/{ref-keys.js → ref-keys.ts} +1 -0
- package/src/validators/{register-validator.js → register-validator.ts} +1 -0
- package/src/validators/{relationship-type-rule.js → relationship-type-rule.ts} +1 -0
- package/src/validators/{sourced-from-locality-rule.js → sourced-from-locality-rule.ts} +1 -0
- package/src/validators/{v3-rules.js → v3-rules.ts} +1 -0
- package/src/validators/{validation-error.js → validation-error.ts} +1 -0
- package/src/validators/{validation-result.js → validation-result.ts} +1 -0
- package/src/validators/{validation-rule.js → validation-rule.ts} +1 -0
- package/src/diff/change.js +0 -139
- package/src/diff/list-diff.js +0 -227
- package/src/errors.js +0 -42
- package/src/migration/hyperedge-migrator.js +0 -119
- package/src/transforms/concept-to-gloss.transform.js +0 -77
- /package/src/rdf/{predicates.js → predicates.ts} +0 -0
- /package/src/transforms/{index.js → index.ts} +0 -0
package/src/diff/list-diff.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { GlossaristModel } from '../models/base.js';
|
|
2
|
-
import { Added, Removed, Changed, deserializeChange } from './change.js';
|
|
3
|
-
import { diffText } from './text-diff.js';
|
|
4
|
-
|
|
5
|
-
export class ListDiff extends GlossaristModel {
|
|
6
|
-
constructor(data = {}) {
|
|
7
|
-
super();
|
|
8
|
-
this._added = (data.added ?? []).map(c => c instanceof Added ? c : Added.fromJSON(c));
|
|
9
|
-
this._removed = (data.removed ?? []).map(c => c instanceof Removed ? c : Removed.fromJSON(c));
|
|
10
|
-
this._changed = (data.changed ?? []).map(c => {
|
|
11
|
-
if (c instanceof Changed) return c;
|
|
12
|
-
return deserializeChange(c);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get added() { return this._added; }
|
|
17
|
-
get removed() { return this._removed; }
|
|
18
|
-
get changed() { return this._changed; }
|
|
19
|
-
|
|
20
|
-
get hasChanges() {
|
|
21
|
-
return this._added.length > 0 || this._removed.length > 0 || this._changed.length > 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get count() {
|
|
25
|
-
return this._added.length + this._removed.length + this._changed.length;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
*entries() {
|
|
29
|
-
yield* this._added;
|
|
30
|
-
yield* this._removed;
|
|
31
|
-
yield* this._changed;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
toJSON() {
|
|
35
|
-
return {
|
|
36
|
-
added: this._added.map(c => c.toJSON()),
|
|
37
|
-
removed: this._removed.map(c => c.toJSON()),
|
|
38
|
-
changed: this._changed.map(c => c.toJSON()),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static fromJSON(data) {
|
|
43
|
-
return new ListDiff(data);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Diff two ordered lists using LCS alignment.
|
|
49
|
-
*
|
|
50
|
-
* - Items present in both (by identityKey) at the same relative position
|
|
51
|
-
* are "unchanged" and omitted from the result.
|
|
52
|
-
* - Items only in the old list are "removed".
|
|
53
|
-
* - Items only in the new list are "added".
|
|
54
|
-
* - When an adjacent removed+added pair is detected at the same edit
|
|
55
|
-
* position, they are merged into a single "changed" entry. If both
|
|
56
|
-
* items expose text via textKey, a word-level TextDiff is attached.
|
|
57
|
-
*
|
|
58
|
-
* @param {Array} oldList
|
|
59
|
-
* @param {Array} newList
|
|
60
|
-
* @param {Object} [options]
|
|
61
|
-
* @param {(item: any) => string} [options.identityKey] - equality key for matching items across lists
|
|
62
|
-
* @param {(item: any) => string|null} [options.textKey] - extracts text content for word-level diff on changed items
|
|
63
|
-
* @returns {ListDiff}
|
|
64
|
-
*/
|
|
65
|
-
export function diffList(oldList = [], newList = [], options = {}) {
|
|
66
|
-
const identityKey = options.identityKey ?? defaultIdentityKey;
|
|
67
|
-
const textKey = options.textKey ?? null;
|
|
68
|
-
|
|
69
|
-
const oldArr = [...oldList];
|
|
70
|
-
const newArr = [...newList];
|
|
71
|
-
|
|
72
|
-
const oldKeys = oldArr.map(identityKey);
|
|
73
|
-
const newKeys = newArr.map(identityKey);
|
|
74
|
-
|
|
75
|
-
const dp = lcsTable(oldKeys, newKeys);
|
|
76
|
-
const ops = backtrackOps(dp, oldKeys, newKeys);
|
|
77
|
-
|
|
78
|
-
const added = [];
|
|
79
|
-
const removed = [];
|
|
80
|
-
const changed = [];
|
|
81
|
-
|
|
82
|
-
for (let k = 0; k < ops.length; k++) {
|
|
83
|
-
const op = ops[k];
|
|
84
|
-
if (op.type === 'equal') continue;
|
|
85
|
-
|
|
86
|
-
if (op.type === 'remove' && k + 1 < ops.length && ops[k + 1].type === 'add') {
|
|
87
|
-
const next = ops[k + 1];
|
|
88
|
-
const oldItem = oldArr[op.i];
|
|
89
|
-
const newItem = newArr[next.j];
|
|
90
|
-
changed.push(buildChanged(oldItem, newItem, textKey));
|
|
91
|
-
k++;
|
|
92
|
-
} else if (op.type === 'add' && k + 1 < ops.length && ops[k + 1].type === 'remove') {
|
|
93
|
-
const next = ops[k + 1];
|
|
94
|
-
const oldItem = oldArr[next.i];
|
|
95
|
-
const newItem = newArr[op.j];
|
|
96
|
-
changed.push(buildChanged(oldItem, newItem, textKey));
|
|
97
|
-
k++;
|
|
98
|
-
} else if (op.type === 'remove') {
|
|
99
|
-
removed.push(new Removed({ value: oldArr[op.i] }));
|
|
100
|
-
} else if (op.type === 'add') {
|
|
101
|
-
added.push(new Added({ value: newArr[op.j] }));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return new ListDiff({ added, removed, changed });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Diff two unordered collections by identity key (set-based).
|
|
110
|
-
*
|
|
111
|
-
* - Items matched by identityKey in both lists are compared for full
|
|
112
|
-
* equality; if they differ → "changed".
|
|
113
|
-
* - Items only in the old list are "removed".
|
|
114
|
-
* - Items only in the new list are "added".
|
|
115
|
-
*
|
|
116
|
-
* Use this for collections where order does not carry meaning (e.g.
|
|
117
|
-
* designations matched by term text, sources matched by ref, dates
|
|
118
|
-
* matched by type).
|
|
119
|
-
*
|
|
120
|
-
* @param {Array} oldList
|
|
121
|
-
* @param {Array} newList
|
|
122
|
-
* @param {Object} options
|
|
123
|
-
* @param {(item: any) => string} options.identityKey - key that identifies the same item across lists
|
|
124
|
-
* @param {(item: any) => string|null} [options.textKey] - extracts text for word-level diff on changed items
|
|
125
|
-
* @returns {ListDiff}
|
|
126
|
-
*/
|
|
127
|
-
export function diffSet(oldList = [], newList = [], options = {}) {
|
|
128
|
-
const identityKey = options.identityKey ?? defaultIdentityKey;
|
|
129
|
-
const textKey = options.textKey ?? null;
|
|
130
|
-
|
|
131
|
-
const oldMap = new Map();
|
|
132
|
-
for (const item of oldList) {
|
|
133
|
-
oldMap.set(identityKey(item), item);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const seenKeys = new Set();
|
|
137
|
-
const added = [];
|
|
138
|
-
const changed = [];
|
|
139
|
-
|
|
140
|
-
for (const item of newList) {
|
|
141
|
-
const key = identityKey(item);
|
|
142
|
-
seenKeys.add(key);
|
|
143
|
-
if (oldMap.has(key)) {
|
|
144
|
-
const oldItem = oldMap.get(key);
|
|
145
|
-
if (!itemsEqual(oldItem, item)) {
|
|
146
|
-
changed.push(buildChanged(oldItem, item, textKey));
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
added.push(new Added({ value: item }));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const removed = [];
|
|
154
|
-
for (const item of oldList) {
|
|
155
|
-
if (!seenKeys.has(identityKey(item))) {
|
|
156
|
-
removed.push(new Removed({ value: item }));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return new ListDiff({ added, removed, changed });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function buildChanged(oldItem, newItem, textKey) {
|
|
164
|
-
const opts = { oldValue: oldItem, newValue: newItem };
|
|
165
|
-
if (textKey) {
|
|
166
|
-
const oldText = textKey(oldItem);
|
|
167
|
-
const newText = textKey(newItem);
|
|
168
|
-
if (oldText != null || newText != null) {
|
|
169
|
-
opts.textDiff = diffText(oldText ?? '', newText ?? '');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return new Changed(opts);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function itemsEqual(a, b) {
|
|
176
|
-
if (a === b) return true;
|
|
177
|
-
if (a instanceof GlossaristModel && b instanceof GlossaristModel) {
|
|
178
|
-
return a.equals(b);
|
|
179
|
-
}
|
|
180
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function defaultIdentityKey(item) {
|
|
184
|
-
if (item == null) return '';
|
|
185
|
-
if (typeof item === 'string') return item;
|
|
186
|
-
if (typeof item === 'number') return String(item);
|
|
187
|
-
if (item instanceof GlossaristModel) {
|
|
188
|
-
return JSON.stringify(item.toJSON());
|
|
189
|
-
}
|
|
190
|
-
return JSON.stringify(item);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function lcsTable(aKeys, bKeys) {
|
|
194
|
-
const m = aKeys.length;
|
|
195
|
-
const n = bKeys.length;
|
|
196
|
-
const dp = Array.from({ length: m + 1 }, () => new Uint32Array(n + 1));
|
|
197
|
-
for (let i = 1; i <= m; i++) {
|
|
198
|
-
for (let j = 1; j <= n; j++) {
|
|
199
|
-
dp[i][j] = aKeys[i - 1] === bKeys[j - 1]
|
|
200
|
-
? dp[i - 1][j - 1] + 1
|
|
201
|
-
: Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return dp;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function backtrackOps(dp, aKeys, bKeys) {
|
|
208
|
-
const ops = [];
|
|
209
|
-
let i = aKeys.length;
|
|
210
|
-
let j = bKeys.length;
|
|
211
|
-
while (i > 0 && j > 0) {
|
|
212
|
-
if (aKeys[i - 1] === bKeys[j - 1]) {
|
|
213
|
-
ops.push({ type: 'equal', i: i - 1, j: j - 1 });
|
|
214
|
-
i--; j--;
|
|
215
|
-
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
216
|
-
ops.push({ type: 'remove', i: i - 1, j });
|
|
217
|
-
i--;
|
|
218
|
-
} else {
|
|
219
|
-
ops.push({ type: 'add', i, j: j - 1 });
|
|
220
|
-
j--;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
while (i > 0) { ops.push({ type: 'remove', i: i - 1, j }); i--; }
|
|
224
|
-
while (j > 0) { ops.push({ type: 'add', i, j: j - 1 }); j--; }
|
|
225
|
-
ops.reverse();
|
|
226
|
-
return ops;
|
|
227
|
-
}
|
package/src/errors.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base error for all glossarist errors.
|
|
3
|
-
*/
|
|
4
|
-
export class GlossaristError extends Error {
|
|
5
|
-
/**
|
|
6
|
-
* @param {string} message
|
|
7
|
-
* @param {{ cause?: Error }} [options]
|
|
8
|
-
*/
|
|
9
|
-
constructor(message, options) {
|
|
10
|
-
super(message, options);
|
|
11
|
-
this.name = 'GlossaristError';
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Thrown when a function receives invalid input (null, undefined, wrong type).
|
|
17
|
-
*/
|
|
18
|
-
export class InvalidInputError extends GlossaristError {
|
|
19
|
-
/**
|
|
20
|
-
* @param {string} what - description of the invalid input
|
|
21
|
-
* @param {string} [expected] - description of what was expected
|
|
22
|
-
*/
|
|
23
|
-
constructor(what, expected) {
|
|
24
|
-
const msg = expected ? `${what} (expected ${expected})` : what;
|
|
25
|
-
super(msg);
|
|
26
|
-
this.name = 'InvalidInputError';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Thrown when YAML content cannot be parsed.
|
|
32
|
-
*/
|
|
33
|
-
export class YamlParseError extends GlossaristError {
|
|
34
|
-
/**
|
|
35
|
-
* @param {string} context - what was being parsed (e.g. concept ID)
|
|
36
|
-
* @param {Error} cause - the original YAML parse error
|
|
37
|
-
*/
|
|
38
|
-
constructor(context, cause) {
|
|
39
|
-
super(`Failed to parse YAML for ${context}: ${cause.message}`, { cause });
|
|
40
|
-
this.name = 'YamlParseError';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// v1 → v2 PartitiveHyperedge migration.
|
|
2
|
-
//
|
|
3
|
-
// Pure function: takes a v1 PartitiveHyperedge hash, returns a v2
|
|
4
|
-
// PartitiveHyperedge hash. Idempotent. Used by the parser when loading
|
|
5
|
-
// legacy YAML; available as a public API for one-shot dataset migrations.
|
|
6
|
-
//
|
|
7
|
-
// Field mapping (per TODO.partitive-relation-v2 items 01–05 and the
|
|
8
|
-
// ISO 704:2022 correction):
|
|
9
|
-
//
|
|
10
|
-
// comprehensive → comprehensive (unchanged shape)
|
|
11
|
-
// parts: [ref, ...] → partitives: [{ ref, presence: 'required', count: 'exactly_one' }, ...]
|
|
12
|
-
// enumeration: open → completeness: partial
|
|
13
|
-
// enumeration: closed→ completeness: complete
|
|
14
|
-
// markers: [...] → (dropped — v2 uses per-member multiplicity
|
|
15
|
-
// + is_delimiting; v1 markers encoded the
|
|
16
|
-
// same information imprecisely and cannot
|
|
17
|
-
// round-trip losslessly)
|
|
18
|
-
// content: "..." → (dropped — structural edges carry no prose)
|
|
19
|
-
//
|
|
20
|
-
// Cardinality check: v1 allowed [1..*] parts; v2 requires [2..*].
|
|
21
|
-
// A v1 hyperedge with exactly 1 part cannot migrate to v2 and is
|
|
22
|
-
// returned with a `migrationWarning` field set so callers can decide
|
|
23
|
-
// whether to surface it (the parser drops these with a warning).
|
|
24
|
-
//
|
|
25
|
-
// ─── Migration decision table (v1 markers → v2 multiplicity) ──────
|
|
26
|
-
//
|
|
27
|
-
// v1 markers encoded per-relation plurality claims. v2 multiplicity
|
|
28
|
-
// is per-member. The mapping is lossy; reviewers must set per-member
|
|
29
|
-
// multiplicity individually after migration.
|
|
30
|
-
//
|
|
31
|
-
// | v1 markers | Likely multiplicity for each partitive | Notes |
|
|
32
|
-
// |-------------------------|------------------------------------------------|--------------------------------------------------------|
|
|
33
|
-
// | (none) | `compulsory` | Default; reviewer confirms |
|
|
34
|
-
// | `[double]` | `compulsory_multiple` or `compulsory_at_least_one` | Reviewer distinguishes "all required" from "≥1 required" |
|
|
35
|
-
// | `[dashed]` | `optional` or `optional_multiple` | Reviewer distinguishes single vs multiple |
|
|
36
|
-
// | `[double, dashed]` | `optional_multiple` or `compulsory_at_least_one` | Plurality claim; reviewer distinguishes uncertain vs required |
|
|
37
|
-
// | (single-part v1 edge) | (cannot migrate — needs ≥2 partitives) | Convert to a binary `has_part` edge |
|
|
38
|
-
//
|
|
39
|
-
// `is_delimiting` is NEVER set by migration. ISO 704:2022 says
|
|
40
|
-
// delimiting depends on the concept system and coordinate concepts,
|
|
41
|
-
// which migration cannot infer. Reviewer sets it based on domain
|
|
42
|
-
// knowledge (see DelimitingCoherenceRule for cross-relation checks).
|
|
43
|
-
|
|
44
|
-
export function migrateHyperedgeToRelation(v1Hash) {
|
|
45
|
-
if (v1Hash == null) return null;
|
|
46
|
-
if (Array.isArray(v1Hash)) {
|
|
47
|
-
return v1Hash.map(migrateHyperedgeToRelation);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const out = {
|
|
51
|
-
comprehensive: { ...(v1Hash.comprehensive ?? {}) },
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const parts = Array.isArray(v1Hash.parts) ? v1Hash.parts : [];
|
|
55
|
-
out.partitives = parts.map(ref => ({
|
|
56
|
-
ref: { ...ref },
|
|
57
|
-
presence: 'required',
|
|
58
|
-
count: 'exactly_one',
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
out.completeness = _migrateEnumeration(v1Hash.enumeration);
|
|
62
|
-
|
|
63
|
-
// v1 content + markers fields are dropped per TODO.partitive-relation-v2.
|
|
64
|
-
if (parts.length < 2) {
|
|
65
|
-
out.migrationWarning =
|
|
66
|
-
`v1 PartitiveHyperedge had ${parts.length} part(s); v2 requires ≥2. ` +
|
|
67
|
-
`Either add more partitives or convert to a binary has_part edge.`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (v1Hash.content != null) {
|
|
71
|
-
out.migrationWarning = (out.migrationWarning ?? '') +
|
|
72
|
-
` v1 'content' field was dropped (v2 PartitiveHyperedge carries no prose; ` +
|
|
73
|
-
`move valuable text to the comprehensive concept's notes).`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (Array.isArray(v1Hash.markers) && v1Hash.markers.length > 0) {
|
|
77
|
-
out.migrationWarning = (out.migrationWarning ?? '') +
|
|
78
|
-
` v1 'markers' field (${v1Hash.markers.join(', ')}) was dropped; ` +
|
|
79
|
-
`v2 uses per-member 'multiplicity' + 'is_delimiting'. Reviewer ` +
|
|
80
|
-
`should set these per partitive based on ISO 704:2022 notation.`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Inverse — v2 → v1 — exists only for tooling that must round-trip
|
|
87
|
-
// through v1 consumers. NOT used by the parser; the serializer emits
|
|
88
|
-
// v2 only. Lossy: drops criterion, multiplicity, is_delimiting.
|
|
89
|
-
export function downgradeRelationToHyperedge(v2Hash) {
|
|
90
|
-
if (v2Hash == null) return null;
|
|
91
|
-
if (Array.isArray(v2Hash)) {
|
|
92
|
-
return v2Hash.map(downgradeRelationToHyperedge);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const out = {
|
|
96
|
-
comprehensive: { ...(v2Hash.comprehensive ?? {}) },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
out.parts = (v2Hash.partitives ?? []).map(m => ({ ...(m.ref ?? {}) }));
|
|
100
|
-
out.enumeration = _downgradeCompleteness(v2Hash.completeness);
|
|
101
|
-
|
|
102
|
-
// criterion, multiplicity, is_delimiting have no v1 equivalent —
|
|
103
|
-
// dropped silently.
|
|
104
|
-
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function _migrateEnumeration(value) {
|
|
109
|
-
if (value === 'open') return 'partial';
|
|
110
|
-
if (value === 'closed') return 'complete';
|
|
111
|
-
if (value == null) return 'complete';
|
|
112
|
-
// Unknown values default to complete (safe default — v2's safe default).
|
|
113
|
-
return 'complete';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function _downgradeCompleteness(value) {
|
|
117
|
-
if (value === 'partial') return 'open';
|
|
118
|
-
return 'closed';
|
|
119
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// ConceptToGlossTransform — JS port of glossarist-ruby's
|
|
2
|
-
// `Transforms::ConceptToGlossTransform`. Converts a Concept model into
|
|
3
|
-
// ontology-faithful RDF (Turtle, JSON-LD, N-Triples).
|
|
4
|
-
//
|
|
5
|
-
// Usage:
|
|
6
|
-
// import { ConceptToGlossTransform } from 'glossarist/transforms';
|
|
7
|
-
// const xform = new ConceptToGlossTransform({ registerId: 'iso-6709', uriBase: 'https://glossarist.org' });
|
|
8
|
-
// const ttl = await xform.toTurtle(concept);
|
|
9
|
-
// const jsonld = await xform.toJsonld(concept);
|
|
10
|
-
// const ttlAll = await xform.toTurtleAll(concepts);
|
|
11
|
-
import { conceptToQuads } from '../rdf/gloss-concept.js';
|
|
12
|
-
import {
|
|
13
|
-
collectQuads,
|
|
14
|
-
writeTurtle,
|
|
15
|
-
writeNTriples,
|
|
16
|
-
writeJsonld,
|
|
17
|
-
} from '../rdf/document-writer.js';
|
|
18
|
-
|
|
19
|
-
// No hardcoded default URI base. Callers MUST pass options.uriBase so
|
|
20
|
-
// concept IRIs reflect the consumer's domain.
|
|
21
|
-
|
|
22
|
-
// Writer registry. Each entry is `{ write: (quads, transform) => Promise<string> }`.
|
|
23
|
-
// Adding a new output format is a single entry here, not six new methods.
|
|
24
|
-
const WRITERS = Object.freeze({
|
|
25
|
-
turtle: { write: (quads) => writeTurtle(quads) },
|
|
26
|
-
ntriples: { write: (quads) => writeNTriples(quads) },
|
|
27
|
-
jsonld: { write: (quads, t) => writeJsonld(quads, t._jsonldOpts()) },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export class ConceptToGlossTransform {
|
|
31
|
-
constructor(options = {}) {
|
|
32
|
-
this.registerId = options.registerId;
|
|
33
|
-
this.uriBase = options.uriBase?.replace(/\/+$/, '');
|
|
34
|
-
if (!this.uriBase) throw new Error('ConceptToGlossTransform requires options.uriBase — the deployment canonical URI root.');
|
|
35
|
-
this.jsonldContext = options.jsonldContext;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
toTurtle(target) { return this._serialize(target, 'turtle'); }
|
|
39
|
-
toTurtleAll(targets) { return this._serialize(targets, 'turtle'); }
|
|
40
|
-
toJsonld(target) { return this._serialize(target, 'jsonld'); }
|
|
41
|
-
toJsonldAll(targets) { return this._serialize(targets, 'jsonld'); }
|
|
42
|
-
toNTriples(target) { return this._serialize(target, 'ntriples'); }
|
|
43
|
-
toNTriplesAll(targets) { return this._serialize(targets, 'ntriples'); }
|
|
44
|
-
|
|
45
|
-
// Single dispatcher: collect quads for one concept or many, then hand
|
|
46
|
-
// off to the registered writer. Replaces six near-identical methods.
|
|
47
|
-
async _serialize(target, format) {
|
|
48
|
-
const writer = WRITERS[format];
|
|
49
|
-
if (!writer) throw new Error(`Unknown serialization format: ${format}`);
|
|
50
|
-
const quads = this._collect(target);
|
|
51
|
-
return writer.write(quads, this);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
_collect(target) {
|
|
55
|
-
if (target && typeof target[Symbol.iterator] === 'function') {
|
|
56
|
-
const quads = [];
|
|
57
|
-
for (const concept of target) {
|
|
58
|
-
for (const q of conceptToQuads(concept, this._optsFor(concept))) {
|
|
59
|
-
quads.push(q);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return quads;
|
|
63
|
-
}
|
|
64
|
-
return collectQuads(conceptToQuads(target, this._optsFor(target)));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
_optsFor(concept) {
|
|
68
|
-
return {
|
|
69
|
-
registerId: this.registerId ?? concept.registerId ?? 'glossary',
|
|
70
|
-
uriBase: this.uriBase,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
_jsonldOpts() {
|
|
75
|
-
return this.jsonldContext ? { context: this.jsonldContext } : {};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
File without changes
|
|
File without changes
|