linked-rolls 0.24.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 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`
@@ -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 } = withSystemContexts(asJsonLdEntity(edition));
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
- const editionSteps = [withSystems, withEditors, withDerivationTolerance];
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));