linked-rolls 0.43.0 → 0.44.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/README.md +79 -2
- package/lib/Collation.d.ts +74 -4
- package/lib/Collation.js +60 -13
- package/lib/Edition.d.ts +8 -2
- package/lib/Version.d.ts +8 -10
- package/lib/editionOps.d.ts +33 -1
- package/lib/editionOps.js +121 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/migrate.js +12 -1
- package/lib/readers/phillipsEroll.d.ts +16 -1
- package/lib/readers/phillipsEroll.js +16 -1
- package/lib/scatter.d.ts +164 -0
- package/lib/scatter.js +171 -0
- package/lib/schema.json +60 -12
- package/lib/statistics.d.ts +71 -0
- package/lib/statistics.js +121 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,83 @@ at. It then reads as the version it derives from, and
|
|
|
75
75
|
`reservationsAboutVersion` says so. A file that names a single
|
|
76
76
|
derivation loads as a list of one.
|
|
77
77
|
|
|
78
|
+
What a version is called is not stored with it. `siglaOf` reads the
|
|
79
|
+
sigla off the stemma as it stands, so a label never outlives the
|
|
80
|
+
arrangement it describes, and nothing should cite one without saying
|
|
81
|
+
which state of the edition it belongs to.
|
|
82
|
+
|
|
83
|
+
## The tolerance a derivation was collated at
|
|
84
|
+
|
|
85
|
+
`collationTolerance` on a derivation is the window two readings of one
|
|
86
|
+
symbol must fall in to be collated: how far apart they may lie at
|
|
87
|
+
either end, and, since two copies differ by a systematic offset as well
|
|
88
|
+
as by scatter, where that window is centred. A window naming no offset
|
|
89
|
+
is centred on nothing, which is what every window written before the
|
|
90
|
+
offset was held here means.
|
|
91
|
+
|
|
92
|
+
The number decides what counts as a reading at all, so it is worth
|
|
93
|
+
measuring rather than picking. `scatterOfCopy` measures how far one
|
|
94
|
+
copy puts each symbol from where the copies collated with it put it,
|
|
95
|
+
and `scatterOf` describes that sample: its median, its scatter as a
|
|
96
|
+
median absolute deviation scaled to a standard deviation, a histogram
|
|
97
|
+
with the normal curve to lay over it, and the readings the curve does
|
|
98
|
+
not account for. The scatter is taken from the median because the
|
|
99
|
+
displacements an editor is looking for are in the sample, and a
|
|
100
|
+
standard deviation would grow towards them until they no longer stood
|
|
101
|
+
out.
|
|
102
|
+
|
|
103
|
+
The tolerance that follows is the scatter times a threshold fixed by
|
|
104
|
+
the size of the sample, the point beyond which fewer than one reading
|
|
105
|
+
is expected to fall by chance (`departureThreshold`). For the few
|
|
106
|
+
hundred readings an edge of a stemma yields this lands a little above
|
|
107
|
+
three. `toleranceAcross` covers several samples with one window, since
|
|
108
|
+
a derivation states one tolerance while notes and expressions scatter
|
|
109
|
+
differently, and `inferredTolerance` builds the belief the number rests
|
|
110
|
+
on, to annotate `collationTolerance` with. It is held likely and not
|
|
111
|
+
true: the tolerance follows from the sample only as far as the sample
|
|
112
|
+
is normal, which is what the reported excess kurtosis and tail counts
|
|
113
|
+
are there to show.
|
|
114
|
+
|
|
115
|
+
Two limits are worth naming. Notes and expressions are estimated apart
|
|
116
|
+
by default, which is a stopgap standing in for a skew across the width
|
|
117
|
+
of the paper rather than a distinction in the model, so the grouping is
|
|
118
|
+
a parameter of the estimator and appears nowhere in the format. And the
|
|
119
|
+
method screens: it says which readings the curve does not account for,
|
|
120
|
+
and an editor says which of those are editorial acts.
|
|
121
|
+
|
|
122
|
+
A separation reports which end made it (`separatedBy`, and
|
|
123
|
+
`admittedAtEnds` for a collation). The two ends answer different
|
|
124
|
+
questions: the onset, with the kind, decides whether two copies read one
|
|
125
|
+
command, and the end decides whether that command was lengthened or
|
|
126
|
+
shortened. On welte225.org the end alone decides 24 collations of 2839
|
|
127
|
+
and the onset alone 39, so neither test is idle, but a difference at the
|
|
128
|
+
end is as often a punch measured badly as a punch genuinely prolonged,
|
|
129
|
+
and the two are not worth confusing.
|
|
130
|
+
|
|
131
|
+
## Collating a derivation a second time
|
|
132
|
+
|
|
133
|
+
A collated symbol is one symbol carrying every copy that reads it, so a
|
|
134
|
+
collation cannot simply be run again: there is no second symbol left to
|
|
135
|
+
match. `separateReadings` is the inverse. It takes one copy's carriers
|
|
136
|
+
back off the symbols it shares, gives them to new symbols of the
|
|
137
|
+
version's own, and states the exchange, leaving the edition in the state
|
|
138
|
+
a collation would have produced had nothing joined. `connectVersions` at
|
|
139
|
+
a new tolerance then re-collates, joining again whatever the new window
|
|
140
|
+
admits.
|
|
141
|
+
|
|
142
|
+
Nothing an editor established is lost by that. `connectVersions`
|
|
143
|
+
rewrites only what a collation wrote (`isCollationsOwn`): a bare
|
|
144
|
+
insertion or deletion, or an equivalence between two systems' spellings.
|
|
145
|
+
An edit naming what the change is, why it was made, or what it rests on
|
|
146
|
+
stays, and the symbols it speaks for are left out of the collation. An
|
|
147
|
+
equivalence counts as the collation's own, since it is derived from the
|
|
148
|
+
two systems' vocabularies rather than read off the paper and freezing it
|
|
149
|
+
would leave the transfers uncollatable, but one drawn a second time over
|
|
150
|
+
the very same symbols is kept as it stands, identifier and motivation
|
|
151
|
+
and all. Readings already separated by hand are carried by one copy
|
|
152
|
+
alone, so `separateReadings` passes over them and they keep their
|
|
153
|
+
identifiers.
|
|
154
|
+
|
|
78
155
|
## What brought a feature about
|
|
79
156
|
|
|
80
157
|
A copy states no list of features. Each feature stands in the act that
|
|
@@ -140,8 +217,8 @@ they carry it, a statement with the belief it rests on;
|
|
|
140
217
|
`versionsWitnessedBy` gathers the same from the side of a copy.
|
|
141
218
|
`carriageProblems` reports a statement made beside features that carry
|
|
142
219
|
symbols already, or one naming a version the edition lacks. A copy may
|
|
143
|
-
carry a `siglum`,
|
|
144
|
-
keeper.
|
|
220
|
+
carry a `siglum`, which is given by hand and not read off anything;
|
|
221
|
+
one without is named by its keeper.
|
|
145
222
|
|
|
146
223
|
```ts
|
|
147
224
|
import { reservationsAbout, stateSource } from 'linked-rolls'
|
package/lib/Collation.d.ts
CHANGED
|
@@ -1,17 +1,69 @@
|
|
|
1
|
+
import { Edit } from "./Edit";
|
|
1
2
|
import { HorizontalSpan } from "./Feature";
|
|
2
3
|
import { AnySymbol } from "./Symbol";
|
|
3
4
|
import { Millimeters } from "./Quantity";
|
|
5
|
+
/** A value taken at each end of a feature: where it begins and where it stops. */
|
|
6
|
+
export interface BothEnds<T> {
|
|
7
|
+
from: T;
|
|
8
|
+
to: T;
|
|
9
|
+
}
|
|
10
|
+
/** How far one text puts a feature from where another puts it, at either end. */
|
|
11
|
+
export type Displacement = BothEnds<Millimeters>;
|
|
4
12
|
/**
|
|
5
|
-
*
|
|
6
|
-
* at either end
|
|
13
|
+
* The window two readings of one feature must fall in to be collated:
|
|
14
|
+
* how far apart they may lie at either end, and where that window is
|
|
15
|
+
* centred.
|
|
16
|
+
*
|
|
17
|
+
* The centre is worth stating. Two copies of one roll differ by a
|
|
18
|
+
* systematic offset as well as by scatter, and a window centred on
|
|
19
|
+
* nothing has to be widened by the whole of that offset before it
|
|
20
|
+
* admits what the offset alone displaces. Naming the offset lets the
|
|
21
|
+
* tolerance stand for the scatter only. A window that names none is
|
|
22
|
+
* centred on nothing, which is what every window written before the
|
|
23
|
+
* offset was held here means.
|
|
7
24
|
*/
|
|
8
25
|
export interface CollationTolerance {
|
|
9
|
-
/**
|
|
26
|
+
/** How far the two readings may lie apart at the start of a feature, measured from `offsetStart`. */
|
|
10
27
|
toleranceStart: Millimeters;
|
|
11
|
-
/**
|
|
28
|
+
/** How far they may lie apart at the end, measured from `offsetEnd`. */
|
|
12
29
|
toleranceEnd: Millimeters;
|
|
30
|
+
/**
|
|
31
|
+
* How much later the collated copy puts the start of a feature than
|
|
32
|
+
* the copy it is read against, where the two differ systematically.
|
|
33
|
+
* Nothing where they do not.
|
|
34
|
+
*/
|
|
35
|
+
offsetStart?: Millimeters;
|
|
36
|
+
/** The same at the end of a feature. */
|
|
37
|
+
offsetEnd?: Millimeters;
|
|
13
38
|
}
|
|
14
39
|
export declare const defaultCollationTolerance: CollationTolerance;
|
|
40
|
+
/** Where the window is centred at the start of a feature, on nothing where it names no offset. */
|
|
41
|
+
export declare const offsetStartOf: (tolerance: CollationTolerance) => Millimeters;
|
|
42
|
+
/** Where the window is centred at the end of a feature. */
|
|
43
|
+
export declare const offsetEndOf: (tolerance: CollationTolerance) => Millimeters;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the window admits the displacement at each end, taken apart.
|
|
46
|
+
*
|
|
47
|
+
* The two ends answer different questions. The onset, with the kind,
|
|
48
|
+
* decides identity: whether the two copies read one command. The end
|
|
49
|
+
* decides duration: whether the same command was lengthened or
|
|
50
|
+
* shortened. A collation joins two readings only where both admit, so a
|
|
51
|
+
* separation may be the work of either, and an editor looking at one
|
|
52
|
+
* wants to know which.
|
|
53
|
+
*
|
|
54
|
+
* A difference at the end alone has at least three causes and only the
|
|
55
|
+
* first is an editorial act. A punch may genuinely have been lengthened
|
|
56
|
+
* or shortened. A chain of punches, bridged to keep the paper strong,
|
|
57
|
+
* may have been read as one perforation on one copy and as repeated
|
|
58
|
+
* notes on the other, which Phillips names as a common error of roll
|
|
59
|
+
* scanning and which runs in both directions (p. 173). Or a reading may
|
|
60
|
+
* report something other than the punched slot, as a pneumatic reader
|
|
61
|
+
* reports how long a valve stayed open. Long held notes are where the
|
|
62
|
+
* second is likeliest, since that is where the chains are.
|
|
63
|
+
*/
|
|
64
|
+
export declare const admittedAtEnds: (tolerance: CollationTolerance, displacement: Displacement) => BothEnds<boolean>;
|
|
65
|
+
/** Whether the window admits the displacement: within the tolerance of the offset, at both ends. */
|
|
66
|
+
export declare const admits: (tolerance: CollationTolerance, displacement: Displacement) => boolean;
|
|
15
67
|
/** Where a symbol lies along the roll, as its carriers put it, or nothing for a symbol without a place. */
|
|
16
68
|
export type Locate = (symbol: AnySymbol) => Readonly<HorizontalSpan> | undefined;
|
|
17
69
|
/**
|
|
@@ -26,3 +78,21 @@ export type Collation = {
|
|
|
26
78
|
};
|
|
27
79
|
/** Each of the own symbols with every inherited symbol it collates with, both in the order given. */
|
|
28
80
|
export declare const collationsOf: (own: readonly Readonly<AnySymbol>[], inherited: readonly Readonly<AnySymbol>[], locate: Locate, tolerance?: CollationTolerance) => Collation[];
|
|
81
|
+
/**
|
|
82
|
+
* Whether the edit is one a collation writes by itself: a bare
|
|
83
|
+
* insertion or a bare deletion saying nothing further, or an
|
|
84
|
+
* equivalence between two systems' spellings for one command.
|
|
85
|
+
*
|
|
86
|
+
* Collating again rewrites these and keeps the rest. An edit that says
|
|
87
|
+
* what the change is, or why it was made, or what it rests on, is an
|
|
88
|
+
* editor's reading of the difference between two texts, and collating
|
|
89
|
+
* the two again is no reason to discard it.
|
|
90
|
+
*
|
|
91
|
+
* An equivalence counts as the collation's own even where an editor
|
|
92
|
+
* added a motivation to it, because it is derived rather than read:
|
|
93
|
+
* freezing it would leave the transfers, where nearly every edit is
|
|
94
|
+
* one, unable to be collated again at all. What an editor wrote on it
|
|
95
|
+
* is not lost by that, since `connectVersions` keeps an equivalence it
|
|
96
|
+
* draws a second time over the very same symbols.
|
|
97
|
+
*/
|
|
98
|
+
export declare const isCollationsOwn: (edit: Readonly<Edit>) => boolean;
|
package/lib/Collation.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
import { keyOf } from "./TrackerBar";
|
|
2
2
|
import { distance, mm } from "./Quantity";
|
|
3
3
|
import { partitionPoint } from "./sorted";
|
|
4
|
+
import { groupBy } from "./utils";
|
|
4
5
|
export const defaultCollationTolerance = { toleranceStart: mm(5), toleranceEnd: mm(5) };
|
|
6
|
+
/** Where the window is centred at the start of a feature, on nothing where it names no offset. */
|
|
7
|
+
export const offsetStartOf = (tolerance) => tolerance.offsetStart ?? mm(0);
|
|
8
|
+
/** Where the window is centred at the end of a feature. */
|
|
9
|
+
export const offsetEndOf = (tolerance) => tolerance.offsetEnd ?? mm(0);
|
|
10
|
+
/**
|
|
11
|
+
* Whether the window admits the displacement at each end, taken apart.
|
|
12
|
+
*
|
|
13
|
+
* The two ends answer different questions. The onset, with the kind,
|
|
14
|
+
* decides identity: whether the two copies read one command. The end
|
|
15
|
+
* decides duration: whether the same command was lengthened or
|
|
16
|
+
* shortened. A collation joins two readings only where both admit, so a
|
|
17
|
+
* separation may be the work of either, and an editor looking at one
|
|
18
|
+
* wants to know which.
|
|
19
|
+
*
|
|
20
|
+
* A difference at the end alone has at least three causes and only the
|
|
21
|
+
* first is an editorial act. A punch may genuinely have been lengthened
|
|
22
|
+
* or shortened. A chain of punches, bridged to keep the paper strong,
|
|
23
|
+
* may have been read as one perforation on one copy and as repeated
|
|
24
|
+
* notes on the other, which Phillips names as a common error of roll
|
|
25
|
+
* scanning and which runs in both directions (p. 173). Or a reading may
|
|
26
|
+
* report something other than the punched slot, as a pneumatic reader
|
|
27
|
+
* reports how long a valve stayed open. Long held notes are where the
|
|
28
|
+
* second is likeliest, since that is where the chains are.
|
|
29
|
+
*/
|
|
30
|
+
export const admittedAtEnds = (tolerance, displacement) => ({
|
|
31
|
+
from: distance(displacement.from, offsetStartOf(tolerance)) <= tolerance.toleranceStart,
|
|
32
|
+
to: distance(displacement.to, offsetEndOf(tolerance)) <= tolerance.toleranceEnd
|
|
33
|
+
});
|
|
34
|
+
/** Whether the window admits the displacement: within the tolerance of the offset, at both ends. */
|
|
35
|
+
export const admits = (tolerance, displacement) => {
|
|
36
|
+
const ends = admittedAtEnds(tolerance, displacement);
|
|
37
|
+
return ends.from && ends.to;
|
|
38
|
+
};
|
|
5
39
|
/**
|
|
6
40
|
* What a symbol says, as a key: the pitch of a note, the type and scope
|
|
7
41
|
* of an expression. Symbols collate within one key only. This is the
|
|
@@ -10,8 +44,8 @@ export const defaultCollationTolerance = { toleranceStart: mm(5), toleranceEnd:
|
|
|
10
44
|
* what carries a note across a transfer between systems.
|
|
11
45
|
*/
|
|
12
46
|
const kindOf = (symbol) => symbol.type === 'text' ? 'text' : keyOf(symbol);
|
|
13
|
-
const
|
|
14
|
-
|
|
47
|
+
const displacementBetween = (here, there) => ({ from: mm(here.from - there.from), to: mm(here.to - there.to) });
|
|
48
|
+
const nearby = (here, there, tolerance) => admits(tolerance, displacementBetween(here, there));
|
|
15
49
|
/**
|
|
16
50
|
* Two symbols collate when they are of one kind, say the same thing
|
|
17
51
|
* (pitch, or expression type and scope), and lie at about the same
|
|
@@ -28,15 +62,6 @@ const placed = (symbols, locate) => symbols.flatMap((symbol, index) => {
|
|
|
28
62
|
const horizontal = locate(symbol);
|
|
29
63
|
return horizontal ? [{ symbol, index, horizontal }] : [];
|
|
30
64
|
});
|
|
31
|
-
const groupBy = (items, keyOf) => items.reduce((groups, item) => {
|
|
32
|
-
const key = keyOf(item);
|
|
33
|
-
const group = groups.get(key);
|
|
34
|
-
if (group)
|
|
35
|
-
group.push(item);
|
|
36
|
-
else
|
|
37
|
-
groups.set(key, [item]);
|
|
38
|
-
return groups;
|
|
39
|
-
}, new Map());
|
|
40
65
|
/** The placed symbols by kind, each kind in order of onset. */
|
|
41
66
|
const byKindInOrderOfOnset = (symbols) => {
|
|
42
67
|
const groups = groupBy(symbols, ({ symbol }) => kindOf(symbol));
|
|
@@ -47,8 +72,9 @@ const byKindInOrderOfOnset = (symbols) => {
|
|
|
47
72
|
const WINDOW_SLACK = 1e-9;
|
|
48
73
|
/** The symbols of a kind whose onset lies within the start tolerance of the span. */
|
|
49
74
|
const nearOnsetOf = (kind, span, tolerance) => {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
75
|
+
const centre = span.from - offsetStartOf(tolerance);
|
|
76
|
+
const lowest = centre - tolerance.toleranceStart - WINDOW_SLACK;
|
|
77
|
+
const highest = centre + tolerance.toleranceStart + WINDOW_SLACK;
|
|
52
78
|
const first = partitionPoint(kind, candidate => candidate.horizontal.from < lowest);
|
|
53
79
|
const end = partitionPoint(kind, candidate => candidate.horizontal.from <= highest);
|
|
54
80
|
return kind.slice(first, end);
|
|
@@ -61,3 +87,24 @@ export const collationsOf = (own, inherited, locate, tolerance = defaultCollatio
|
|
|
61
87
|
.sort((a, b) => a.index - b.index)
|
|
62
88
|
.map(({ symbol: counterpart }) => ({ symbol, counterpart })));
|
|
63
89
|
};
|
|
90
|
+
/** The edit type a collation draws by itself, from the two systems' vocabularies rather than off the paper. */
|
|
91
|
+
const drawnByCollation = 'replace-with-equivalent';
|
|
92
|
+
/**
|
|
93
|
+
* Whether the edit is one a collation writes by itself: a bare
|
|
94
|
+
* insertion or a bare deletion saying nothing further, or an
|
|
95
|
+
* equivalence between two systems' spellings for one command.
|
|
96
|
+
*
|
|
97
|
+
* Collating again rewrites these and keeps the rest. An edit that says
|
|
98
|
+
* what the change is, or why it was made, or what it rests on, is an
|
|
99
|
+
* editor's reading of the difference between two texts, and collating
|
|
100
|
+
* the two again is no reason to discard it.
|
|
101
|
+
*
|
|
102
|
+
* An equivalence counts as the collation's own even where an editor
|
|
103
|
+
* added a motivation to it, because it is derived rather than read:
|
|
104
|
+
* freezing it would leave the transfers, where nearly every edit is
|
|
105
|
+
* one, unable to be collated again at all. What an editor wrote on it
|
|
106
|
+
* is not lost by that, since `connectVersions` keeps an equivalence it
|
|
107
|
+
* draws a second time over the very same symbols.
|
|
108
|
+
*/
|
|
109
|
+
export const isCollationsOwn = (edit) => edit.editType === drawnByCollation
|
|
110
|
+
|| (edit.editType === undefined && edit.motivation === undefined && edit['@annotation'] === undefined);
|
package/lib/Edition.d.ts
CHANGED
|
@@ -29,9 +29,15 @@ export interface EditionCreation {
|
|
|
29
29
|
*/
|
|
30
30
|
publicationDate: Date;
|
|
31
31
|
/**
|
|
32
|
-
* The tolerance
|
|
33
|
-
*
|
|
32
|
+
* The tolerance every version of the edition was collated at,
|
|
33
|
+
* before each derivation stated its own. How precisely two copies
|
|
34
|
+
* place a symbol depends on which two they are, so the tolerance
|
|
35
|
+
* belongs to the derivation, `Derivation.collationTolerance`, and
|
|
36
|
+
* migration writes this one onto every derivation that gives none.
|
|
37
|
+
* Nothing else reads it. A window's offset is the displacement
|
|
38
|
+
* between two particular copies and so can never be stated here.
|
|
34
39
|
* Not exported to RDF.
|
|
40
|
+
* @deprecated
|
|
35
41
|
*/
|
|
36
42
|
collationTolerance?: CollationTolerance;
|
|
37
43
|
}
|
package/lib/Version.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Edit } from "./Edit";
|
|
2
2
|
import { Concept } from "./Agent";
|
|
3
|
-
import { ActorAssignment, Belief, Certainty, DateAssignment, ReferenceAssumption } from "./Assumption";
|
|
3
|
+
import { ActorAssignment, Belief, Certainty, DateAssignment, ObjectAssumption, ReferenceAssumption } from "./Assumption";
|
|
4
4
|
import { CollationTolerance } from "./Collation";
|
|
5
5
|
import { AnySymbol } from "./Symbol";
|
|
6
6
|
import { WithId, WithNote, WithType } from "./utils";
|
|
@@ -22,8 +22,14 @@ export type Derivation = ReferenceAssumption & {
|
|
|
22
22
|
* The tolerance at which the derived version was collated against
|
|
23
23
|
* the one it is based on. A derivation written before the tolerance
|
|
24
24
|
* was held here states none. Not exported to RDF.
|
|
25
|
+
*
|
|
26
|
+
* It is an object assumption, so that a tolerance calculated from
|
|
27
|
+
* the scatter of the readings can carry the inference it was drawn
|
|
28
|
+
* by. It is the one quantity in the edition that decides what
|
|
29
|
+
* counts as a reading at all, and a number arrived at by a method
|
|
30
|
+
* should say so.
|
|
25
31
|
*/
|
|
26
|
-
collationTolerance?: CollationTolerance
|
|
32
|
+
collationTolerance?: ObjectAssumption<CollationTolerance>;
|
|
27
33
|
};
|
|
28
34
|
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
29
35
|
export declare const collationToleranceOf: (derivation: Readonly<Derivation>) => CollationTolerance;
|
|
@@ -66,14 +72,6 @@ export interface VersionCreation {
|
|
|
66
72
|
* @see lrmoo:F2 Expression
|
|
67
73
|
*/
|
|
68
74
|
export interface Version extends WithId, WithType<'Version'> {
|
|
69
|
-
/**
|
|
70
|
-
* A label a document carries from before the sigla were computed.
|
|
71
|
-
* Nothing should write one here and nothing should show one: what a
|
|
72
|
-
* version is called comes from `siglaOf`.
|
|
73
|
-
* @deprecated
|
|
74
|
-
* @see reo:siglum
|
|
75
|
-
*/
|
|
76
|
-
siglum?: string;
|
|
77
75
|
/**
|
|
78
76
|
* The reproducing system this version is coded for. One roll was
|
|
79
77
|
* often issued for several of them, and a version is a reading in
|
package/lib/editionOps.d.ts
CHANGED
|
@@ -157,14 +157,46 @@ export declare const mergeFeatures: (copyId: string, featureIds: readonly string
|
|
|
157
157
|
* and the child lacks becomes its deletions. The derivation states the
|
|
158
158
|
* tolerance it was collated at and becomes the principal one; the
|
|
159
159
|
* hypotheses the child stated beside its former one stay.
|
|
160
|
+
*
|
|
161
|
+
* The edits the child already stated are rewritten only as far as a
|
|
162
|
+
* collation wrote them (`isCollationsOwn`). An editor's reading of the
|
|
163
|
+
* difference between the two texts stays, and the symbols it speaks for
|
|
164
|
+
* are left out of the collation, so that connecting the two again
|
|
165
|
+
* neither doubles them nor silently drops what somebody established by
|
|
166
|
+
* hand. An equivalence is the collation's own, since it is drawn from
|
|
167
|
+
* the two systems' vocabularies rather than read off the paper, but one
|
|
168
|
+
* drawn again over the very same symbols is kept as it stands, with its
|
|
169
|
+
* identifier and whatever was written on it.
|
|
170
|
+
*
|
|
171
|
+
* The child's own text is what it shows less what the parent hands
|
|
172
|
+
* down, so that connecting two versions already connected collates the
|
|
173
|
+
* child's symbols and not the parent's with themselves.
|
|
160
174
|
*/
|
|
161
|
-
export declare const connectVersions: (view: EditionView, childId: string, parentId: string, tolerance?: CollationTolerance) => EditionOp;
|
|
175
|
+
export declare const connectVersions: (view: EditionView, childId: string, parentId: string, tolerance?: ObjectAssumption<CollationTolerance>) => EditionOp;
|
|
162
176
|
/**
|
|
163
177
|
* Folds the version's own symbols into those it inherits and collates
|
|
164
178
|
* with: the carriers pass over, and the insertions go. Collates at the
|
|
165
179
|
* tolerance of the derivation, where the caller names none.
|
|
166
180
|
*/
|
|
167
181
|
export declare const collateSymbols: (view: EditionView, versionId: string, symbolIds: readonly string[], tolerance?: CollationTolerance) => EditionOp;
|
|
182
|
+
/**
|
|
183
|
+
* Takes the copy's reading of a symbol back out of the symbol it was
|
|
184
|
+
* collated into: the copy's carriers pass to a new symbol of the
|
|
185
|
+
* version's own, the other copies keep the symbol they had, and the
|
|
186
|
+
* version states the exchange.
|
|
187
|
+
*
|
|
188
|
+
* This is the inverse of the hand-over a collation makes, and it is
|
|
189
|
+
* what lets a derivation be collated a second time. A collated symbol
|
|
190
|
+
* is one symbol carrying every copy that reads it, so nothing else
|
|
191
|
+
* takes the two readings apart again, and without that a tolerance
|
|
192
|
+
* arrived at after the fact could never be applied.
|
|
193
|
+
*
|
|
194
|
+
* Symbols the copy alone carries are already the version's own and are
|
|
195
|
+
* passed over, so a reading somebody has separated by hand keeps its
|
|
196
|
+
* identifier and the edit that speaks for it. Named symbols narrow the
|
|
197
|
+
* act to those; naming none separates the copy's whole reading.
|
|
198
|
+
*/
|
|
199
|
+
export declare const separateReadings: (view: EditionView, versionId: string, copyId: string, symbolIds?: readonly string[]) => EditionOp;
|
|
168
200
|
/**
|
|
169
201
|
* Makes the version stand on its own: what it inherited becomes its
|
|
170
202
|
* own insertions, and its derivations go, the hypotheses among them,
|
package/lib/editionOps.js
CHANGED
|
@@ -2,7 +2,7 @@ import { current, isDraft } from "immer";
|
|
|
2
2
|
import { v4 } from "uuid";
|
|
3
3
|
import { getAt } from "./EditionView";
|
|
4
4
|
import { isCommand, placementRelations } from "./Symbol";
|
|
5
|
-
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
5
|
+
import { collationsOf, defaultCollationTolerance, isCollationsOwn } from "./Collation";
|
|
6
6
|
import { collationToleranceOf, editsOf, insertedBy, principalDerivationOf } from "./Version";
|
|
7
7
|
import { asSymbols, barOf, featuresByAct, featuresOf, isPaperStretch, statesNothing } from "./RollCopy";
|
|
8
8
|
import { systemOf } from "./TrackerBar";
|
|
@@ -651,6 +651,20 @@ const dropDerivations = (version, matches) => {
|
|
|
651
651
|
else
|
|
652
652
|
delete version.basedOn;
|
|
653
653
|
};
|
|
654
|
+
/** The symbols a set of edits speaks for, whether by inserting or by deleting them. */
|
|
655
|
+
const spokenForBy = (edits) => new Set(edits.flatMap(edit => [
|
|
656
|
+
...(edit.insert ?? []).map(symbol => symbol.id),
|
|
657
|
+
...(edit.delete ?? [])
|
|
658
|
+
]));
|
|
659
|
+
/** An exchange of symbols, as a key: what it puts in against what it takes out. */
|
|
660
|
+
const exchangeKey = (inserted, deleted) => `${[...inserted].sort().join(',')}/${[...deleted].sort().join(',')}`;
|
|
661
|
+
/**
|
|
662
|
+
* The edits already stated, by the exchange each of them makes. An
|
|
663
|
+
* equivalence the collation draws a second time is the one already
|
|
664
|
+
* stated, and drawing it again is no reason to mint a new identifier or
|
|
665
|
+
* to drop the motivation somebody wrote on it.
|
|
666
|
+
*/
|
|
667
|
+
const byExchange = (edits) => new Map(edits.map(edit => [exchangeKey((edit.insert ?? []).map(symbol => symbol.id), edit.delete ?? []), edit]));
|
|
654
668
|
/**
|
|
655
669
|
* Bases the child on the parent. A symbol of the child that collates
|
|
656
670
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
@@ -658,10 +672,36 @@ const dropDerivations = (version, matches) => {
|
|
|
658
672
|
* and the child lacks becomes its deletions. The derivation states the
|
|
659
673
|
* tolerance it was collated at and becomes the principal one; the
|
|
660
674
|
* hypotheses the child stated beside its former one stay.
|
|
675
|
+
*
|
|
676
|
+
* The edits the child already stated are rewritten only as far as a
|
|
677
|
+
* collation wrote them (`isCollationsOwn`). An editor's reading of the
|
|
678
|
+
* difference between the two texts stays, and the symbols it speaks for
|
|
679
|
+
* are left out of the collation, so that connecting the two again
|
|
680
|
+
* neither doubles them nor silently drops what somebody established by
|
|
681
|
+
* hand. An equivalence is the collation's own, since it is drawn from
|
|
682
|
+
* the two systems' vocabularies rather than read off the paper, but one
|
|
683
|
+
* drawn again over the very same symbols is kept as it stands, with its
|
|
684
|
+
* identifier and whatever was written on it.
|
|
685
|
+
*
|
|
686
|
+
* The child's own text is what it shows less what the parent hands
|
|
687
|
+
* down, so that connecting two versions already connected collates the
|
|
688
|
+
* child's symbols and not the parent's with themselves.
|
|
661
689
|
*/
|
|
662
690
|
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
663
|
-
const
|
|
664
|
-
const
|
|
691
|
+
const child = view.get(childId);
|
|
692
|
+
const stated = child ? editsOf(child) : [];
|
|
693
|
+
const established = stated.filter(edit => !isCollationsOwn(edit));
|
|
694
|
+
const spokenFor = spokenForBy(established);
|
|
695
|
+
const unspoken = (symbol) => !spokenFor.has(symbol.id);
|
|
696
|
+
// What the parent hands down and the child still shows passes
|
|
697
|
+
// through: it is neither the child's own symbol nor one it lacks.
|
|
698
|
+
// Without that, connecting a pair already connected would collate
|
|
699
|
+
// the inherited symbols with themselves and double their carriers.
|
|
700
|
+
const handedDown = view.snapshot(parentId).filter(unspoken);
|
|
701
|
+
const shown = new Set(view.snapshot(childId).map(symbol => symbol.id));
|
|
702
|
+
const inheritedIds = new Set(handedDown.map(symbol => symbol.id));
|
|
703
|
+
const inherited = handedDown.filter(symbol => !shown.has(symbol.id));
|
|
704
|
+
const own = view.snapshot(childId).filter(symbol => unspoken(symbol) && !inheritedIds.has(symbol.id));
|
|
665
705
|
const locate = (symbol) => view.placeOf(symbol);
|
|
666
706
|
const collations = collationsOf(own, inherited, locate, tolerance);
|
|
667
707
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
@@ -673,18 +713,25 @@ export const connectVersions = (view, childId, parentId, tolerance = defaultColl
|
|
|
673
713
|
* apart would make the apparatus a list of unexplained losses beside
|
|
674
714
|
* a list of unexplained gains.
|
|
675
715
|
*/
|
|
676
|
-
const substituted = differ(
|
|
716
|
+
const substituted = differ(child, view.get(parentId))
|
|
677
717
|
? substitutionsBetween(own.filter(symbol => !collated.has(symbol.id)), inherited.filter(symbol => !matched.has(symbol.id)), locate, tolerance)
|
|
678
718
|
: [];
|
|
679
719
|
const paired = new Set(substituted.flatMap(({ replaced, by }) => [...by, ...replaced].map(symbol => symbol.id)));
|
|
680
|
-
const
|
|
681
|
-
|
|
720
|
+
const alreadyStated = byExchange(stated);
|
|
721
|
+
const equivalence = ({ replaced, by }) => {
|
|
722
|
+
const inserted = by.map(symbol => symbol.id);
|
|
723
|
+
const deleted = replaced.map(symbol => symbol.id);
|
|
724
|
+
return alreadyStated.get(exchangeKey(inserted, deleted)) ?? {
|
|
682
725
|
type: 'edit',
|
|
683
726
|
id: v4(),
|
|
684
727
|
editType: 'replace-with-equivalent',
|
|
685
728
|
insert: [...by],
|
|
686
|
-
delete:
|
|
687
|
-
}
|
|
729
|
+
delete: deleted
|
|
730
|
+
};
|
|
731
|
+
};
|
|
732
|
+
const edits = [
|
|
733
|
+
...established,
|
|
734
|
+
...substituted.map(equivalence),
|
|
688
735
|
...own.filter(symbol => !collated.has(symbol.id) && !paired.has(symbol.id)).map(insertion),
|
|
689
736
|
...inherited
|
|
690
737
|
.filter(symbol => !matched.has(symbol.id) && !paired.has(symbol.id))
|
|
@@ -718,6 +765,72 @@ export const collateSymbols = (view, versionId, symbolIds, tolerance) => {
|
|
|
718
765
|
dropInsertions(version, collated);
|
|
719
766
|
});
|
|
720
767
|
};
|
|
768
|
+
/** The symbol as one copy reads it: what it says, on the given carriers, standing in no relation of its own. */
|
|
769
|
+
const readingOf = (symbol, carriers) => {
|
|
770
|
+
const identity = { id: v4(), carriers };
|
|
771
|
+
switch (symbol.type) {
|
|
772
|
+
case 'note':
|
|
773
|
+
return { type: 'note', pitch: symbol.pitch, ...identity };
|
|
774
|
+
case 'expression':
|
|
775
|
+
return { type: 'expression', expressionType: symbol.expressionType, scope: symbol.scope, ...identity };
|
|
776
|
+
case 'text':
|
|
777
|
+
return { type: 'text', text: symbol.text, ...identity };
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
/** The symbol where the copy and at least one other carry it, with the carriers of each side. */
|
|
781
|
+
const sharedWith = (view, symbol, copyId) => {
|
|
782
|
+
const onCopy = (carrier) => view.copyOf(idOf(carrier))?.id === copyId;
|
|
783
|
+
const mine = symbol.carriers.filter(onCopy);
|
|
784
|
+
const theirs = symbol.carriers.filter(carrier => !onCopy(carrier));
|
|
785
|
+
return mine.length > 0 && theirs.length > 0 ? [{ symbol, mine, theirs }] : [];
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* Takes the copy's reading of a symbol back out of the symbol it was
|
|
789
|
+
* collated into: the copy's carriers pass to a new symbol of the
|
|
790
|
+
* version's own, the other copies keep the symbol they had, and the
|
|
791
|
+
* version states the exchange.
|
|
792
|
+
*
|
|
793
|
+
* This is the inverse of the hand-over a collation makes, and it is
|
|
794
|
+
* what lets a derivation be collated a second time. A collated symbol
|
|
795
|
+
* is one symbol carrying every copy that reads it, so nothing else
|
|
796
|
+
* takes the two readings apart again, and without that a tolerance
|
|
797
|
+
* arrived at after the fact could never be applied.
|
|
798
|
+
*
|
|
799
|
+
* Symbols the copy alone carries are already the version's own and are
|
|
800
|
+
* passed over, so a reading somebody has separated by hand keeps its
|
|
801
|
+
* identifier and the edit that speaks for it. Named symbols narrow the
|
|
802
|
+
* act to those; naming none separates the copy's whole reading.
|
|
803
|
+
*/
|
|
804
|
+
export const separateReadings = (view, versionId, copyId, symbolIds) => {
|
|
805
|
+
const version = view.get(versionId);
|
|
806
|
+
if (!version)
|
|
807
|
+
return noChange;
|
|
808
|
+
const chosen = symbolIds && new Set(symbolIds);
|
|
809
|
+
const shared = view.snapshot(versionId)
|
|
810
|
+
.filter(symbol => chosen === undefined || chosen.has(symbol.id))
|
|
811
|
+
.flatMap(symbol => sharedWith(view, symbol, copyId));
|
|
812
|
+
if (shared.length === 0)
|
|
813
|
+
return noChange;
|
|
814
|
+
const inserted = new Set(insertedBy(version).map(symbol => symbol.id));
|
|
815
|
+
const stated = editsOf(version);
|
|
816
|
+
const separations = shared.map(({ symbol, mine }) => ({
|
|
817
|
+
type: 'edit',
|
|
818
|
+
id: v4(),
|
|
819
|
+
insert: [readingOf(symbol, [...mine])],
|
|
820
|
+
// A symbol the version inserts itself stays where it is, minus
|
|
821
|
+
// the carriers that leave it. Only one it inherits is exchanged.
|
|
822
|
+
...(inserted.has(symbol.id) ? {} : { delete: [symbol.id] })
|
|
823
|
+
}));
|
|
824
|
+
return onVersion(versionId, (version, draft) => {
|
|
825
|
+
shared.forEach(({ symbol, theirs }) => {
|
|
826
|
+
const path = view.getPath(symbol.id);
|
|
827
|
+
const target = path && getAt(path, draft);
|
|
828
|
+
if (target)
|
|
829
|
+
target.carriers = theirs;
|
|
830
|
+
});
|
|
831
|
+
version.edits = [...stated, ...separations];
|
|
832
|
+
});
|
|
833
|
+
};
|
|
721
834
|
/**
|
|
722
835
|
* Makes the version stand on its own: what it inherited becomes its
|
|
723
836
|
* own insertions, and its derivations go, the hypotheses among them,
|
package/lib/index.d.ts
CHANGED
|
@@ -22,8 +22,10 @@ export * from './vocabulary';
|
|
|
22
22
|
export * from './reservations';
|
|
23
23
|
export * from './witnesses';
|
|
24
24
|
export * from './alignment';
|
|
25
|
+
export * from './statistics';
|
|
25
26
|
export * from './Edition';
|
|
26
27
|
export * from './EditionView';
|
|
28
|
+
export * from './scatter';
|
|
27
29
|
export * from './editionOps';
|
|
28
30
|
export * from './Emulation';
|
|
29
31
|
export * from './validate';
|
package/lib/index.js
CHANGED
|
@@ -22,8 +22,10 @@ export * from './vocabulary';
|
|
|
22
22
|
export * from './reservations';
|
|
23
23
|
export * from './witnesses';
|
|
24
24
|
export * from './alignment';
|
|
25
|
+
export * from './statistics';
|
|
25
26
|
export * from './Edition';
|
|
26
27
|
export * from './EditionView';
|
|
28
|
+
export * from './scatter';
|
|
27
29
|
export * from './editionOps';
|
|
28
30
|
export * from './Emulation';
|
|
29
31
|
export * from './validate';
|
package/lib/migrate.js
CHANGED
|
@@ -40,6 +40,17 @@ const withoutVersionType = (node) => {
|
|
|
40
40
|
const { versionType: _retired, ...rest } = node;
|
|
41
41
|
return rest;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* A version carried the label it was cited under, from before the sigla
|
|
45
|
+
* were read off the stemma. A copy keeps its siglum, which is given by
|
|
46
|
+
* hand and derives from nothing.
|
|
47
|
+
*/
|
|
48
|
+
const withoutVersionSiglum = (node) => {
|
|
49
|
+
if (node['@type'] !== 'Version' || !Object.hasOwn(node, 'siglum'))
|
|
50
|
+
return node;
|
|
51
|
+
const { siglum: _labelled, ...rest } = node;
|
|
52
|
+
return rest;
|
|
53
|
+
};
|
|
43
54
|
const withLowerCaseTerms = (node) => Object.entries(lowerCasedTerms).reduce((result, [key, terms]) => {
|
|
44
55
|
const lowered = terms[result[key]];
|
|
45
56
|
return lowered ? { ...result, [key]: lowered } : result;
|
|
@@ -221,7 +232,7 @@ const withTimeSpanDates = (node) => {
|
|
|
221
232
|
const { '@value': within, '@type': _typed, ...rest } = node;
|
|
222
233
|
return { ...rest, within };
|
|
223
234
|
};
|
|
224
|
-
const migrateNode = (node) => [withRenamedKeys, withTypology, withoutVersionType, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
|
|
235
|
+
const migrateNode = (node) => [withRenamedKeys, withTypology, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
|
|
225
236
|
withProductionNodes, withScale, withDerivationList, withReadingKind, withTimeSpanDates, withoutFeatureKind,
|
|
226
237
|
withBorneFeaturesNamed, withFeaturesInActs]
|
|
227
238
|
.reduce((result, step) => step(result), node);
|
|
@@ -14,7 +14,22 @@ import { Millimeters, Seconds } from "../Quantity";
|
|
|
14
14
|
* elapsed time, not paper, and the take-up spool accelerates the paper
|
|
15
15
|
* as it fills, so `placeAt` has to put the time back onto the paper.
|
|
16
16
|
* And a switch stays open longer than its perforation is long, so a
|
|
17
|
-
* hole read here runs past the punched one
|
|
17
|
+
* hole read here runs past the punched one: Phillips measures the
|
|
18
|
+
* pneumatic on-time as exceeding the apparent length of a perforation
|
|
19
|
+
* (p. 178), and names what it depends on as the length of the
|
|
20
|
+
* perforation, the height of the tracker bar hole and the paper speed
|
|
21
|
+
* (p. 180).
|
|
22
|
+
*
|
|
23
|
+
* That extension is not subtracted. Doing so would need the absolute
|
|
24
|
+
* hole height of the Welte bar, where he gives only the 0.5 mm by which
|
|
25
|
+
* it exceeds the Licensee's. So the end of a hole on this copy is a
|
|
26
|
+
* pneumatic on-time where the end of a scanned copy's hole is a punched
|
|
27
|
+
* slot, and the two are not the same quantity. It shows in a collation:
|
|
28
|
+
* across welte225.org his notes run about 1.8 mm longer than the other
|
|
29
|
+
* copies read them, while the onsets agree. Until it is corrected, a
|
|
30
|
+
* comparison of hole ends against this copy measures the reader as much
|
|
31
|
+
* as the roll, and `offsetEnd` on the derivation absorbs the systematic
|
|
32
|
+
* part of it.
|
|
18
33
|
*/
|
|
19
34
|
/**
|
|
20
35
|
* He numbers his files by two rules at once, which his free samples
|