linked-rolls 0.14.0 → 0.16.0
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/lib/EditionView.js +4 -2
- package/lib/Feature.d.ts +9 -1
- package/lib/Quantity.d.ts +6 -1
- package/lib/Quantity.js +3 -0
- package/lib/RollCopy.d.ts +10 -0
- package/lib/Version.d.ts +19 -1
- package/lib/Version.js +3 -0
- package/lib/editionOps.d.ts +45 -5
- package/lib/editionOps.js +254 -15
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/migrate.js +21 -1
- package/lib/readers/stanfordAton.js +4 -1
- package/lib/schema.json +106 -2
- package/lib/spec/context.json +1 -0
- package/lib/validate.d.ts +10 -1
- package/lib/validate.js +12 -7
- package/package.json +2 -1
package/lib/EditionView.js
CHANGED
|
@@ -13,8 +13,10 @@ export const getAt = (path, obj) => {
|
|
|
13
13
|
/** Keys under which an object names others by id, singly or in a list. */
|
|
14
14
|
const referenceKeys = ['delete', 'comprehends', 'motivation'];
|
|
15
15
|
const isObject = (v) => v !== null && typeof v === "object";
|
|
16
|
-
/**
|
|
17
|
-
const
|
|
16
|
+
/** What a reference may state besides the id: a belief about it, and the tolerance a derivation was collated at. */
|
|
17
|
+
const referenceOwnKeys = new Set(['id', '@annotation', 'collationTolerance']);
|
|
18
|
+
/** An object that names another by its id and says nothing of its own beyond that. */
|
|
19
|
+
const isReferenceOnly = (keys) => keys.every(key => referenceOwnKeys.has(key));
|
|
18
20
|
const pathOf = (trail) => {
|
|
19
21
|
const path = [];
|
|
20
22
|
for (let link = trail; link !== null; link = link.up)
|
package/lib/Feature.d.ts
CHANGED
|
@@ -89,6 +89,14 @@ export declare const conditions: {
|
|
|
89
89
|
readonly Mark: readonly ["faded"];
|
|
90
90
|
readonly GluedOn: readonly ["detaching", "ripped"];
|
|
91
91
|
};
|
|
92
|
+
/** The kinds of condition a feature may be in, whichever kind of feature it is. */
|
|
93
|
+
export type FeatureConditionType = typeof conditions[FeatureType][number];
|
|
94
|
+
/**
|
|
95
|
+
* A condition assigned to a feature, annotatable with a belief about
|
|
96
|
+
* its certainty. Which of the kinds a feature may be in depends on its
|
|
97
|
+
* own kind, which `conditions` states.
|
|
98
|
+
*/
|
|
99
|
+
export type FeatureConditionAssignment = ObjectAssumption<ConditionState<FeatureConditionType>>;
|
|
92
100
|
/**
|
|
93
101
|
* A hole (perforation) in the roll paper. Holes are the primary
|
|
94
102
|
* carriers of musical information on piano rolls, as they trigger
|
|
@@ -109,7 +117,7 @@ export interface Hole extends RollFeature<'Hole', typeof conditions.Hole[number]
|
|
|
109
117
|
* A trace is a visible mark or writing on the roll surface.
|
|
110
118
|
* Traces may fade over time.
|
|
111
119
|
*/
|
|
112
|
-
export interface Trace<T extends FeatureType> extends RollFeature<T, typeof conditions
|
|
120
|
+
export interface Trace<T extends FeatureType> extends RollFeature<T, typeof conditions[T][number]> {
|
|
113
121
|
}
|
|
114
122
|
export declare const writingMethods: readonly ['Print', 'Handwriting', 'Stamp'];
|
|
115
123
|
/**
|
package/lib/Quantity.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* The unit names are the ones the records state in their `unit` field.
|
|
8
8
|
*/
|
|
9
9
|
declare const unit: unique symbol;
|
|
10
|
-
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min';
|
|
10
|
+
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in';
|
|
11
11
|
export type Quantity<U extends Unit> = number & {
|
|
12
12
|
readonly [unit]: U;
|
|
13
13
|
};
|
|
@@ -22,6 +22,8 @@ export type Seconds = Quantity<'s'>;
|
|
|
22
22
|
export type Milliseconds = Quantity<'ms'>;
|
|
23
23
|
export type FeetPerMinute = Quantity<'ft/min'>;
|
|
24
24
|
export type MetersPerMinute = Quantity<'m/min'>;
|
|
25
|
+
/** How finely a scan was read: pixels of the image per inch of paper. */
|
|
26
|
+
export type Resolution = Quantity<'px/in'>;
|
|
25
27
|
export declare const mm: (value: number) => Quantity<"mm">;
|
|
26
28
|
export declare const cm: (value: number) => Quantity<"cm">;
|
|
27
29
|
export declare const px: (value: number) => Quantity<"px">;
|
|
@@ -30,6 +32,7 @@ export declare const seconds: (value: number) => Quantity<"s">;
|
|
|
30
32
|
export declare const milliseconds: (value: number) => Quantity<"ms">;
|
|
31
33
|
export declare const feetPerMinute: (value: number) => Quantity<"ft/min">;
|
|
32
34
|
export declare const metersPerMinute: (value: number) => Quantity<"m/min">;
|
|
35
|
+
export declare const pixelsPerInch: (value: number) => Quantity<"px/in">;
|
|
33
36
|
/**
|
|
34
37
|
* A value together with the unit it was measured in, as a record
|
|
35
38
|
* states it.
|
|
@@ -56,6 +59,8 @@ export declare const sum: <U extends Unit>(values: readonly Quantity<U>[]) => Qu
|
|
|
56
59
|
export declare const mean: <U extends Unit>(values: readonly Quantity<U>[]) => Quantity<U>;
|
|
57
60
|
/** A place in a scan taken at `dpi` dots per inch, on the paper. */
|
|
58
61
|
export declare const inMillimeters: (place: Pixels, dpi: number) => Millimeters;
|
|
62
|
+
/** A place on the paper, in a scan taken at `dpi` dots per inch. */
|
|
63
|
+
export declare const inPixels: (place: Millimeters, dpi: number) => Pixels;
|
|
59
64
|
export declare const inCentimeters: (length: Millimeters) => Centimeters;
|
|
60
65
|
export declare const inSeconds: (time: Milliseconds) => Seconds;
|
|
61
66
|
/** A speed as a record states it, in feet or metres per minute. */
|
package/lib/Quantity.js
CHANGED
|
@@ -7,6 +7,7 @@ export const seconds = quantity;
|
|
|
7
7
|
export const milliseconds = quantity;
|
|
8
8
|
export const feetPerMinute = quantity;
|
|
9
9
|
export const metersPerMinute = quantity;
|
|
10
|
+
export const pixelsPerInch = quantity;
|
|
10
11
|
// The second operand takes its unit from the first. Inferred from both,
|
|
11
12
|
// TypeScript would unite two units rather than reject them.
|
|
12
13
|
export const add = (a, b) => quantity(a + b);
|
|
@@ -21,6 +22,8 @@ export const mean = (values) => quantity(sum(values) / values.length);
|
|
|
21
22
|
const MM_PER_INCH = 25.4;
|
|
22
23
|
/** A place in a scan taken at `dpi` dots per inch, on the paper. */
|
|
23
24
|
export const inMillimeters = (place, dpi) => mm(place / dpi * MM_PER_INCH);
|
|
25
|
+
/** A place on the paper, in a scan taken at `dpi` dots per inch. */
|
|
26
|
+
export const inPixels = (place, dpi) => px(place / MM_PER_INCH * dpi);
|
|
24
27
|
export const inCentimeters = (length) => cm(length / 10);
|
|
25
28
|
export const inSeconds = (time) => seconds(time / 1000);
|
|
26
29
|
const METERS_PER_FOOT = 0.3048;
|
package/lib/RollCopy.d.ts
CHANGED
|
@@ -222,6 +222,16 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
|
|
|
222
222
|
* Not exported to RDF.
|
|
223
223
|
*/
|
|
224
224
|
scale: number;
|
|
225
|
+
/**
|
|
226
|
+
* The resolution this copy's scan was read at, along the roll.
|
|
227
|
+
* It is what the measurements given in pixels are to be read
|
|
228
|
+
* against, and what every conversion between a place in the
|
|
229
|
+
* scan and one on the paper goes through. A scanner may read
|
|
230
|
+
* across the roll at another resolution, which the analysis
|
|
231
|
+
* files do not state.
|
|
232
|
+
* @see reo:scanResolution
|
|
233
|
+
*/
|
|
234
|
+
scanResolution: Measure<'px/in'>;
|
|
225
235
|
/**
|
|
226
236
|
* Relates this copy's scan to the tracker bar: how the scanning
|
|
227
237
|
* software's hole numbering was shifted onto the bar, and where
|
package/lib/Version.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Edit } from "./Edit";
|
|
2
2
|
import { ReferenceAssumption } from "./Assumption";
|
|
3
|
+
import { CollationTolerance } from "./Collation";
|
|
3
4
|
import { AnySymbol } from "./Symbol";
|
|
4
5
|
import { WithId, WithNote, WithType } from "./utils";
|
|
5
6
|
export declare const versionTypes: readonly [
|
|
@@ -24,6 +25,23 @@ export type VersionType = typeof versionTypes[number];
|
|
|
24
25
|
* @see crm:E33 Linguistic Object
|
|
25
26
|
*/
|
|
26
27
|
export type Motivation = WithType<'motivation'> & WithId & WithNote;
|
|
28
|
+
/**
|
|
29
|
+
* A derivation names the version another one was derived from, together
|
|
30
|
+
* with the tolerance the two were collated at. How precisely the copies
|
|
31
|
+
* put a symbol depends on what they are and on how their features were
|
|
32
|
+
* obtained, so the tolerance can differ from derivation to derivation.
|
|
33
|
+
* @see lrmoo:R76 is derivative of
|
|
34
|
+
*/
|
|
35
|
+
export type Derivation = ReferenceAssumption & {
|
|
36
|
+
/**
|
|
37
|
+
* The tolerance at which the derived version was collated against
|
|
38
|
+
* the one it is based on. A derivation written before the tolerance
|
|
39
|
+
* was held here states none. Not exported to RDF.
|
|
40
|
+
*/
|
|
41
|
+
collationTolerance?: CollationTolerance;
|
|
42
|
+
};
|
|
43
|
+
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
44
|
+
export declare const collationToleranceOf: (derivation: Readonly<Derivation>) => CollationTolerance;
|
|
27
45
|
/**
|
|
28
46
|
* A version is defined by the sum of edits applied
|
|
29
47
|
* to the version it is based on. For simple identification,
|
|
@@ -46,7 +64,7 @@ export interface Version extends WithId, WithType<'Version'> {
|
|
|
46
64
|
* If no derivation is defined, it is assumed that this version represents the mother roll.
|
|
47
65
|
* @see lrmoo:R76 is derivative of
|
|
48
66
|
*/
|
|
49
|
-
basedOn?:
|
|
67
|
+
basedOn?: Derivation;
|
|
50
68
|
/**
|
|
51
69
|
* The list of edits that, applied to the base version, produce this version.
|
|
52
70
|
* @see reo:involvedEdit
|
package/lib/Version.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultCollationTolerance } from "./Collation";
|
|
1
2
|
export const versionTypes = [
|
|
2
3
|
/**
|
|
3
4
|
* The roll is in a state where it is (possibly) used as
|
|
@@ -9,6 +10,8 @@ export const versionTypes = [
|
|
|
9
10
|
*/
|
|
10
11
|
'unicum'
|
|
11
12
|
];
|
|
13
|
+
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
14
|
+
export const collationToleranceOf = (derivation) => derivation.collationTolerance ?? defaultCollationTolerance;
|
|
12
15
|
/** The symbols the version's edits insert. */
|
|
13
16
|
export const insertedBy = (version) => version.edits.flatMap(edit => edit.insert ?? []);
|
|
14
17
|
/** The ids of the symbols the version's edits delete. */
|
package/lib/editionOps.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { Edition } from "./Edition";
|
|
|
4
4
|
import { AnySymbol, PlacementRelation } from "./Symbol";
|
|
5
5
|
import { CollationTolerance } from "./Collation";
|
|
6
6
|
import { Edit } from "./Edit";
|
|
7
|
-
import { RollCopy, ScaleReading, Shift } from "./RollCopy";
|
|
7
|
+
import { GeneralRollCondition, RollCopy, ScaleReading, Shift } from "./RollCopy";
|
|
8
8
|
import { FeatureSource } from "./FeatureSource";
|
|
9
|
-
import { AnyArgumentation, Certainty } from "./Assumption";
|
|
9
|
+
import { AnyArgumentation, Certainty, ObjectAssumption } from "./Assumption";
|
|
10
|
+
import { AnyFeature, FeatureConditionAssignment } from "./Feature";
|
|
10
11
|
/**
|
|
11
12
|
* A change to an edition, written onto an immer draft of it. One
|
|
12
13
|
* operation is one undo step, so an operation that has to read the
|
|
@@ -35,25 +36,64 @@ export declare const unalignCopy: (copyId: string) => EditionOp;
|
|
|
35
36
|
export declare const stateSource: (copyId: string, source: FeatureSource) => EditionOp;
|
|
36
37
|
/** Takes back the statement, leaving the copy silent about its source again. */
|
|
37
38
|
export declare const clearSource: (copyId: string) => EditionOp;
|
|
39
|
+
/**
|
|
40
|
+
* Adds a general condition to the copy, beside whatever is stated of it
|
|
41
|
+
* already. The other condition a copy may be in, a paper stretch, is
|
|
42
|
+
* read off an alignment and stated by `alignCopy`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const addGeneralCondition: (copyId: string, condition: ObjectAssumption<GeneralRollCondition>) => EditionOp;
|
|
45
|
+
/**
|
|
46
|
+
* States the condition of the feature, in place of any earlier
|
|
47
|
+
* statement. Throws where the kind of feature is in no such condition;
|
|
48
|
+
* `conditions` says which conditions each kind of feature may be in.
|
|
49
|
+
*/
|
|
50
|
+
export declare const stateFeatureCondition: (copyId: string, featureId: string, condition: FeatureConditionAssignment) => EditionOp;
|
|
38
51
|
/** The symbols of the versions that no other copy carries. */
|
|
39
52
|
export declare const symbolsCarriedOnlyBy: (edition: Edition, copyId: string) => AnySymbol[];
|
|
40
53
|
/** Takes the features off the copy, and out of the versions with what only they carried. */
|
|
41
54
|
export declare const removeFeatures: (copyId: string, featureIds: readonly string[]) => EditionOp;
|
|
42
55
|
/**
|
|
43
56
|
* Takes the copy out of the edition together with the symbols only it
|
|
44
|
-
* carries, and with every reference the versions
|
|
57
|
+
* carries, and with every reference the versions and the argumentations
|
|
58
|
+
* made to those symbols.
|
|
45
59
|
*/
|
|
46
60
|
export declare const removeCopy: (copyId: string) => EditionOp;
|
|
61
|
+
/** Why several features cannot be replaced by one. */
|
|
62
|
+
export type MergeObstacle = 'fewer-than-two' | 'different-types' | 'different-tracks' | 'differing-conditions' | 'unlike-features';
|
|
63
|
+
/**
|
|
64
|
+
* What stands in the way of reading the features as one, or nothing
|
|
65
|
+
* where they may be merged. Features may be merged where they differ
|
|
66
|
+
* in nothing but their place along the roll, their depiction and the
|
|
67
|
+
* condition at most one of them states. How far apart they lie is not
|
|
68
|
+
* asked: whether a gap is a bridge of the perforator, a tear or two
|
|
69
|
+
* perforations of their own is the editor's reading.
|
|
70
|
+
*/
|
|
71
|
+
export declare const mergeObstacle: (features: readonly AnyFeature[]) => MergeObstacle | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Replaces the features of the copy with a single one covering them
|
|
74
|
+
* all, where a scan has split what the editor reads as one feature.
|
|
75
|
+
* The merged feature takes the place of the first it replaces and
|
|
76
|
+
* spans from the first to the last, any gap between them included.
|
|
77
|
+
* What the replaced features carried is carried by the merged one, and
|
|
78
|
+
* whatever else named them, a modification or a comprehension, names
|
|
79
|
+
* the merged one in their stead.
|
|
80
|
+
*
|
|
81
|
+
* Throws where the features cannot stand for one; `mergeObstacle`
|
|
82
|
+
* says beforehand whether they can.
|
|
83
|
+
*/
|
|
84
|
+
export declare const mergeFeatures: (copyId: string, featureIds: readonly string[]) => EditionOp;
|
|
47
85
|
/**
|
|
48
86
|
* Bases the child on the parent. A symbol of the child that collates
|
|
49
87
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
50
88
|
* rest become the child's insertions, and what the parent hands down
|
|
51
|
-
* and the child lacks becomes its deletions.
|
|
89
|
+
* and the child lacks becomes its deletions. The derivation states the
|
|
90
|
+
* tolerance it was collated at.
|
|
52
91
|
*/
|
|
53
92
|
export declare const connectVersions: (view: EditionView, childId: string, parentId: string, tolerance?: CollationTolerance) => EditionOp;
|
|
54
93
|
/**
|
|
55
94
|
* Folds the version's own symbols into those it inherits and collates
|
|
56
|
-
* with: the carriers pass over, and the insertions go.
|
|
95
|
+
* with: the carriers pass over, and the insertions go. Collates at the
|
|
96
|
+
* tolerance of the derivation, where the caller names none.
|
|
57
97
|
*/
|
|
58
98
|
export declare const collateSymbols: (view: EditionView, versionId: string, symbolIds: readonly string[], tolerance?: CollationTolerance) => EditionOp;
|
|
59
99
|
/**
|
package/lib/editionOps.js
CHANGED
|
@@ -3,10 +3,11 @@ import { v4 } from "uuid";
|
|
|
3
3
|
import { getAt } from "./EditionView";
|
|
4
4
|
import { isPerforation, placementRelations } from "./Symbol";
|
|
5
5
|
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
6
|
-
import { insertedBy } from "./Version";
|
|
6
|
+
import { collationToleranceOf, insertedBy } from "./Version";
|
|
7
7
|
import { asSymbols } from "./RollCopy";
|
|
8
8
|
import { applyShift, applyScale, revertShift, revertScale } from "./alignment";
|
|
9
9
|
import { assignReference, idOf } from "./Assumption";
|
|
10
|
+
import { conditions as conditionsAllowed } from "./Feature";
|
|
10
11
|
import { distance, mm, subtract } from "./Quantity";
|
|
11
12
|
const noChange = () => undefined;
|
|
12
13
|
const onCopy = (copyId, op) => draft => {
|
|
@@ -19,6 +20,11 @@ const onVersion = (versionId, op) => draft => {
|
|
|
19
20
|
if (version)
|
|
20
21
|
op(version, draft);
|
|
21
22
|
};
|
|
23
|
+
const onFeature = (copyId, featureId, op) => onCopy(copyId, copy => {
|
|
24
|
+
const feature = copy.features.find(f => f.id === featureId);
|
|
25
|
+
if (feature)
|
|
26
|
+
op(feature);
|
|
27
|
+
});
|
|
22
28
|
/**
|
|
23
29
|
* The state a draft stands at, as plain data. Reading a draft proxies
|
|
24
30
|
* everything it touches, so what is only read is read from this.
|
|
@@ -31,15 +37,24 @@ const mapped = (items, change) => {
|
|
|
31
37
|
const changed = items.map(change);
|
|
32
38
|
return changed.every((item, i) => item === items[i]) ? items : changed;
|
|
33
39
|
};
|
|
40
|
+
/** The items each changed, less those the change emptied; the very same array where it changed none. */
|
|
41
|
+
const pruned = (items, change, emptied) => {
|
|
42
|
+
const changed = mapped(items, change);
|
|
43
|
+
return changed === items ? items : changed.filter((item, i) => item === items[i] || !emptied(item));
|
|
44
|
+
};
|
|
45
|
+
/** The record with the field replaced, or the very same record where that is what stood there. */
|
|
46
|
+
const replacing = (record, key, value) => record[key] === value ? record : { ...record, [key]: value };
|
|
47
|
+
/** The ids of the items the rewriting left out. */
|
|
48
|
+
const droppedIds = (before, after) => {
|
|
49
|
+
const kept = new Set(after.map(item => item.id));
|
|
50
|
+
return before.flatMap(item => kept.has(item.id) ? [] : [item.id]);
|
|
51
|
+
};
|
|
34
52
|
const insertion = (symbol) => ({ type: 'edit', id: v4(), insert: [symbol] });
|
|
35
53
|
const deletion = (symbolId) => ({ type: 'edit', id: v4(), delete: [symbolId] });
|
|
36
54
|
const isEmpty = (edit) => !edit.insert?.length && !edit.delete?.length;
|
|
37
55
|
const insertedIn = (versions) => versions.flatMap(insertedBy);
|
|
38
56
|
/** The edits with the change applied, less those it emptied; the very same array where it changed none. */
|
|
39
|
-
const edited = (edits, change) =>
|
|
40
|
-
const changed = mapped(edits, change);
|
|
41
|
-
return changed === edits ? edits : changed.filter((edit, i) => edit === edits[i] || !isEmpty(edit));
|
|
42
|
-
};
|
|
57
|
+
const edited = (edits, change) => pruned(edits, change, isEmpty);
|
|
43
58
|
/** The edit without the symbols among its insertions, or the very same edit where it inserts none of them. */
|
|
44
59
|
const droppingInsertions = (symbolIds) => (edit) => {
|
|
45
60
|
const insert = edit.insert && without(edit.insert, symbol => symbolIds.has(symbol.id));
|
|
@@ -104,6 +119,34 @@ export const stateSource = (copyId, source) => onCopy(copyId, copy => {
|
|
|
104
119
|
export const clearSource = (copyId) => onCopy(copyId, copy => {
|
|
105
120
|
copy.readFrom = undefined;
|
|
106
121
|
});
|
|
122
|
+
/**
|
|
123
|
+
* Adds a general condition to the copy, beside whatever is stated of it
|
|
124
|
+
* already. The other condition a copy may be in, a paper stretch, is
|
|
125
|
+
* read off an alignment and stated by `alignCopy`.
|
|
126
|
+
*/
|
|
127
|
+
export const addGeneralCondition = (copyId, condition) => onCopy(copyId, copy => {
|
|
128
|
+
copy.conditions.push(condition);
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Whether the feature's own kind allows a condition of this kind. The
|
|
132
|
+
* type narrowed to is the weaker statement, holding of a condition any
|
|
133
|
+
* kind of feature may be in.
|
|
134
|
+
*/
|
|
135
|
+
const allows = (feature, condition) => {
|
|
136
|
+
const allowed = conditionsAllowed[feature.type];
|
|
137
|
+
return allowed.includes(condition.conditionType);
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* States the condition of the feature, in place of any earlier
|
|
141
|
+
* statement. Throws where the kind of feature is in no such condition;
|
|
142
|
+
* `conditions` says which conditions each kind of feature may be in.
|
|
143
|
+
*/
|
|
144
|
+
export const stateFeatureCondition = (copyId, featureId, condition) => onFeature(copyId, featureId, feature => {
|
|
145
|
+
if (!allows(feature, condition)) {
|
|
146
|
+
throw new Error(`A ${feature.type} is in no '${condition.conditionType}' condition`);
|
|
147
|
+
}
|
|
148
|
+
feature.condition = condition;
|
|
149
|
+
});
|
|
107
150
|
const featureIdsOf = (copy) => new Set(copy.features.map(feature => feature.id));
|
|
108
151
|
/**
|
|
109
152
|
* A symbol every carrier of which lies among the features loses its
|
|
@@ -146,17 +189,97 @@ const forgettingFeatures = (features, dropped) => {
|
|
|
146
189
|
return { ...edit, ...(insert && { insert }), ...(deleted && { delete: deleted }) };
|
|
147
190
|
};
|
|
148
191
|
};
|
|
192
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
193
|
+
/** The record's values each changed, or the very same record where the change left every one as it was. */
|
|
194
|
+
const withValues = (record, change) => {
|
|
195
|
+
const entries = Object.entries(record);
|
|
196
|
+
const changed = entries.map(([key, value]) => [key, change(value)]);
|
|
197
|
+
return changed.every(([, value], i) => value === entries[i][1]) ? record : Object.fromEntries(changed);
|
|
198
|
+
};
|
|
199
|
+
/** The value with the change applied to every record within it and then to itself, innermost first. */
|
|
200
|
+
const deeply = (change) => {
|
|
201
|
+
const changed = (value) => Array.isArray(value) ? mapped(value, changed)
|
|
202
|
+
: isRecord(value) ? change(withValues(value, changed))
|
|
203
|
+
: value;
|
|
204
|
+
return changed;
|
|
205
|
+
};
|
|
206
|
+
/** Whether the two stand alike, so that what differs within them can be written where it lies. */
|
|
207
|
+
const alike = (before, after) => {
|
|
208
|
+
if (!isRecord(before) || !isRecord(after))
|
|
209
|
+
return false;
|
|
210
|
+
if (Array.isArray(before))
|
|
211
|
+
return Array.isArray(after) && before.length === after.length;
|
|
212
|
+
return !Array.isArray(after);
|
|
213
|
+
};
|
|
214
|
+
/** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
|
|
215
|
+
const writeInto = (draft, before, after) => Object.entries(after).forEach(([key, value]) => {
|
|
216
|
+
if (value === before[key])
|
|
217
|
+
return;
|
|
218
|
+
if (alike(before[key], value))
|
|
219
|
+
writeInto(draft[key], before[key], value);
|
|
220
|
+
else
|
|
221
|
+
draft[key] = value;
|
|
222
|
+
});
|
|
223
|
+
const isCopy = (record) => 'type' in record && record.type === 'RollCopy';
|
|
224
|
+
const isBelief = (record) => 'type' in record && record.type === 'belief';
|
|
225
|
+
const isComprehension = (reason) => reason.type === 'meaningComprehension';
|
|
226
|
+
/** What the modification names, be it as added or as removed. */
|
|
227
|
+
const membersOf = (modification) => modification.type === 'Addition' ? modification.added : modification.removed;
|
|
228
|
+
const namesNothing = (modification) => membersOf(modification).length === 0;
|
|
229
|
+
const comprehendsNothing = (reason) => isComprehension(reason) && reason.comprehends.length === 0;
|
|
230
|
+
/** The modification with what it names rewritten, or the very same one where that leaves it as it was. */
|
|
231
|
+
const renamingMembers = (rename) => (modification) => modification.type === 'Addition'
|
|
232
|
+
? replacing(modification, 'added', rename(modification.added))
|
|
233
|
+
: replacing(modification, 'removed', rename(modification.removed));
|
|
234
|
+
/** The reason with what a comprehension comprehends rewritten; any other reason names nothing of the kind. */
|
|
235
|
+
const renamingComprehended = (rename) => (reason) => isComprehension(reason) ? replacing(reason, 'comprehends', rename(reason.comprehends)) : reason;
|
|
236
|
+
/**
|
|
237
|
+
* The record with the ids it names rewritten: what a copy's
|
|
238
|
+
* modifications added or removed, and what the comprehensions among a
|
|
239
|
+
* belief's reasons comprehend. A modification or a comprehension the
|
|
240
|
+
* rewriting emptied goes with what it named; one that named nothing
|
|
241
|
+
* before stays, as an edit that was empty before it does.
|
|
242
|
+
*/
|
|
243
|
+
const renaming = (rename) => (record) => {
|
|
244
|
+
if (isCopy(record)) {
|
|
245
|
+
return replacing(record, 'modifications', pruned(record.modifications, renamingMembers(rename), namesNothing));
|
|
246
|
+
}
|
|
247
|
+
if (isBelief(record)) {
|
|
248
|
+
return replacing(record, 'reasons', pruned(record.reasons, renamingComprehended(rename), comprehendsNothing));
|
|
249
|
+
}
|
|
250
|
+
return record;
|
|
251
|
+
};
|
|
149
252
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
253
|
+
* Rewrites the ids by which the edition names features and symbols
|
|
254
|
+
* outside the versions. A belief is annotatable anywhere, so the whole
|
|
255
|
+
* edition is read; what the rewriting leaves alone is left the very
|
|
256
|
+
* object it was.
|
|
257
|
+
*/
|
|
258
|
+
const renameReferences = (draft, rename) => {
|
|
259
|
+
const before = stateOf(draft);
|
|
260
|
+
const after = deeply(renaming(rename))(before);
|
|
261
|
+
if (after !== before)
|
|
262
|
+
writeInto(draft, before, after);
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Strikes the features from the edition: their carriers go, a symbol
|
|
266
|
+
* that had no other carrier goes with them, an edit left exchanging
|
|
267
|
+
* nothing goes as well, and so does every reference the versions, the
|
|
268
|
+
* modifications of the copies and the comprehensions made to what went.
|
|
153
269
|
*/
|
|
154
270
|
const forgetFeatures = (draft, features) => {
|
|
155
271
|
const versions = stateOf(draft.versions);
|
|
156
272
|
const dropped = new Set(insertedIn(versions).filter(carriedOnlyOn(features)).map(symbol => symbol.id));
|
|
157
273
|
const forget = forgettingFeatures(features, dropped);
|
|
274
|
+
const edits = versions.map(version => edited(version.edits, forget));
|
|
275
|
+
const gone = new Set([
|
|
276
|
+
...features,
|
|
277
|
+
...dropped,
|
|
278
|
+
...versions.flatMap((version, i) => droppedIds(version.edits, edits[i]))
|
|
279
|
+
]);
|
|
280
|
+
renameReferences(draft, ids => without(ids, id => gone.has(id)));
|
|
158
281
|
draft.versions.forEach((version, i) => {
|
|
159
|
-
version.edits =
|
|
282
|
+
version.edits = edits[i];
|
|
160
283
|
});
|
|
161
284
|
};
|
|
162
285
|
/** Takes the features off the copy, and out of the versions with what only they carried. */
|
|
@@ -167,12 +290,126 @@ export const removeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draf
|
|
|
167
290
|
});
|
|
168
291
|
/**
|
|
169
292
|
* Takes the copy out of the edition together with the symbols only it
|
|
170
|
-
* carries, and with every reference the versions
|
|
293
|
+
* carries, and with every reference the versions and the argumentations
|
|
294
|
+
* made to those symbols.
|
|
171
295
|
*/
|
|
172
296
|
export const removeCopy = (copyId) => onCopy(copyId, (copy, draft) => {
|
|
173
297
|
forgetFeatures(draft, featureIdsOf(copy));
|
|
174
298
|
draft.copies = draft.copies.filter(c => c.id !== copyId);
|
|
175
299
|
});
|
|
300
|
+
/**
|
|
301
|
+
* Whether two records of the edition state the same, the identity of a
|
|
302
|
+
* statement left out: two transcriptions reading the same word say the
|
|
303
|
+
* same, whichever ids they were given.
|
|
304
|
+
*/
|
|
305
|
+
const sayTheSame = (a, b) => {
|
|
306
|
+
if (a === b)
|
|
307
|
+
return true;
|
|
308
|
+
if (!isRecord(a) || !isRecord(b) || Array.isArray(a) !== Array.isArray(b))
|
|
309
|
+
return false;
|
|
310
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
311
|
+
keys.delete('id');
|
|
312
|
+
return [...keys].every(key => sayTheSame(a[key], b[key]));
|
|
313
|
+
};
|
|
314
|
+
/** What a feature states beyond its identity, its place along the roll, its depiction and its condition. */
|
|
315
|
+
const nature = (feature) => {
|
|
316
|
+
const { id, horizontal, depiction, condition, ...rest } = feature;
|
|
317
|
+
return rest;
|
|
318
|
+
};
|
|
319
|
+
const conditionsOf = (features) => features.flatMap(feature => feature.condition ? [feature.condition] : []);
|
|
320
|
+
/**
|
|
321
|
+
* What stands in the way of reading the features as one, or nothing
|
|
322
|
+
* where they may be merged. Features may be merged where they differ
|
|
323
|
+
* in nothing but their place along the roll, their depiction and the
|
|
324
|
+
* condition at most one of them states. How far apart they lie is not
|
|
325
|
+
* asked: whether a gap is a bridge of the perforator, a tear or two
|
|
326
|
+
* perforations of their own is the editor's reading.
|
|
327
|
+
*/
|
|
328
|
+
export const mergeObstacle = (features) => {
|
|
329
|
+
if (features.length < 2)
|
|
330
|
+
return 'fewer-than-two';
|
|
331
|
+
const [first, ...rest] = features;
|
|
332
|
+
if (rest.some(feature => feature.type !== first.type))
|
|
333
|
+
return 'different-types';
|
|
334
|
+
if (rest.some(feature => !sayTheSame(feature.vertical, first.vertical)))
|
|
335
|
+
return 'different-tracks';
|
|
336
|
+
if (rest.some(feature => !sayTheSame(nature(feature), nature(first))))
|
|
337
|
+
return 'unlike-features';
|
|
338
|
+
const conditions = conditionsOf(features);
|
|
339
|
+
if (conditions.some(condition => !sayTheSame(condition, conditions[0])))
|
|
340
|
+
return 'differing-conditions';
|
|
341
|
+
return undefined;
|
|
342
|
+
};
|
|
343
|
+
/** The items with the replacement in the place of the first it stands for, the others dropped. */
|
|
344
|
+
const standingFor = (items, stands, replacement) => {
|
|
345
|
+
const first = items.findIndex(stands);
|
|
346
|
+
if (first < 0)
|
|
347
|
+
return items;
|
|
348
|
+
return items.flatMap((item, i) => i === first ? [replacement] : stands(item) ? [] : [item]);
|
|
349
|
+
};
|
|
350
|
+
const spanning = (features) => ({
|
|
351
|
+
unit: 'mm',
|
|
352
|
+
from: mm(Math.min(...features.map(feature => feature.horizontal.from))),
|
|
353
|
+
to: mm(Math.max(...features.map(feature => feature.horizontal.to)))
|
|
354
|
+
});
|
|
355
|
+
/**
|
|
356
|
+
* The feature the merged ones give way to. It spans them all and is in
|
|
357
|
+
* every other respect the one of them that states a condition, the
|
|
358
|
+
* others being alike in all but their place. The depiction goes: a
|
|
359
|
+
* region of the scan showing one part depicts no more than that part.
|
|
360
|
+
*/
|
|
361
|
+
const mergedFrom = (features) => {
|
|
362
|
+
const stating = features.find(feature => feature.condition) ?? features[0];
|
|
363
|
+
const merged = { ...stating, id: v4(), horizontal: spanning(features) };
|
|
364
|
+
delete merged.depiction;
|
|
365
|
+
return merged;
|
|
366
|
+
};
|
|
367
|
+
/** The symbol carried by the merged feature where it named one of those it replaces, and naming it once. */
|
|
368
|
+
const carriedByMerged = (replaced, mergedId) => (symbol) => {
|
|
369
|
+
const names = (carrier) => replaced.has(idOf(carrier));
|
|
370
|
+
const first = symbol.carriers.find(names);
|
|
371
|
+
if (!first)
|
|
372
|
+
return symbol;
|
|
373
|
+
return { ...symbol, carriers: standingFor(symbol.carriers, names, { ...first, id: mergedId }) };
|
|
374
|
+
};
|
|
375
|
+
/** Points the versions at the merged feature wherever they name one it replaces. */
|
|
376
|
+
const carryOver = (draft, replaced, mergedId) => {
|
|
377
|
+
const recarry = carriedByMerged(replaced, mergedId);
|
|
378
|
+
const recarrying = (edit) => {
|
|
379
|
+
const insert = edit.insert && mapped(edit.insert, recarry);
|
|
380
|
+
return insert === edit.insert ? edit : { ...edit, insert };
|
|
381
|
+
};
|
|
382
|
+
const versions = stateOf(draft.versions);
|
|
383
|
+
draft.versions.forEach((version, i) => {
|
|
384
|
+
version.edits = mapped(versions[i].edits, recarrying);
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Replaces the features of the copy with a single one covering them
|
|
389
|
+
* all, where a scan has split what the editor reads as one feature.
|
|
390
|
+
* The merged feature takes the place of the first it replaces and
|
|
391
|
+
* spans from the first to the last, any gap between them included.
|
|
392
|
+
* What the replaced features carried is carried by the merged one, and
|
|
393
|
+
* whatever else named them, a modification or a comprehension, names
|
|
394
|
+
* the merged one in their stead.
|
|
395
|
+
*
|
|
396
|
+
* Throws where the features cannot stand for one; `mergeObstacle`
|
|
397
|
+
* says beforehand whether they can.
|
|
398
|
+
*/
|
|
399
|
+
export const mergeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
400
|
+
const replaced = new Set(featureIds);
|
|
401
|
+
const isReplaced = (feature) => replaced.has(feature.id);
|
|
402
|
+
const features = stateOf(copy.features);
|
|
403
|
+
const toMerge = features.filter(isReplaced);
|
|
404
|
+
const obstacle = mergeObstacle(toMerge);
|
|
405
|
+
if (obstacle) {
|
|
406
|
+
throw new Error(`The features of copy ${copyId} cannot be merged: ${obstacle}`);
|
|
407
|
+
}
|
|
408
|
+
const merged = mergedFrom(toMerge);
|
|
409
|
+
copy.features = standingFor(features, isReplaced, merged);
|
|
410
|
+
carryOver(draft, replaced, merged.id);
|
|
411
|
+
renameReferences(draft, ids => standingFor(ids, id => replaced.has(id), merged.id));
|
|
412
|
+
});
|
|
176
413
|
/** The carriers of each collated symbol pass to its counterpart. */
|
|
177
414
|
const handOverCarriers = (view, draft, collations) => collations.forEach(({ symbol, counterpart }) => {
|
|
178
415
|
const path = view.getPath(counterpart.id);
|
|
@@ -183,7 +420,8 @@ const handOverCarriers = (view, draft, collations) => collations.forEach(({ symb
|
|
|
183
420
|
* Bases the child on the parent. A symbol of the child that collates
|
|
184
421
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
185
422
|
* rest become the child's insertions, and what the parent hands down
|
|
186
|
-
* and the child lacks becomes its deletions.
|
|
423
|
+
* and the child lacks becomes its deletions. The derivation states the
|
|
424
|
+
* tolerance it was collated at.
|
|
187
425
|
*/
|
|
188
426
|
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
189
427
|
const inherited = view.snapshot(parentId);
|
|
@@ -198,20 +436,21 @@ export const connectVersions = (view, childId, parentId, tolerance = defaultColl
|
|
|
198
436
|
return onVersion(childId, (child, draft) => {
|
|
199
437
|
handOverCarriers(view, draft, collations);
|
|
200
438
|
child.edits = edits;
|
|
201
|
-
child.basedOn = assignReference(parentId);
|
|
439
|
+
child.basedOn = { ...assignReference(parentId), collationTolerance: tolerance };
|
|
202
440
|
});
|
|
203
441
|
};
|
|
204
442
|
/**
|
|
205
443
|
* Folds the version's own symbols into those it inherits and collates
|
|
206
|
-
* with: the carriers pass over, and the insertions go.
|
|
444
|
+
* with: the carriers pass over, and the insertions go. Collates at the
|
|
445
|
+
* tolerance of the derivation, where the caller names none.
|
|
207
446
|
*/
|
|
208
|
-
export const collateSymbols = (view, versionId, symbolIds, tolerance
|
|
447
|
+
export const collateSymbols = (view, versionId, symbolIds, tolerance) => {
|
|
209
448
|
const version = view.get(versionId);
|
|
210
449
|
if (!version?.basedOn)
|
|
211
450
|
return noChange;
|
|
212
451
|
const chosen = new Set(symbolIds);
|
|
213
452
|
const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
|
|
214
|
-
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance);
|
|
453
|
+
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance ?? collationToleranceOf(version.basedOn));
|
|
215
454
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
216
455
|
return onVersion(versionId, (version, draft) => {
|
|
217
456
|
handOverCarriers(view, draft, collations);
|
package/lib/index.d.ts
CHANGED
|
@@ -27,5 +27,6 @@ export * from './constraints';
|
|
|
27
27
|
export * from './context';
|
|
28
28
|
export * from './asJsonLd';
|
|
29
29
|
export * from './importJsonLd';
|
|
30
|
+
export * from './migrate';
|
|
30
31
|
export { readFromStanfordAton, type StanfordAtonOptions } from './readers/stanfordAton';
|
|
31
32
|
export { readFromSpencerBar, readSpencerAnn, paperSpeedOfSpencerAnn, SPENCER_ROWS_PER_INCH, type SpencerBarOptions } from './readers/spencerBar';
|
package/lib/index.js
CHANGED
|
@@ -27,5 +27,6 @@ export * from './constraints';
|
|
|
27
27
|
export * from './context';
|
|
28
28
|
export * from './asJsonLd';
|
|
29
29
|
export * from './importJsonLd';
|
|
30
|
+
export * from './migrate';
|
|
30
31
|
export { readFromStanfordAton } from './readers/stanfordAton';
|
|
31
32
|
export { readFromSpencerBar, readSpencerAnn, paperSpeedOfSpencerAnn, SPENCER_ROWS_PER_INCH } from './readers/spencerBar';
|
package/lib/migrate.js
CHANGED
|
@@ -107,4 +107,24 @@ const withRollSystem = (edition) => {
|
|
|
107
107
|
const withEditors = (edition) => !edition.creation || edition.creation.editors
|
|
108
108
|
? edition
|
|
109
109
|
: { ...edition, creation: { ...edition.creation, editors: [] } };
|
|
110
|
-
|
|
110
|
+
const statesNoTolerance = (version) => version.basedOn && !version.basedOn.collationTolerance;
|
|
111
|
+
/**
|
|
112
|
+
* The collation tolerance was the edition's before it was stated on
|
|
113
|
+
* each derivation. An edition written then collated every version at
|
|
114
|
+
* that one value, so it is written onto every derivation that gives
|
|
115
|
+
* none of its own.
|
|
116
|
+
*/
|
|
117
|
+
const withDerivationTolerance = (edition) => {
|
|
118
|
+
const collationTolerance = edition.creation?.collationTolerance;
|
|
119
|
+
const versions = Array.isArray(edition.versions) ? edition.versions : [];
|
|
120
|
+
if (!collationTolerance || !versions.some(statesNoTolerance))
|
|
121
|
+
return edition;
|
|
122
|
+
return {
|
|
123
|
+
...edition,
|
|
124
|
+
versions: versions.map(version => statesNoTolerance(version)
|
|
125
|
+
? { ...version, basedOn: { ...version.basedOn, collationTolerance } }
|
|
126
|
+
: version)
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const editionSteps = [withRollSystem, withEditors, withDerivationTolerance];
|
|
130
|
+
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|
|
@@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
|
|
2
2
|
import { AtonParser } from "./AtonParser";
|
|
3
3
|
import { systemOf, translationBetween } from "../TrackerBar";
|
|
4
4
|
import { welteT100 } from "../systems/welteT100/bar";
|
|
5
|
-
import { inMillimeters, mean, mm, px, subtract, track } from "../Quantity";
|
|
5
|
+
import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../Quantity";
|
|
6
6
|
/** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
|
|
7
7
|
const readPx = (value) => px(parseFloat(value));
|
|
8
8
|
/** The parser writes one record as an object and several as an array. */
|
|
@@ -155,6 +155,9 @@ export function readFromStanfordAton(atonString, { trackShift, bar = welteT100,
|
|
|
155
155
|
bass: readPx(json.ROLLINFO.HARD_MARGIN_BASS),
|
|
156
156
|
unit: 'px'
|
|
157
157
|
},
|
|
158
|
+
...(Number.isFinite(dpi) && {
|
|
159
|
+
scanResolution: { value: pixelsPerInch(dpi), unit: 'px/in' }
|
|
160
|
+
}),
|
|
158
161
|
trackCalibration: calibration,
|
|
159
162
|
...(measuredBy && { measuredBy })
|
|
160
163
|
},
|
package/lib/schema.json
CHANGED
|
@@ -277,6 +277,41 @@
|
|
|
277
277
|
],
|
|
278
278
|
"type": "object"
|
|
279
279
|
},
|
|
280
|
+
"Derivation": {
|
|
281
|
+
"description": "A derivation names the version another one was derived from, together with the tolerance the two were collated at. How precisely the copies put a symbol depends on what they are and on how their features were obtained, so the tolerance can differ from derivation to derivation. [ontology: lrmoo:R76 is derivative of]",
|
|
282
|
+
"properties": {
|
|
283
|
+
"@annotation": {
|
|
284
|
+
"description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
|
|
285
|
+
"properties": {
|
|
286
|
+
"belief": {
|
|
287
|
+
"$ref": "#/definitions/Belief",
|
|
288
|
+
"description": "The belief held about the annotated statement. [ontology: crminf:J4i is subject of]"
|
|
289
|
+
},
|
|
290
|
+
"@id": {
|
|
291
|
+
"description": "A unique identifier for this object.",
|
|
292
|
+
"type": "string"
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
"required": [
|
|
296
|
+
"belief",
|
|
297
|
+
"@id"
|
|
298
|
+
],
|
|
299
|
+
"type": "object"
|
|
300
|
+
},
|
|
301
|
+
"collationTolerance": {
|
|
302
|
+
"$ref": "#/definitions/CollationTolerance",
|
|
303
|
+
"description": "The tolerance at which the derived version was collated against the one it is based on. A derivation written before the tolerance was held here states none. Not exported to RDF."
|
|
304
|
+
},
|
|
305
|
+
"@id": {
|
|
306
|
+
"description": "A unique identifier for this object.",
|
|
307
|
+
"type": "string"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"required": [
|
|
311
|
+
"@id"
|
|
312
|
+
],
|
|
313
|
+
"type": "object"
|
|
314
|
+
},
|
|
280
315
|
"Edit": {
|
|
281
316
|
"description": "A set of edits transforms a version of a roll into another version. Edits insert or delete symbols, or both (= replace). Edits may be motivated by a given set of reasons, e.g. to add an additional accent or to correct an error. If an edit is the interpretation of a metamark, such as a pencil mark, this should be made explicit using a meaning comprehension on the `@annotation` field. [ontology: reo:Edit]",
|
|
282
317
|
"properties": {
|
|
@@ -882,6 +917,25 @@
|
|
|
882
917
|
],
|
|
883
918
|
"type": "object"
|
|
884
919
|
},
|
|
920
|
+
"Measure<\"px/in\">": {
|
|
921
|
+
"description": "A value together with the unit it was measured in, as a record states it. [ontology: crm:E54 Dimension]",
|
|
922
|
+
"properties": {
|
|
923
|
+
"unit": {
|
|
924
|
+
"const": "px/in",
|
|
925
|
+
"description": "The unit of measurement. [ontology: crm:P91 has unit]",
|
|
926
|
+
"type": "string"
|
|
927
|
+
},
|
|
928
|
+
"value": {
|
|
929
|
+
"$ref": "#/definitions/Quantity%3C%22px%2Fin%22%3E",
|
|
930
|
+
"description": "The measured value. [ontology: crm:P90 has value]"
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
"required": [
|
|
934
|
+
"value",
|
|
935
|
+
"unit"
|
|
936
|
+
],
|
|
937
|
+
"type": "object"
|
|
938
|
+
},
|
|
885
939
|
"Millimeters": {
|
|
886
940
|
"$ref": "#/definitions/Quantity%3C%22mm%22%3E",
|
|
887
941
|
"description": "A place along the roll, or any length on the paper."
|
|
@@ -1227,6 +1281,49 @@
|
|
|
1227
1281
|
],
|
|
1228
1282
|
"type": "object"
|
|
1229
1283
|
},
|
|
1284
|
+
"ObjectAssumption<ConditionState<(\"illegible\")>>": {
|
|
1285
|
+
"description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
|
|
1286
|
+
"properties": {
|
|
1287
|
+
"@annotation": {
|
|
1288
|
+
"description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
|
|
1289
|
+
"properties": {
|
|
1290
|
+
"belief": {
|
|
1291
|
+
"$ref": "#/definitions/Belief",
|
|
1292
|
+
"description": "The belief held about the annotated statement. [ontology: crminf:J4i is subject of]"
|
|
1293
|
+
},
|
|
1294
|
+
"@id": {
|
|
1295
|
+
"description": "A unique identifier for this object.",
|
|
1296
|
+
"type": "string"
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
"required": [
|
|
1300
|
+
"belief",
|
|
1301
|
+
"@id"
|
|
1302
|
+
],
|
|
1303
|
+
"type": "object"
|
|
1304
|
+
},
|
|
1305
|
+
"conditionType": {
|
|
1306
|
+
"const": "illegible",
|
|
1307
|
+
"description": "The kind of condition, from the list the roll or the kind of feature allows. [ontology: crm:P2 has type]",
|
|
1308
|
+
"type": "string"
|
|
1309
|
+
},
|
|
1310
|
+
"description": {
|
|
1311
|
+
"description": "A free-text description of the condition, providing details beyond the type classification. [ontology: crm:P3 has note]",
|
|
1312
|
+
"type": "string"
|
|
1313
|
+
},
|
|
1314
|
+
"@type": {
|
|
1315
|
+
"const": "ConditionState",
|
|
1316
|
+
"description": "The type discriminator for this object. [ontology: rdf:type]",
|
|
1317
|
+
"type": "string",
|
|
1318
|
+
"ontology": "rdf:type"
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
"required": [
|
|
1322
|
+
"conditionType",
|
|
1323
|
+
"@type"
|
|
1324
|
+
],
|
|
1325
|
+
"type": "object"
|
|
1326
|
+
},
|
|
1230
1327
|
"ObjectAssumption<ConditionState<(\"partially-torn\"|\"missing-perforation\")>>": {
|
|
1231
1328
|
"description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
|
|
1232
1329
|
"properties": {
|
|
@@ -1610,6 +1707,9 @@
|
|
|
1610
1707
|
"Quantity<\"px\">": {
|
|
1611
1708
|
"type": "number"
|
|
1612
1709
|
},
|
|
1710
|
+
"Quantity<\"px/in\">": {
|
|
1711
|
+
"type": "number"
|
|
1712
|
+
},
|
|
1613
1713
|
"Quantity<\"track\">": {
|
|
1614
1714
|
"type": "number"
|
|
1615
1715
|
},
|
|
@@ -1817,6 +1917,10 @@
|
|
|
1817
1917
|
"description": "The factor this copy's features were scaled by to align them with the others. What it is put down to is stated apart: a paper-stretch condition, or the speed the copy was cut for. Not exported to RDF.",
|
|
1818
1918
|
"type": "number"
|
|
1819
1919
|
},
|
|
1920
|
+
"scanResolution": {
|
|
1921
|
+
"$ref": "#/definitions/Measure%3C%22px%2Fin%22%3E",
|
|
1922
|
+
"description": "The resolution this copy's scan was read at, along the roll. It is what the measurements given in pixels are to be read against, and what every conversion between a place in the scan and one on the paper goes through. A scanner may read across the roll at another resolution, which the analysis files do not state. [ontology: reo:scanResolution]"
|
|
1923
|
+
},
|
|
1820
1924
|
"shift": {
|
|
1821
1925
|
"$ref": "#/definitions/Shift",
|
|
1822
1926
|
"description": "The shift applied to align this copy with the others. Not exported to RDF."
|
|
@@ -1978,7 +2082,7 @@
|
|
|
1978
2082
|
"description": "A version is defined by the sum of edits applied to the version it is based on. For simple identification, a siglum is given to each version. [ontology: lrmoo:F2 Expression]",
|
|
1979
2083
|
"properties": {
|
|
1980
2084
|
"basedOn": {
|
|
1981
|
-
"$ref": "#/definitions/
|
|
2085
|
+
"$ref": "#/definitions/Derivation",
|
|
1982
2086
|
"description": "If no derivation is defined, it is assumed that this version represents the mother roll. [ontology: lrmoo:R76 is derivative of]"
|
|
1983
2087
|
},
|
|
1984
2088
|
"edits": {
|
|
@@ -2059,7 +2163,7 @@
|
|
|
2059
2163
|
"description": "A piece of writing found on the roll, such as a label, catalogue number, or annotation. Writings have a method of production and a transcription of their content. [ontology: crm:E25 Human-Made Feature]",
|
|
2060
2164
|
"properties": {
|
|
2061
2165
|
"condition": {
|
|
2062
|
-
"$ref": "#/definitions/ObjectAssumption%3CConditionState%3C(%
|
|
2166
|
+
"$ref": "#/definitions/ObjectAssumption%3CConditionState%3C(%22illegible%22)%3E%3E",
|
|
2063
2167
|
"description": "This can be used e.g. to indicate a perforation which is torn out or in any other way damaged. [ontology: crm:P44 has condition]"
|
|
2064
2168
|
},
|
|
2065
2169
|
"depiction": {
|
package/lib/spec/context.json
CHANGED
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"height": "reo:height",
|
|
132
132
|
"punchDiameter": "reo:punchDiameter",
|
|
133
133
|
"holeSeparation": "reo:holeSeparation",
|
|
134
|
+
"scanResolution": "reo:scanResolution",
|
|
134
135
|
"value": "crm:P90_has_value",
|
|
135
136
|
"measuredBy": "crmdig:L23_used_software_or_firmware",
|
|
136
137
|
"software": "rdfs:label",
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { type ErrorObject } from "ajv";
|
|
1
2
|
import { Edition } from "./Edition";
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Holds a document against the edition schema. `errors` says what the
|
|
5
|
+
* last call found wrong, and is empty where it found nothing.
|
|
6
|
+
*/
|
|
7
|
+
export interface ValidateEdition {
|
|
8
|
+
(document: unknown): document is Edition;
|
|
9
|
+
errors?: ErrorObject[] | null;
|
|
10
|
+
}
|
|
11
|
+
declare const validate: ValidateEdition;
|
|
3
12
|
export { validate };
|
package/lib/validate.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import * as schema from "./schema.json";
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const validate =
|
|
3
|
+
const compileEditionSchema = () => new Ajv({ strict: false, formats: { "date": true } }).compile(schema);
|
|
4
|
+
/**
|
|
5
|
+
* Compiling costs enough that a consumer which never validates should not
|
|
6
|
+
* pay for it, so the compiled form is made on first use and kept.
|
|
7
|
+
*/
|
|
8
|
+
let compiled;
|
|
9
|
+
const validate = (document) => {
|
|
10
|
+
compiled ?? (compiled = compileEditionSchema());
|
|
11
|
+
const valid = compiled(document);
|
|
12
|
+
validate.errors = compiled.errors;
|
|
13
|
+
return valid;
|
|
14
|
+
};
|
|
10
15
|
export { validate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"url": "git+https://github.com/pfefferniels/linked-rolls.git"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
11
12
|
"main": "lib/index.js",
|
|
12
13
|
"types": "lib/index.d.ts",
|
|
13
14
|
"exports": {
|