linked-rolls 0.25.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/Assumption.d.ts +7 -0
- package/lib/Assumption.js +7 -0
- package/lib/asJsonLd.js +72 -2
- package/lib/migrate.js +47 -1
- package/lib/spec/context.json +12 -2
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -24,6 +24,16 @@ https://pfefferniels.github.io/linked-rolls/reo/; the w3id.org
|
|
|
24
24
|
identifiers are not registered yet. `ontology/README.md` records the
|
|
25
25
|
naming decisions.
|
|
26
26
|
|
|
27
|
+
Beliefs travel as JSON-LD-star annotations (`@annotation`), which state
|
|
28
|
+
the triple they annotate. A reference the edition holds possible,
|
|
29
|
+
unlikely or false is therefore written as an embedded node beside the
|
|
30
|
+
document: RDF names the statement without stating it, and
|
|
31
|
+
`importJsonLd` puts it back where it stood. A doubted value, such as a
|
|
32
|
+
date or an attribution, stays annotated in place for now. Reading the
|
|
33
|
+
beliefs needs a processor that implements JSON-LD-star, such as the
|
|
34
|
+
Ruby `json-ld` gem; jsonld.js ignores `@annotation`, and every belief
|
|
35
|
+
with it.
|
|
36
|
+
|
|
27
37
|
## Format revisions
|
|
28
38
|
|
|
29
39
|
Files written by linked-rolls 0.1 load unchanged: `importJsonLd`
|
package/lib/Assumption.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export declare const certainties: readonly ['true', 'likely', 'possible', 'unlik
|
|
|
6
6
|
* and some values in between.
|
|
7
7
|
*/
|
|
8
8
|
export type Certainty = typeof certainties[number];
|
|
9
|
+
/**
|
|
10
|
+
* Whether a statement held with this certainty is stated as a fact when
|
|
11
|
+
* the edition is read as RDF. One held possible, unlikely or false is
|
|
12
|
+
* only quoted, so that a reader who leaves the beliefs aside does not
|
|
13
|
+
* take a doubted statement for the edition's own.
|
|
14
|
+
*/
|
|
15
|
+
export declare const isAsserted: (certainty: Certainty) => boolean;
|
|
9
16
|
/**
|
|
10
17
|
* An argumentation provides reasons for a belief and
|
|
11
18
|
* may be associated with a person carrying out that argumentation.
|
package/lib/Assumption.js
CHANGED
|
@@ -5,6 +5,13 @@ export const certainties = [
|
|
|
5
5
|
'unlikely',
|
|
6
6
|
'false'
|
|
7
7
|
];
|
|
8
|
+
/**
|
|
9
|
+
* Whether a statement held with this certainty is stated as a fact when
|
|
10
|
+
* the edition is read as RDF. One held possible, unlikely or false is
|
|
11
|
+
* only quoted, so that a reader who leaves the beliefs aside does not
|
|
12
|
+
* take a doubted statement for the edition's own.
|
|
13
|
+
*/
|
|
14
|
+
export const isAsserted = (certainty) => certainty === 'true' || certainty === 'likely';
|
|
8
15
|
export function valueOf(assumption) {
|
|
9
16
|
return assumption['@value'];
|
|
10
17
|
}
|
package/lib/asJsonLd.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { systemIdIn } from "./TrackerBar";
|
|
2
|
+
import { isAsserted } from "./Assumption";
|
|
3
|
+
import context from "./spec/context.json";
|
|
2
4
|
export const exportDate = (date) => {
|
|
3
5
|
const year = date.getFullYear();
|
|
4
6
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -62,9 +64,76 @@ const withSystemContexts = (node) => {
|
|
|
62
64
|
? { '@context': `https://w3id.org/reo/${system}/context.jsonld`, ...walked }
|
|
63
65
|
: walked;
|
|
64
66
|
};
|
|
67
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
68
|
+
/** Terms the context sets to null, which say nothing when the edition is read as RDF. */
|
|
69
|
+
const silentTerms = new Set(Object.entries(context['@context'])
|
|
70
|
+
.filter(([, definition]) => definition === null)
|
|
71
|
+
.map(([term]) => term));
|
|
72
|
+
/**
|
|
73
|
+
* A reference its belief does not hold to be so: it names a node, states
|
|
74
|
+
* nothing RDF would read besides, and its belief is below likely.
|
|
75
|
+
*/
|
|
76
|
+
const isDoubtedReference = (value) => isRecord(value)
|
|
77
|
+
&& typeof value['@id'] === 'string'
|
|
78
|
+
&& isRecord(value['@annotation'])
|
|
79
|
+
&& !isAsserted(value['@annotation'].belief?.certainty ?? 'true')
|
|
80
|
+
&& Object.keys(value).every(key => key === '@id' || key === '@annotation' || silentTerms.has(key));
|
|
81
|
+
/**
|
|
82
|
+
* The statement a doubted reference makes, as a JSON-LD-star embedded
|
|
83
|
+
* node: the triple is named without being stated, and the belief is
|
|
84
|
+
* about it. The annotation's own id goes along under a key RDF does not
|
|
85
|
+
* read, so that an import can put it back.
|
|
86
|
+
*/
|
|
87
|
+
const quote = (subject, key, reference, listed) => {
|
|
88
|
+
const { '@annotation': { '@id': annotation, ...about }, ...object } = reference;
|
|
89
|
+
return { '@id': { '@id': subject, [key]: listed ? [object] : object }, annotation, ...about };
|
|
90
|
+
};
|
|
91
|
+
/** The value a node states under a key, less the doubted references, and those references quoted. */
|
|
92
|
+
const quotingValue = (subject, key, value) => {
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return {
|
|
95
|
+
node: value.filter(item => !isDoubtedReference(item)),
|
|
96
|
+
quoted: value.filter(isDoubtedReference).map(item => quote(subject, key, item, true))
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return isDoubtedReference(value)
|
|
100
|
+
? { node: undefined, quoted: [quote(subject, key, value, false)] }
|
|
101
|
+
: { node: value, quoted: [] };
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The document with every doubted reference taken off the node that
|
|
105
|
+
* states it, and quoted instead.
|
|
106
|
+
*
|
|
107
|
+
* An `@annotation` in JSON-LD-star states the triple it annotates and
|
|
108
|
+
* then says something about it, so a statement the edition holds
|
|
109
|
+
* possible, unlikely or false would reach RDF as a fact. Only references
|
|
110
|
+
* between nodes are quoted, since only they can be put back where they
|
|
111
|
+
* stood; a doubted date or attribution stays annotated in place.
|
|
112
|
+
*/
|
|
113
|
+
const withDoubtedReferencesQuoted = (value) => {
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
const quotings = value.map(withDoubtedReferencesQuoted);
|
|
116
|
+
return { node: quotings.map(({ node }) => node), quoted: quotings.flatMap(({ quoted }) => quoted) };
|
|
117
|
+
}
|
|
118
|
+
if (!isRecord(value))
|
|
119
|
+
return { node: value, quoted: [] };
|
|
120
|
+
const subject = value['@id'];
|
|
121
|
+
const entries = Object.entries(value).map(([key, child]) => {
|
|
122
|
+
const own = typeof subject === 'string' && !key.startsWith('@')
|
|
123
|
+
? quotingValue(subject, key, child)
|
|
124
|
+
: { node: child, quoted: [] };
|
|
125
|
+
const below = withDoubtedReferencesQuoted(own.node);
|
|
126
|
+
return { key, node: below.node, quoted: [...own.quoted, ...below.quoted] };
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
node: Object.fromEntries(entries.filter(({ node }) => node !== undefined).map(({ key, node }) => [key, node])),
|
|
130
|
+
quoted: entries.flatMap(({ quoted }) => quoted)
|
|
131
|
+
};
|
|
132
|
+
};
|
|
65
133
|
export const asJsonLd = (edition) => {
|
|
134
|
+
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(asJsonLdEntity(edition)));
|
|
66
135
|
// The context is the export's own; one carried in from an import must not override it.
|
|
67
|
-
const { base, '@context': carried, ...rest } =
|
|
136
|
+
const { base, '@context': carried, ...rest } = node;
|
|
68
137
|
return {
|
|
69
138
|
'@context': [
|
|
70
139
|
'https://w3id.org/reo/context.jsonld',
|
|
@@ -74,6 +143,7 @@ export const asJsonLd = (edition) => {
|
|
|
74
143
|
],
|
|
75
144
|
'@type': "Edition",
|
|
76
145
|
'@id': edition.base,
|
|
77
|
-
...rest
|
|
146
|
+
...rest,
|
|
147
|
+
...(quoted.length > 0 && { '@included': quoted })
|
|
78
148
|
};
|
|
79
149
|
};
|
package/lib/migrate.js
CHANGED
|
@@ -200,5 +200,51 @@ const withDerivationTolerance = (edition) => {
|
|
|
200
200
|
: version)
|
|
201
201
|
};
|
|
202
202
|
};
|
|
203
|
-
|
|
203
|
+
/** A statement an export quoted rather than stated: an included node whose id is a triple. */
|
|
204
|
+
const isQuotedStatement = (node) => node !== null && typeof node === 'object' && node['@id'] !== null && typeof node['@id'] === 'object';
|
|
205
|
+
/** The reference a quoted statement made, annotated again with the belief the export set beside it. */
|
|
206
|
+
const referenceOf = (statement) => {
|
|
207
|
+
const { '@id': { '@id': subject, ...made }, annotation, ...about } = statement;
|
|
208
|
+
const [key, value] = Object.entries(made)[0];
|
|
209
|
+
const listed = Array.isArray(value);
|
|
210
|
+
return {
|
|
211
|
+
subject,
|
|
212
|
+
key,
|
|
213
|
+
listed,
|
|
214
|
+
reference: {
|
|
215
|
+
...(listed ? value[0] : value),
|
|
216
|
+
'@annotation': { ...(annotation !== undefined && { '@id': annotation }), ...about }
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
/** The document with each quoted reference back on the node that makes it, a node being what has a type. */
|
|
221
|
+
const withReferencesOn = (value, bySubject) => {
|
|
222
|
+
if (Array.isArray(value))
|
|
223
|
+
return value.map(item => withReferencesOn(item, bySubject));
|
|
224
|
+
if (!value || typeof value !== 'object')
|
|
225
|
+
return value;
|
|
226
|
+
const walked = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, withReferencesOn(child, bySubject)]));
|
|
227
|
+
const references = typeof value['@id'] === 'string' && value['@type'] !== undefined
|
|
228
|
+
? bySubject.get(value['@id']) ?? []
|
|
229
|
+
: [];
|
|
230
|
+
return references.reduce((node, { key, listed, reference }) => ({ ...node, [key]: listed ? [...(node[key] ?? []), reference] : reference }), walked);
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Puts back what an export quoted. A reference the edition doubts goes
|
|
234
|
+
* out as a JSON-LD-star embedded node beside the document, so that RDF
|
|
235
|
+
* does not state it; in the edition it belongs on the node that makes
|
|
236
|
+
* it, under its belief. In a list it comes back after the references
|
|
237
|
+
* that were stated.
|
|
238
|
+
*/
|
|
239
|
+
const withQuotedStatementsInPlace = (edition) => {
|
|
240
|
+
const included = Array.isArray(edition['@included']) ? edition['@included'] : [];
|
|
241
|
+
const statements = included.filter(isQuotedStatement);
|
|
242
|
+
if (statements.length === 0)
|
|
243
|
+
return edition;
|
|
244
|
+
const others = included.filter(node => !isQuotedStatement(node));
|
|
245
|
+
const { '@included': _quoted, ...rest } = edition;
|
|
246
|
+
const bySubject = Map.groupBy(statements.map(referenceOf), ({ subject }) => subject);
|
|
247
|
+
return withReferencesOn({ ...rest, ...(others.length > 0 && { '@included': others }) }, bySubject);
|
|
248
|
+
};
|
|
249
|
+
const editionSteps = [withQuotedStatementsInPlace, withSystems, withEditors, withDerivationTolerance];
|
|
204
250
|
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|
package/lib/spec/context.json
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"@type": "xsd:date"
|
|
85
85
|
},
|
|
86
86
|
"collationTolerance": null,
|
|
87
|
+
"annotation": null,
|
|
87
88
|
"roll": "lrmoo:R3i_realises",
|
|
88
89
|
"catalogueNumber": "dcterms:identifier",
|
|
89
90
|
"system": "crm:P2_has_type",
|
|
@@ -117,7 +118,12 @@
|
|
|
117
118
|
"role": {
|
|
118
119
|
"@id": "crm:P2_has_type",
|
|
119
120
|
"@type": "@vocab",
|
|
120
|
-
"@context": {
|
|
121
|
+
"@context": {
|
|
122
|
+
"@vocab": "https://w3id.org/reo/type/",
|
|
123
|
+
"pianist": "https://w3id.org/reo/type/pianist",
|
|
124
|
+
"publisher": "https://w3id.org/reo/type/publisher",
|
|
125
|
+
"transcription": "https://w3id.org/reo/type/transcription"
|
|
126
|
+
}
|
|
121
127
|
},
|
|
122
128
|
"ops": null,
|
|
123
129
|
"measurements": {
|
|
@@ -173,7 +179,11 @@
|
|
|
173
179
|
"kind": {
|
|
174
180
|
"@id": "crm:P2_has_type",
|
|
175
181
|
"@type": "@vocab",
|
|
176
|
-
"@context": {
|
|
182
|
+
"@context": {
|
|
183
|
+
"@vocab": "https://w3id.org/reo/type/",
|
|
184
|
+
"roll": "https://w3id.org/reo/type/roll",
|
|
185
|
+
"scan": "https://w3id.org/reo/type/scan"
|
|
186
|
+
}
|
|
177
187
|
},
|
|
178
188
|
"output": {
|
|
179
189
|
"@id": "crmdig:L11_had_output",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Digital editions of piano rolls: import, collation, editorial assumptions, JSON-LD export, and emulation through a reproducing system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
"build:docs": "node schema/docs/generate.ts src/schema.json docs/index.html"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"@types/jsonld": "^1.5.15",
|
|
47
48
|
"@types/node": "^24.13.3",
|
|
49
|
+
"jsonld": "^9.0.0",
|
|
48
50
|
"ts-json-schema-generator": "^2.9.0",
|
|
49
51
|
"typescript": "^7.0.2",
|
|
50
52
|
"vite": "^8.2.2",
|