@docx4j/core-ts 0.4.0 → 0.6.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/NOTICE +4 -0
- package/README.md +34 -3
- package/dist/html/convert.mjs +40 -15
- package/dist/html/elements.mjs +2 -1
- package/dist/model/color/ColorMath.d.mts +29 -0
- package/dist/model/color/ColorMath.mjs +146 -0
- package/dist/model/color/ColorTables.d.mts +6 -0
- package/dist/model/color/ColorTables.mjs +83 -0
- package/dist/model/color/DrawingMLColors.d.mts +52 -0
- package/dist/model/color/DrawingMLColors.mjs +374 -0
- package/dist/model/color/ResolvedColor.d.mts +36 -0
- package/dist/model/color/ResolvedColor.mjs +65 -0
- package/dist/model/color/SpreadsheetColors.d.mts +36 -0
- package/dist/model/color/SpreadsheetColors.mjs +84 -0
- package/dist/model/color/StyleFill.d.mts +28 -0
- package/dist/model/color/StyleFill.mjs +21 -0
- package/dist/model/color/ThemeColors.d.mts +86 -0
- package/dist/model/color/ThemeColors.mjs +304 -0
- package/dist/model/color/WordColors.d.mts +71 -0
- package/dist/model/color/WordColors.mjs +121 -0
- package/dist/model/color/index.d.mts +6 -0
- package/dist/model/color/index.mjs +30 -0
- package/dist/model/content/Body.d.mts +153 -19
- package/dist/model/content/Body.mjs +570 -131
- package/dist/model/content/Comment.d.mts +21 -3
- package/dist/model/content/Comment.mjs +202 -19
- package/dist/model/content/ContentControl.d.mts +197 -18
- package/dist/model/content/ContentControl.mjs +664 -95
- package/dist/model/content/Font.d.mts +67 -17
- package/dist/model/content/Font.mjs +114 -35
- package/dist/model/content/InlinePicture.d.mts +82 -7
- package/dist/model/content/InlinePicture.mjs +221 -28
- package/dist/model/content/List.mjs +13 -1
- package/dist/model/content/Paragraph.d.mts +213 -14
- package/dist/model/content/Paragraph.mjs +936 -100
- package/dist/model/content/Range.d.mts +242 -11
- package/dist/model/content/Range.mjs +975 -119
- package/dist/model/content/ResolvedView.d.mts +104 -0
- package/dist/model/content/ResolvedView.mjs +596 -0
- package/dist/model/content/Table.d.mts +29 -3
- package/dist/model/content/Table.mjs +109 -33
- package/dist/model/content/TrackedChange.d.mts +301 -16
- package/dist/model/content/TrackedChange.mjs +1284 -158
- package/dist/model/content/binder.d.mts +2 -1
- package/dist/model/content/binder.mjs +48 -1
- package/dist/model/content/comments.d.mts +20 -6
- package/dist/model/content/comments.mjs +67 -57
- package/dist/model/content/generalException.d.mts +18 -0
- package/dist/model/content/generalException.mjs +24 -0
- package/dist/model/content/index.d.mts +8 -3
- package/dist/model/content/index.mjs +7 -2
- package/dist/model/content/mathText.d.mts +21 -0
- package/dist/model/content/mathText.mjs +267 -0
- package/dist/model/content/officeText.d.mts +27 -0
- package/dist/model/content/officeText.mjs +74 -0
- package/dist/model/content/outline.d.mts +5 -0
- package/dist/model/content/outline.mjs +13 -0
- package/dist/model/content/placeholders.d.mts +110 -0
- package/dist/model/content/placeholders.mjs +253 -0
- package/dist/model/content/relationships.d.mts +30 -0
- package/dist/model/content/relationships.mjs +133 -0
- package/dist/model/content/search.d.mts +72 -0
- package/dist/model/content/search.mjs +204 -2
- package/dist/model/content/spanEdits.d.mts +159 -0
- package/dist/model/content/spanEdits.mjs +728 -0
- package/dist/model/content/story.d.mts +44 -0
- package/dist/model/content/story.mjs +92 -0
- package/dist/model/content/tableColumns.d.mts +1 -1
- package/dist/model/content/tableColumns.mjs +108 -80
- package/dist/model/content/tableRevisions.d.mts +11 -3
- package/dist/model/content/tableRevisions.mjs +72 -42
- package/dist/model/content/tableRules.d.mts +30 -0
- package/dist/model/content/tableRules.mjs +100 -0
- package/dist/model/content/textBoxes.d.mts +18 -0
- package/dist/model/content/textBoxes.mjs +105 -0
- package/dist/model/content/trackedChangeFilter.d.mts +39 -0
- package/dist/model/content/trackedChangeFilter.mjs +81 -0
- package/dist/model/content/tracking.d.mts +28 -0
- package/dist/model/content/tracking.mjs +149 -10
- package/dist/model/content/tree.d.mts +212 -13
- package/dist/model/content/tree.mjs +356 -35
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
- package/dist/model/customxml/XmlMapping.d.mts +1 -1
- package/dist/model/customxml/XmlMapping.mjs +11 -0
- package/dist/model/customxml/bindings.d.mts +21 -2
- package/dist/model/customxml/bindings.mjs +76 -32
- package/dist/model/customxml/kinds.d.mts +8 -3
- package/dist/model/customxml/kinds.mjs +26 -5
- package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
- package/dist/model/customxml/picturePlaceholder.mjs +32 -0
- package/dist/model/customxml/pictures.d.mts +9 -5
- package/dist/model/customxml/pictures.mjs +31 -13
- package/dist/model/customxml/xhtml.mjs +8 -2
- package/dist/model/fonts/families.generated.mjs +2 -2
- package/dist/model/fonts/registry.mjs +7 -1
- package/dist/model/fonts/substitutions.generated.mjs +2 -2
- package/dist/model/fonts/themes.generated.mjs +1 -1
- package/dist/model/index.d.mts +2 -0
- package/dist/model/index.mjs +4 -0
- package/dist/model/listnumbering/Emulator.d.mts +37 -5
- package/dist/model/listnumbering/Emulator.mjs +63 -9
- package/dist/model/listnumbering/definitions.mjs +18 -0
- package/dist/model/properties/PropertyResolver.d.mts +87 -7
- package/dist/model/properties/PropertyResolver.mjs +197 -17
- package/dist/model/properties/catalogue.d.mts +32 -4
- package/dist/model/properties/catalogue.mjs +115 -36
- package/dist/model/properties/log.d.mts +1 -1
- package/dist/model/properties/log.mjs +5 -5
- package/dist/model/properties/table.d.mts +16 -2
- package/dist/model/properties/table.mjs +17 -5
- package/dist/model/text/Extractor.d.mts +289 -0
- package/dist/model/text/Extractor.mjs +2162 -0
- package/dist/model/text/LinkTargets.d.mts +45 -0
- package/dist/model/text/LinkTargets.mjs +170 -0
- package/dist/model/text/MarkdownExporter.d.mts +140 -0
- package/dist/model/text/MarkdownExporter.mjs +255 -0
- package/dist/model/text/PlainText.d.mts +29 -0
- package/dist/model/text/PlainText.mjs +539 -0
- package/dist/model/text/TextContext.d.mts +161 -0
- package/dist/model/text/TextContext.mjs +425 -0
- package/dist/model/text/TextOptions.d.mts +150 -0
- package/dist/model/text/TextOptions.mjs +278 -0
- package/dist/model/text/TextResult.d.mts +504 -0
- package/dist/model/text/TextResult.mjs +526 -0
- package/dist/model/text/Texts.d.mts +99 -0
- package/dist/model/text/Texts.mjs +187 -0
- package/dist/model/text/WmlToMarkdown.d.mts +120 -0
- package/dist/model/text/WmlToMarkdown.mjs +911 -0
- package/dist/model/text/commonmark.d.mts +121 -0
- package/dist/model/text/commonmark.mjs +655 -0
- package/dist/model/text/index.d.mts +7 -0
- package/dist/model/text/index.mjs +8 -0
- package/dist/model/text/normalize.d.mts +8 -0
- package/dist/model/text/normalize.mjs +13 -0
- package/dist/model/text/outline.d.mts +4 -0
- package/dist/model/text/outline.mjs +71 -0
- package/dist/office-js/fragment.d.mts +7 -5
- package/dist/office-js/fragment.mjs +63 -14
- package/dist/office-js/proxy.d.mts +8 -0
- package/dist/office-js/proxy.mjs +29 -0
- package/dist/office-js/run.mjs +2 -0
- package/dist/office-js/supported.generated.mjs +16 -0
- package/dist/office-js/toApiScript.mjs +21 -5
- package/dist/opc/Load.d.mts +9 -2
- package/dist/opc/Save.d.mts +17 -0
- package/dist/opc/Save.mjs +12 -2
- package/dist/opc/index.d.mts +3 -3
- package/dist/opc/index.mjs +2 -2
- package/dist/opc/mce/preprocessor.d.mts +88 -7
- package/dist/opc/mce/preprocessor.mjs +144 -37
- package/dist/opc/mce/understood.d.mts +34 -0
- package/dist/opc/mce/understood.mjs +55 -3
- package/dist/packages/OpcPackage.d.mts +20 -0
- package/dist/packages/OpcPackage.mjs +16 -3
- package/dist/packages/WordprocessingMLPackage.d.mts +77 -7
- package/dist/packages/WordprocessingMLPackage.mjs +242 -11
- package/dist/parts/DefaultXmlPart.mjs +2 -2
- package/dist/parts/Part.d.mts +10 -2
- package/dist/parts/XmlPart.d.mts +35 -32
- package/dist/parts/XmlPart.mjs +107 -107
- package/dist/parts/wml/comments.mjs +30 -0
- package/dist/parts/wml/index.mjs +12 -0
- package/dist/xml/dom.d.mts +7 -0
- package/dist/xml/dom.mjs +18 -0
- package/package.json +2 -2
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import { textOf } from '@docx4j/generated-objects-ts/builders/wml';
|
|
2
2
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
3
|
-
import { typeNameOf, runItemsOf, revisionKindOf, linkParents, segmentsOf, textOfView, cellsOf, rowsOf, childrenOf, W_NS, itemTextOf } from './tree.mjs';
|
|
3
|
+
import { typeNameOf, runItemsOf, revisionKindOf, linkParents, segmentsOf, textOfView, cellsOf, rowsOf, childrenOf, W_NS, itemTextOf, selectedBranchOf, branchesOf, BRANCH_TYPES, ALTERNATE_CONTENT } from './tree.mjs';
|
|
4
4
|
import { removeMarkers } from './comments.mjs';
|
|
5
5
|
import { Range } from './Range.mjs';
|
|
6
|
+
import { storyLayout, storyOf as storyOfParagraph } from './story.mjs';
|
|
6
7
|
import { rPrFromElements } from '@docx4j/generated-objects-ts/builders/wml';
|
|
7
8
|
import { revisionDateOf, toRestoredText, toDeletedText, restoreRPr, restorePPr, pruneParagraphProperties, HOLDER_TYPES, holderItemsOf } from './tracking.mjs';
|
|
8
9
|
import { tableRecordsOf, hasTableRecords, tableChangeText, rowText as tableRowText, acceptTable, rejectTable } from './tableRevisions.mjs';
|
|
9
10
|
import { deepCopy } from '@docx4j/generated-objects-ts';
|
|
11
|
+
import { relationshipIdsIn, dropRelationshipsUnused, queueRelationshipRemovals } from './relationships.mjs';
|
|
10
12
|
/**
|
|
11
13
|
* A subset of Office JS `Word.TrackedChange`: `type`, `author`, `date`, `text`, `accept()`,
|
|
12
|
-
* `reject()` and `getRange()`, plus `element`, `id
|
|
13
|
-
* `Paragraph` and `Range`: it holds the markup it is
|
|
14
|
+
* `reject()` and `getRange()`, plus `element`, `id`, `target`, and docx4j's `kind`, `part` and
|
|
15
|
+
* `pieces` (CR-011) as extensions. A view, like `Paragraph` and `Range`: it holds the markup it is
|
|
16
|
+
* over, nothing is cached.
|
|
14
17
|
*/
|
|
15
18
|
export class TrackedChange {
|
|
16
19
|
constructor(target,
|
|
17
20
|
/** The paragraph the change is in; undefined for a row revision. */
|
|
18
|
-
paragraph
|
|
21
|
+
paragraph,
|
|
22
|
+
/** The body a change without a paragraph (a row, a cell) is in, for the notes it may take away. */
|
|
23
|
+
body) {
|
|
19
24
|
this.target = target;
|
|
20
25
|
this.paragraph = paragraph;
|
|
26
|
+
this.body = body;
|
|
21
27
|
}
|
|
22
28
|
/** Office JS `Word.ChangeTrackingState`. */
|
|
23
29
|
get type() {
|
|
@@ -28,7 +34,10 @@ export class TrackedChange {
|
|
|
28
34
|
case 'row': return t.row === 'ins' ? 'Added' : 'Deleted';
|
|
29
35
|
case 'group': return t.revision === 'ins' ? 'Added' : t.revision === 'del' ? 'Deleted' : 'Formatted';
|
|
30
36
|
case 'cell': return t.cell === 'ins' ? 'Added' : 'Deleted';
|
|
31
|
-
case 'holder': return 'Added';
|
|
37
|
+
case 'holder': return t.revision === 'ins' ? 'Added' : 'Deleted';
|
|
38
|
+
case 'field': return 'Added';
|
|
39
|
+
case 'mathRun':
|
|
40
|
+
case 'mathStructure': return t.revision === 'ins' ? 'Added' : 'Deleted';
|
|
32
41
|
default: return 'Formatted';
|
|
33
42
|
}
|
|
34
43
|
}
|
|
@@ -47,8 +56,55 @@ export class TrackedChange {
|
|
|
47
56
|
get id() {
|
|
48
57
|
return this.target.value.id;
|
|
49
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* docx4j's kind of change (an extension; its `TrackedChange.getKind()`, CR-011): `target.kind` in
|
|
61
|
+
* docx4j's names, an equation's runs as runs. A group's is its pieces' when they are all of one kind,
|
|
62
|
+
* else `INSERTION`, `DELETION` or `FORMATTING`.
|
|
63
|
+
*/
|
|
64
|
+
get kind() {
|
|
65
|
+
const t = this.target;
|
|
66
|
+
switch (t.kind) {
|
|
67
|
+
case 'run': return t.revision === 'ins' ? 'RUN_INSERTION' : t.revision === 'del' ? 'RUN_DELETION' : 'MOVE';
|
|
68
|
+
case 'mark': return t.mark === 'ins' ? 'PARAGRAPH_MARK_INSERTION' : 'PARAGRAPH_MARK_DELETION';
|
|
69
|
+
case 'runProperties': return 'RUN_PROPERTIES';
|
|
70
|
+
case 'markProperties': return 'PARAGRAPH_MARK_PROPERTIES';
|
|
71
|
+
case 'paragraphProperties': return 'PARAGRAPH_PROPERTIES';
|
|
72
|
+
case 'row': return t.row === 'ins' ? 'ROW_INSERTION' : 'ROW_DELETION';
|
|
73
|
+
case 'field': return 'FIELD_INSERTION';
|
|
74
|
+
case 'holder': return t.revision === 'ins' ? 'CONTENT_CONTROL_INSERTION' : 'CONTENT_CONTROL_DELETION';
|
|
75
|
+
case 'mathRun': return t.revision === 'ins' ? 'RUN_INSERTION' : 'RUN_DELETION';
|
|
76
|
+
case 'mathStructure': return t.revision === 'ins' ? 'MATH_STRUCTURE_INSERTION' : 'MATH_STRUCTURE_DELETION';
|
|
77
|
+
case 'tableProperties': return 'TABLE_PROPERTIES';
|
|
78
|
+
case 'sectionProperties': return 'SECTION_PROPERTIES';
|
|
79
|
+
case 'cell': return t.cell === 'ins' ? 'CELL_INSERTION' : 'CELL_DELETION';
|
|
80
|
+
case 'group': {
|
|
81
|
+
const first = t.pieces[0].kind;
|
|
82
|
+
if (t.pieces.every((piece) => piece.kind === first))
|
|
83
|
+
return first;
|
|
84
|
+
return t.revision === 'ins' ? 'INSERTION' : t.revision === 'del' ? 'DELETION' : 'FORMATTING';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The part the change's story is in (an extension; docx4j's `getPart()`, CR-011): the main document
|
|
90
|
+
* part, a header or footer part, the footnotes, endnotes or comments part. A text box's change gives
|
|
91
|
+
* the part the text box is in.
|
|
92
|
+
*/
|
|
93
|
+
get part() {
|
|
94
|
+
var _a;
|
|
95
|
+
return (_a = this.body_) === null || _a === void 0 ? void 0 : _a.part;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The single changes this one groups, in document order (an extension; docx4j's `getPieces()`,
|
|
99
|
+
* CR-011); a change that is no group is its own one piece. A filter's conditions are met or not by
|
|
100
|
+
* each piece, and each can be accepted or rejected on its own.
|
|
101
|
+
*/
|
|
102
|
+
get pieces() {
|
|
103
|
+
return this.target.kind === 'group' ? this.target.pieces : [this];
|
|
104
|
+
}
|
|
50
105
|
/** The markup this change is over (an extension): the `w:ins` element, the `w:rPrChange`, ... */
|
|
51
106
|
get element() {
|
|
107
|
+
var _a;
|
|
52
108
|
const t = this.target;
|
|
53
109
|
switch (t.kind) {
|
|
54
110
|
case 'run': return t.element;
|
|
@@ -58,12 +114,15 @@ export class TrackedChange {
|
|
|
58
114
|
case 'cell': return t.tc;
|
|
59
115
|
case 'sectionProperties': return t.sectPr;
|
|
60
116
|
case 'holder': return t.holder;
|
|
117
|
+
case 'field': return t.span[0];
|
|
118
|
+
case 'mathRun': return t.item;
|
|
119
|
+
case 'mathStructure': return (_a = t.structure) !== null && _a !== void 0 ? _a : t.ctrlPr;
|
|
61
120
|
default: return t.value;
|
|
62
121
|
}
|
|
63
122
|
}
|
|
64
123
|
/** The text the change covers; `\r` for a paragraph mark, as Office JS gives it (check 22). */
|
|
65
124
|
get text() {
|
|
66
|
-
var _a, _b;
|
|
125
|
+
var _a, _b, _c;
|
|
67
126
|
const t = this.target;
|
|
68
127
|
switch (t.kind) {
|
|
69
128
|
case 'mark': return '\r';
|
|
@@ -72,17 +131,22 @@ export class TrackedChange {
|
|
|
72
131
|
case 'tableProperties': return tableChangeText(t.tbl.value);
|
|
73
132
|
case 'cell': return tableRowText(t.tr);
|
|
74
133
|
case 'sectionProperties': return t.text;
|
|
75
|
-
case 'run': return segmentsOf(t.element.value, { view: t.revision === 'ins' || t.revision === 'moveTo' ? 'accepted' : 'original' }).map((s) => s.text).join('');
|
|
134
|
+
case 'run': return segmentsOf(t.element.value, { view: t.revision === 'ins' || t.revision === 'moveTo' ? 'accepted' : 'original', characters: 'textOf' }).map((s) => s.text).join('');
|
|
76
135
|
case 'runProperties': return textOf(t.run);
|
|
136
|
+
// the field's result: its instruction is no text, as Office JS reports it (check 30: "1")
|
|
137
|
+
case 'field': return segmentsOf({ content: t.span }, { view: 'accepted', characters: 'textOf' }).map((s) => s.text).join('');
|
|
77
138
|
case 'paragraphProperties': return (_b = (_a = this.paragraph) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
|
|
78
139
|
// a deleted row's content is `w:delText` inside `w:del`, which the accepted view skips:
|
|
79
140
|
// its text is what the row said before, as for a run deletion
|
|
80
141
|
case 'row': return rowText(t.tr.value, t.row === 'del' ? 'original' : 'accepted');
|
|
142
|
+
// an equation's text: its m:t, as docx4j gives it; a structure's own marker covers no text
|
|
143
|
+
case 'mathRun': return ((_c = t.value.rest) !== null && _c !== void 0 ? _c : []).map((item) => { var _a; return (_a = mathTextOf(item)) !== null && _a !== void 0 ? _a : ''; }).join('');
|
|
81
144
|
default: return '';
|
|
82
145
|
}
|
|
83
146
|
}
|
|
84
147
|
/** The range the change covers; a deletion is a collapsed range where it sits in the accepted text. */
|
|
85
148
|
getRange() {
|
|
149
|
+
var _a;
|
|
86
150
|
const p = this.paragraph;
|
|
87
151
|
if (!p)
|
|
88
152
|
return undefined;
|
|
@@ -92,11 +156,55 @@ export class TrackedChange {
|
|
|
92
156
|
return new Range(p, start, end);
|
|
93
157
|
}
|
|
94
158
|
if (t.kind === 'group') {
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
159
|
+
// the group from where its first piece starts to where its last ends, across paragraphs where its pieces are
|
|
160
|
+
// in several (CR-014 phase 1; check 52: an insertion through a paragraph's mark is one change whose range
|
|
161
|
+
// spans both paragraphs). A mark ends its own paragraph's range (check 53: an inserted paragraph's range is
|
|
162
|
+
// that paragraph, its mark included). A deletion is collapsed where it starts, its text no longer in the
|
|
163
|
+
// accepted view, as Word's default view reads it (check 53). Until then a group's range was its part in the
|
|
164
|
+
// paragraph it starts in.
|
|
165
|
+
const ranges = t.pieces.filter((piece) => piece.target.kind === 'run' || piece.target.kind === 'runProperties' || piece.target.kind === 'field')
|
|
166
|
+
.map((piece) => piece.getRange()).filter((r) => r !== undefined);
|
|
167
|
+
const marks = t.pieces.filter((piece) => piece.target.kind === 'mark' && piece.paragraph !== undefined).map((piece) => piece.paragraph);
|
|
168
|
+
if (ranges.length === 0 && marks.length === 0)
|
|
98
169
|
return t.pieces[0].getRange();
|
|
99
|
-
|
|
170
|
+
// within one paragraph, as most groups are: no story to lay out
|
|
171
|
+
if (marks.length === 0 && ranges.every((r) => r.paragraph.element === ranges[0].paragraph.element && !r.spansParagraphs)) {
|
|
172
|
+
return new Range(ranges[0].paragraph, Math.min(...ranges.map((r) => r.start)), Math.max(...ranges.map((r) => r.end)));
|
|
173
|
+
}
|
|
174
|
+
const layout = storyLayout(storyOfParagraph(p.p).items, (element, container) => p.viewOf(element, container).text);
|
|
175
|
+
const at = (paragraph, offset) => {
|
|
176
|
+
const i = layout.indexOf(paragraph.p);
|
|
177
|
+
return i < 0 ? Number.NaN : layout.paragraphs[i].start + offset;
|
|
178
|
+
};
|
|
179
|
+
// each piece's span in the story's text; a mark's is its paragraph's end, holding the mark
|
|
180
|
+
const spans = ranges.map((r) => ({ start: at(r.paragraph, r.start), end: at(r.endParagraph, r.end) }));
|
|
181
|
+
for (const paragraph of marks)
|
|
182
|
+
spans.push({ start: at(paragraph, paragraph.text.length), end: at(paragraph, paragraph.text.length), mark: paragraph });
|
|
183
|
+
if (spans.some((span) => Number.isNaN(span.start) || Number.isNaN(span.end))) {
|
|
184
|
+
const here = ranges.filter((r) => r.paragraph.element === p.element);
|
|
185
|
+
return here.length === 0 ? (_a = ranges[0]) !== null && _a !== void 0 ? _a : t.pieces[0].getRange()
|
|
186
|
+
: new Range(p, Math.min(...here.map((r) => r.start)), Math.max(...here.map((r) => r.end)));
|
|
187
|
+
}
|
|
188
|
+
const from = Math.min(...spans.map((span) => span.start));
|
|
189
|
+
const first = layout.paragraphs.find((sp) => sp.start <= from && from <= sp.end);
|
|
190
|
+
const view = (sp) => (sp.element === p.element ? p : p.viewOf(sp.element, sp.container));
|
|
191
|
+
if (this.type === 'Deleted')
|
|
192
|
+
return new Range(view(first), from - first.start, from - first.start);
|
|
193
|
+
// the last end, a mark's after text ending at the same place
|
|
194
|
+
const last = spans.reduce((a, b) => (b.end > a.end || (b.end === a.end && b.mark !== undefined) ? b : a));
|
|
195
|
+
const lastParagraph = layout.paragraphs.find((sp) => sp.start <= last.end && last.end <= sp.end && (last.mark === undefined || sp.element === last.mark.element));
|
|
196
|
+
return new Range(view(first), from - first.start, last.end - lastParagraph.start, false, last.mark !== undefined, lastParagraph === first ? undefined : view(lastParagraph));
|
|
197
|
+
}
|
|
198
|
+
if (t.kind === 'mark') {
|
|
199
|
+
// a paragraph's mark alone: inserted, the mark (check 53: \r); deleted, collapsed at the paragraph's end,
|
|
200
|
+
// as Word's default view reads it. Until CR-014 phase 1 it was the paragraph's whole range
|
|
201
|
+
const length = p.text.length;
|
|
202
|
+
return new Range(p, length, length, false, t.mark === 'ins');
|
|
203
|
+
}
|
|
204
|
+
if (t.kind === 'field') {
|
|
205
|
+
const [start] = spanOf(p, t.span[0]);
|
|
206
|
+
const length = segmentsOf({ content: t.span }, { hidden: p.hiddenRuns() }).map((s) => s.text).join('').length;
|
|
207
|
+
return new Range(p, start, start + length);
|
|
100
208
|
}
|
|
101
209
|
if (t.kind === 'runProperties') {
|
|
102
210
|
const segs = p.segments().filter((s) => s.run === t.run);
|
|
@@ -104,13 +212,43 @@ export class TrackedChange {
|
|
|
104
212
|
return p.getRange('Start');
|
|
105
213
|
return new Range(p, segs[0].start, segs[segs.length - 1].end);
|
|
106
214
|
}
|
|
215
|
+
// the equation's text, Word's drawing of it, since CR-012 phase 4; collapsed where it stands when the
|
|
216
|
+
// view leaves it none
|
|
217
|
+
if ((t.kind === 'mathRun' || t.kind === 'mathStructure') && t.equation) {
|
|
218
|
+
const equation = p.segments().find((seg) => seg.math && seg.item === t.equation);
|
|
219
|
+
if (equation)
|
|
220
|
+
return new Range(p, equation.start, equation.end);
|
|
221
|
+
const [at] = spanOf(p, t.equation);
|
|
222
|
+
return new Range(p, at, at);
|
|
223
|
+
}
|
|
107
224
|
return p.getRange('Whole');
|
|
108
225
|
}
|
|
109
226
|
/**
|
|
110
227
|
* Keeps the change: a `w:ins` is unwrapped, a `w:del` removed, a deleted mark joins the paragraphs.
|
|
111
228
|
* Either half of a move keeps the whole move, as Word's Review tab does (CR-002 section 29).
|
|
229
|
+
*
|
|
230
|
+
* Returns whether it acted: false, changing nothing, when the change has been resolved since it was
|
|
231
|
+
* listed (CR-002 section 53). An extension: Office JS's `accept()` returns nothing.
|
|
112
232
|
*/
|
|
113
233
|
accept() {
|
|
234
|
+
if (!this.isCurrent())
|
|
235
|
+
return false;
|
|
236
|
+
resolving(this.package_, this.body_, () => { this.acceptHere(); this.resolveTwins('accept'); });
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The change's twins resolved the same way: the changes in the other branches of the kept
|
|
241
|
+
* `mc:AlternateContent` this one was listed from, paired with it by kind and position (`pairTwins`,
|
|
242
|
+
* docx4j's; CR-013 phase 3), each with its own twins. A group's twins are its pieces', which its
|
|
243
|
+
* pieces resolve. A twin resolved since it was listed is passed over.
|
|
244
|
+
*/
|
|
245
|
+
resolveTwins(what) {
|
|
246
|
+
var _a;
|
|
247
|
+
for (const twin of (_a = twinsOf.get(this)) !== null && _a !== void 0 ? _a : [])
|
|
248
|
+
if (twin.isCurrent())
|
|
249
|
+
twin[what]();
|
|
250
|
+
}
|
|
251
|
+
acceptHere() {
|
|
114
252
|
var _a, _b, _c, _d, _e, _f;
|
|
115
253
|
const t = this.target;
|
|
116
254
|
switch (t.kind) {
|
|
@@ -159,7 +297,7 @@ export class TrackedChange {
|
|
|
159
297
|
if (t.cell === 'ins')
|
|
160
298
|
(_d = t.tc.value.tcPr) === null || _d === void 0 ? true : delete _d.cellIns;
|
|
161
299
|
else
|
|
162
|
-
removeCell(t.tc, t.owner);
|
|
300
|
+
removeCell(t.tc, t.owner, t.tr);
|
|
163
301
|
return;
|
|
164
302
|
case 'markProperties': {
|
|
165
303
|
const p = this.requireParagraph().p;
|
|
@@ -171,16 +309,85 @@ export class TrackedChange {
|
|
|
171
309
|
delete t.sectPr.sectPrChange;
|
|
172
310
|
return;
|
|
173
311
|
case 'holder':
|
|
174
|
-
// the element stays, the markers go (check 30: accept all leaves the control, no marker)
|
|
175
|
-
|
|
312
|
+
// an insertion: the element stays, the markers go (check 30: accept all leaves the control, no marker);
|
|
313
|
+
// a deletion: the element goes, its content with it (check 54 case 15: Word's Accept All took the control)
|
|
314
|
+
if (t.revision === 'ins')
|
|
315
|
+
removeHolderMarkers(t);
|
|
316
|
+
else
|
|
317
|
+
removeHolder(t);
|
|
318
|
+
return;
|
|
319
|
+
case 'field':
|
|
320
|
+
for (const el of t.span) {
|
|
321
|
+
if (revisionKindOf(el) === 'ins')
|
|
322
|
+
unwrap({ kind: 'run', revision: 'ins', element: el, owner: currentOwner(t.owner, el), value: el.value });
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
case 'mathRun':
|
|
326
|
+
resolveMathRun(t, t.revision === 'ins');
|
|
327
|
+
return;
|
|
328
|
+
case 'mathStructure':
|
|
329
|
+
resolveMathStructure(t, t.revision === 'ins');
|
|
176
330
|
return;
|
|
177
331
|
}
|
|
178
332
|
}
|
|
179
333
|
/**
|
|
180
334
|
* Puts back what was there: a `w:ins` is removed, a `w:del` restored, a `w:rPrChange` re-applied.
|
|
181
335
|
* Either half of a move rejects the whole move, as Word's Review tab does (CR-002 section 29).
|
|
336
|
+
*
|
|
337
|
+
* Returns whether it acted, as `accept()` does.
|
|
182
338
|
*/
|
|
183
339
|
reject() {
|
|
340
|
+
if (!this.isCurrent())
|
|
341
|
+
return false;
|
|
342
|
+
resolving(this.package_, this.body_, () => { this.rejectHere(); this.resolveTwins('reject'); });
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Whether the record this view was listed for, `target.value`, is still where it was listed. A view
|
|
347
|
+
* whose change has been resolved since, by itself or by another verb, does nothing; otherwise it
|
|
348
|
+
* would act on what stands there now (CR-002 section 53). The record is compared by identity. docx4j's
|
|
349
|
+
* views throw instead, once any change on the package has been resolved since they were listed; that
|
|
350
|
+
* would stop a loop resolving one listing last first, as `Body.acceptAll` does.
|
|
351
|
+
*/
|
|
352
|
+
isCurrent() {
|
|
353
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
354
|
+
const t = this.target;
|
|
355
|
+
const inList = (owner, element) => currentOwner(owner, element).includes(element);
|
|
356
|
+
switch (t.kind) {
|
|
357
|
+
case 'run':
|
|
358
|
+
return inList(t.owner, t.element);
|
|
359
|
+
case 'mathRun':
|
|
360
|
+
return ((_a = t.run.value.content) !== null && _a !== void 0 ? _a : []).includes(t.item);
|
|
361
|
+
case 'tableProperties':
|
|
362
|
+
return hasTableRecords(t.tbl.value); // resolved from the table's records as they stand
|
|
363
|
+
case 'group':
|
|
364
|
+
return t.pieces.some((piece) => piece.isCurrent()); // each piece decides for itself
|
|
365
|
+
case 'mark': {
|
|
366
|
+
const paragraph = this.paragraph;
|
|
367
|
+
return !!paragraph && ((_c = (_b = paragraph.p.pPr) === null || _b === void 0 ? void 0 : _b.rPr) === null || _c === void 0 ? void 0 : _c[t.mark]) === t.value && inList(paragraph.container, paragraph.element);
|
|
368
|
+
}
|
|
369
|
+
case 'runProperties':
|
|
370
|
+
return ((_d = t.run.rPr) === null || _d === void 0 ? void 0 : _d.rPrChange) === t.value;
|
|
371
|
+
case 'paragraphProperties':
|
|
372
|
+
return ((_f = (_e = this.paragraph) === null || _e === void 0 ? void 0 : _e.p.pPr) === null || _f === void 0 ? void 0 : _f.pPrChange) === t.value;
|
|
373
|
+
case 'markProperties':
|
|
374
|
+
return ((_j = (_h = (_g = this.paragraph) === null || _g === void 0 ? void 0 : _g.p.pPr) === null || _h === void 0 ? void 0 : _h.rPr) === null || _j === void 0 ? void 0 : _j.rPrChange) === t.value;
|
|
375
|
+
case 'sectionProperties':
|
|
376
|
+
return t.sectPr.sectPrChange === t.value;
|
|
377
|
+
case 'row':
|
|
378
|
+
return ((_k = t.tr.value.trPr) === null || _k === void 0 ? void 0 : _k[t.row]) === t.value && inList(t.owner, t.tr);
|
|
379
|
+
case 'cell':
|
|
380
|
+
return ((_l = t.tc.value.tcPr) === null || _l === void 0 ? void 0 : _l[t.cell === 'ins' ? 'cellIns' : 'cellDel']) === t.value && inList(t.owner, t.tc);
|
|
381
|
+
case 'holder':
|
|
382
|
+
return inList(t.owner, t.outer[0]);
|
|
383
|
+
case 'field':
|
|
384
|
+
return t.span.length > 0 && inList(t.owner, t.span[0]);
|
|
385
|
+
case 'mathStructure':
|
|
386
|
+
// an equation's lists are not the ones childrenOf gives its PARENT, so the structure's own
|
|
387
|
+
return t.ctrlPr[t.revision] === t.value && (!t.structure || !t.owner || currentOwner(t.owner, t.structure).includes(t.structure));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
rejectHere() {
|
|
184
391
|
var _a, _b;
|
|
185
392
|
const t = this.target;
|
|
186
393
|
switch (t.kind) {
|
|
@@ -229,7 +436,7 @@ export class TrackedChange {
|
|
|
229
436
|
if (t.cell === 'del')
|
|
230
437
|
(_b = t.tc.value.tcPr) === null || _b === void 0 ? true : delete _b.cellDel;
|
|
231
438
|
else
|
|
232
|
-
removeCell(t.tc, t.owner);
|
|
439
|
+
removeCell(t.tc, t.owner, t.tr);
|
|
233
440
|
return;
|
|
234
441
|
case 'markProperties':
|
|
235
442
|
restoreMarkProperties(this.requireParagraph().p, t.value);
|
|
@@ -238,13 +445,35 @@ export class TrackedChange {
|
|
|
238
445
|
restoreSection(t.sectPr, t.value);
|
|
239
446
|
return;
|
|
240
447
|
case 'holder':
|
|
241
|
-
// the element goes, its content stays where it stood: what is left of it after the
|
|
242
|
-
// inside were rejected (nothing, for content inserted with it; check 30: reject all removes
|
|
243
|
-
// control, and an existing control, which has no markers, stays)
|
|
244
|
-
|
|
448
|
+
// an insertion: the element goes, its content stays where it stood: what is left of it after the
|
|
449
|
+
// pieces inside were rejected (nothing, for content inserted with it; check 30: reject all removes
|
|
450
|
+
// the control, and an existing control, which has no markers, stays); a deletion: the markers go
|
|
451
|
+
if (t.revision === 'ins')
|
|
452
|
+
unwrapHolder(t);
|
|
453
|
+
else
|
|
454
|
+
removeHolderMarkers(t);
|
|
455
|
+
return;
|
|
456
|
+
case 'field':
|
|
457
|
+
removeField(t, this.paragraph);
|
|
458
|
+
return;
|
|
459
|
+
case 'mathRun':
|
|
460
|
+
resolveMathRun(t, t.revision === 'del');
|
|
461
|
+
return;
|
|
462
|
+
case 'mathStructure':
|
|
463
|
+
resolveMathStructure(t, t.revision === 'del');
|
|
245
464
|
return;
|
|
246
465
|
}
|
|
247
466
|
}
|
|
467
|
+
/** The body the change is in. */
|
|
468
|
+
get body_() {
|
|
469
|
+
var _a, _b;
|
|
470
|
+
return (_b = (_a = this.paragraph) === null || _a === void 0 ? void 0 : _a.parentBody) !== null && _b !== void 0 ? _b : this.body;
|
|
471
|
+
}
|
|
472
|
+
/** The package the change is in, which keeps the notes whose references it took away. */
|
|
473
|
+
get package_() {
|
|
474
|
+
var _a;
|
|
475
|
+
return (_a = this.body_) === null || _a === void 0 ? void 0 : _a.package_;
|
|
476
|
+
}
|
|
248
477
|
requireParagraph() {
|
|
249
478
|
if (!this.paragraph)
|
|
250
479
|
throw new Docx4JException('This tracked change has no paragraph');
|
|
@@ -268,19 +497,20 @@ function rowText(tr, view) {
|
|
|
268
497
|
for (const el of (_a = childrenOf(tc)) !== null && _a !== void 0 ? _a : []) {
|
|
269
498
|
const tn = typeNameOf(el);
|
|
270
499
|
if (tn === 'org_docx4j_wml.P') {
|
|
271
|
-
out += (afterParagraph ? '\r' : '') + textOfView(el.value, { view });
|
|
500
|
+
out += (afterParagraph ? '\r' : '') + textOfView(el.value, { view, characters: 'textOf' });
|
|
272
501
|
afterParagraph = true;
|
|
273
502
|
}
|
|
274
503
|
else if (tn === 'org_docx4j_wml.Tbl') {
|
|
275
|
-
for (const row of rowsOf(el.value))
|
|
504
|
+
for (const row of rowsOf(el.value, 'read'))
|
|
276
505
|
out += rowText(row.element.value, view);
|
|
277
506
|
afterParagraph = false;
|
|
278
507
|
}
|
|
279
508
|
else if (typeof el.value === 'object' && el.value !== null) {
|
|
280
|
-
// a block control or custom XML element
|
|
281
|
-
|
|
509
|
+
// a block control or custom XML element, or a kept mc:AlternateContent's selected branch (CR-013
|
|
510
|
+
// phase 2): what it holds, as if it were not there
|
|
511
|
+
for (const inner of (_b = childrenOf(el.value, 'read')) !== null && _b !== void 0 ? _b : []) {
|
|
282
512
|
if (typeNameOf(inner) === 'org_docx4j_wml.P') {
|
|
283
|
-
out += (afterParagraph ? '\r' : '') + textOfView(inner.value, { view });
|
|
513
|
+
out += (afterParagraph ? '\r' : '') + textOfView(inner.value, { view, characters: 'textOf' });
|
|
284
514
|
afterParagraph = true;
|
|
285
515
|
}
|
|
286
516
|
}
|
|
@@ -288,7 +518,7 @@ function rowText(tr, view) {
|
|
|
288
518
|
}
|
|
289
519
|
return out;
|
|
290
520
|
};
|
|
291
|
-
return cellsOf(tr).map((cell) => cellText(cell.element.value)).join('\t') + '\r\n';
|
|
521
|
+
return cellsOf(tr, 'read').map((cell) => cellText(cell.element.value)).join('\t') + '\r\n';
|
|
292
522
|
}
|
|
293
523
|
/** The changes inside a row, last first, so that a paragraph join never disturbs one still to do. */
|
|
294
524
|
function applyInner(inner, what) {
|
|
@@ -351,7 +581,7 @@ function indentWithin(ind, level) {
|
|
|
351
581
|
* never strands a piece still to do. Office JS and Word's Review tab resolve the group as one (checks
|
|
352
582
|
* 22 and 23), and so, piece by piece, does this.
|
|
353
583
|
*/
|
|
354
|
-
function resolvePieces(pieces, what) {
|
|
584
|
+
export function resolvePieces(pieces, what) {
|
|
355
585
|
const runs = pieces.filter((piece) => piece.target.kind !== 'mark');
|
|
356
586
|
const marks = pieces.filter((piece) => piece.target.kind === 'mark');
|
|
357
587
|
for (let i = runs.length - 1; i >= 0; i--)
|
|
@@ -360,14 +590,13 @@ function resolvePieces(pieces, what) {
|
|
|
360
590
|
marks[i][what]();
|
|
361
591
|
}
|
|
362
592
|
/**
|
|
363
|
-
* The list an element is in now: `owner` as the change was listed
|
|
364
|
-
*
|
|
365
|
-
*
|
|
593
|
+
* The list an element is in now: the list its `PARENT` keeps, else `owner` as the change was listed.
|
|
594
|
+
* A piece listed inside a holder that was unwrapped since (its content moved out into the owner, check
|
|
595
|
+
* 30) is still found, and so is a run whose paragraph was joined into the one before it: the joined
|
|
596
|
+
* paragraph's own list still holds it, but is no longer in the document (CR-002 section 53).
|
|
366
597
|
*/
|
|
367
598
|
function currentOwner(owner, element) {
|
|
368
599
|
var _a, _b;
|
|
369
|
-
if (owner.includes(element))
|
|
370
|
-
return owner;
|
|
371
600
|
const parent = (_a = element.value) === null || _a === void 0 ? void 0 : _a.PARENT;
|
|
372
601
|
const actual = parent ? (_b = runItemsOf(parent)) !== null && _b !== void 0 ? _b : childrenOf(parent) : undefined;
|
|
373
602
|
return (actual === null || actual === void 0 ? void 0 : actual.includes(element)) ? actual : owner;
|
|
@@ -375,10 +604,331 @@ function currentOwner(owner, element) {
|
|
|
375
604
|
function remove(owner, element) {
|
|
376
605
|
const list = currentOwner(owner, element);
|
|
377
606
|
const i = list.indexOf(element);
|
|
378
|
-
if (i
|
|
379
|
-
|
|
607
|
+
if (i < 0)
|
|
608
|
+
return;
|
|
609
|
+
discarding(element);
|
|
610
|
+
list.splice(i, 1);
|
|
611
|
+
}
|
|
612
|
+
let resolution;
|
|
613
|
+
/**
|
|
614
|
+
* Runs `resolve`, then takes away what went with the content it removed:
|
|
615
|
+
* - **Notes**: Word took a footnote from the notes part when its reference went with the content
|
|
616
|
+
* around it, accepting a deletion and rejecting an insertion alike (the editor's `review-for-word`
|
|
617
|
+
* saves, Word 15 and Word 2010); the entries go when the package is saved
|
|
618
|
+
* (`WordprocessingMLPackage.pendingNoteRemovals`).
|
|
619
|
+
* - **Bookmarks**: Word removed a bookmark whole when its end went with a deletion it accepted, and
|
|
620
|
+
* when the insertion it was put around went with a reject (docx4j CR-043's probe, C11 and C11b, Word
|
|
621
|
+
* 16.0.20326). docx4j's rule from the two (its CR-043 section 3.2) is followed here: a bookmark with a
|
|
622
|
+
* marker in removed content goes whole, its other marker too, even with content left beside it; so
|
|
623
|
+
* does one with content between its markers that is left with none, a deletion it was around accepted
|
|
624
|
+
* among them (U15); and one that was empty before, as `_GoBack` is, stays. Those three are not measured.
|
|
625
|
+
* - **Relationships and parts**: Word's saves kept no part the resolved content alone referenced. A
|
|
626
|
+
* picture deleted and accepted, or inserted and rejected, took its image part (docx4j CR-043's probe,
|
|
627
|
+
* C05 and C04), and a section break deleted and accepted took the headers and footers of the section
|
|
628
|
+
* it ended (C06: six of each became three). docx4j's rule (its CR-043 section 3.2, D7) is followed: a
|
|
629
|
+
* relationship of the change's part that the removed content referenced, through any `r:` attribute,
|
|
630
|
+
* goes when nothing left in the part references it; its part goes when no relationship in the package
|
|
631
|
+
* targets it any more, and what that part alone related to goes with it. The kinds beyond pictures,
|
|
632
|
+
* headers and footers (charts, OLE objects, `v:imagedata`) are docx4j's reading, not measured.
|
|
633
|
+
* - **Comments**: a comment `removeRevision` left with its range's start and end in different paragraphs
|
|
634
|
+
* goes whole when the joins have brought them into one list with nothing with text between
|
|
635
|
+
* (`settleSpanningComments`; CR-002 section 54).
|
|
636
|
+
* A change resolved inside another's (a row's cells, a group's pieces, `acceptAll`'s changes) shares the
|
|
637
|
+
* outer resolution, so the bookmarks are read twice for the lot, and not at all when nothing is removed.
|
|
638
|
+
*/
|
|
639
|
+
export function resolving(pkg, body, resolve) {
|
|
640
|
+
var _a, _b, _c, _d, _e, _f;
|
|
641
|
+
const notes = pkg === null || pkg === void 0 ? void 0 : pkg.pendingNoteRemovals;
|
|
642
|
+
const root = storyRootOf(body);
|
|
643
|
+
const outer = resolution;
|
|
644
|
+
if (outer) {
|
|
645
|
+
(_a = outer.root) !== null && _a !== void 0 ? _a : (outer.root = root);
|
|
646
|
+
(_b = outer.part) !== null && _b !== void 0 ? _b : (outer.part = body === null || body === void 0 ? void 0 : body.part);
|
|
647
|
+
(_c = outer.body) !== null && _c !== void 0 ? _c : (outer.body = body);
|
|
648
|
+
const previous = outer.notes;
|
|
649
|
+
outer.notes = notes !== null && notes !== void 0 ? notes : previous;
|
|
650
|
+
try {
|
|
651
|
+
resolve();
|
|
652
|
+
}
|
|
653
|
+
finally {
|
|
654
|
+
outer.notes = previous;
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
resolution = { notes, root, part: body === null || body === void 0 ? void 0 : body.part };
|
|
659
|
+
if (body)
|
|
660
|
+
resolution.body = body;
|
|
661
|
+
try {
|
|
662
|
+
resolve();
|
|
663
|
+
const done = resolution;
|
|
664
|
+
if ((_d = done.comments) === null || _d === void 0 ? void 0 : _d.size)
|
|
665
|
+
settleSpanningComments(done.comments);
|
|
666
|
+
if (((_e = done.commentCandidates) === null || _e === void 0 ? void 0 : _e.size) && done.root && done.body)
|
|
667
|
+
settleCommentAnchors(done.root, done.body, done.commentCandidates);
|
|
668
|
+
if (done.root && done.before)
|
|
669
|
+
dropBookmarksBroken(done.root, done.before);
|
|
670
|
+
if (done.part && ((_f = done.relationships) === null || _f === void 0 ? void 0 : _f.size))
|
|
671
|
+
dropRelationshipsUnused(done.part, done.relationships);
|
|
672
|
+
}
|
|
673
|
+
finally {
|
|
674
|
+
resolution = undefined;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/** The tree a body's bookmarks are read in: its part's, so a marker in another cell or paragraph is found. */
|
|
678
|
+
function storyRootOf(body) {
|
|
679
|
+
var _a;
|
|
680
|
+
if (!body)
|
|
681
|
+
return undefined;
|
|
682
|
+
return ((_a = body.part) === null || _a === void 0 ? void 0 : _a.isUnmarshalled) ? body.part.contents : body.container;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Called before content is removed (or a paragraph mark joins two): records its notes, the relationships
|
|
686
|
+
* it references, and the bookmarks as they stand. `properties` is a paragraph's `w:pPr` that a join drops,
|
|
687
|
+
* whose `w:sectPr` references its section's headers and footers.
|
|
688
|
+
*/
|
|
689
|
+
function discarding(element, properties) {
|
|
690
|
+
var _a, _b, _c;
|
|
691
|
+
if (!resolution)
|
|
692
|
+
return;
|
|
693
|
+
if (resolution.root)
|
|
694
|
+
(_a = resolution.before) !== null && _a !== void 0 ? _a : (resolution.before = bookmarksIn(resolution.root));
|
|
695
|
+
if (element && resolution.notes)
|
|
696
|
+
noteReferencesIn(element, resolution.notes);
|
|
697
|
+
if (element)
|
|
698
|
+
commentAnchorsIn(element, ((_b = resolution.commentCandidates) !== null && _b !== void 0 ? _b : (resolution.commentCandidates = new Set())));
|
|
699
|
+
if (resolution.part && (element || properties)) {
|
|
700
|
+
const into = ((_c = resolution.relationships) !== null && _c !== void 0 ? _c : (resolution.relationships = new Set()));
|
|
701
|
+
if (element)
|
|
702
|
+
relationshipIdsIn(element, into);
|
|
703
|
+
if (properties)
|
|
704
|
+
relationshipIdsIn(properties, into);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const MATH_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/math';
|
|
708
|
+
/** Run content that is something in the text: a character, a drawing, a field character, a note's reference. */
|
|
709
|
+
const FILLS = new Set(['tab', 'br', 'cr', 'noBreakHyphen', 'softHyphen', 'sym', 'ptab', 'drawing', 'pict', 'object',
|
|
710
|
+
'fldChar', 'footnoteReference', 'endnoteReference', 'commentReference', 'footnoteRef', 'endnoteRef', 'annotationRef',
|
|
711
|
+
'separator', 'continuationSeparator', 'pgNum', 'dayShort', 'monthShort', 'yearShort', 'dayLong', 'monthLong', 'yearLong',
|
|
712
|
+
'contentPart', 'ruby']);
|
|
713
|
+
/** Of those, the ones that can hold a story of their own (a text box), and so bookmarks: walked into. */
|
|
714
|
+
const HOLDS_CONTENT = new Set(['drawing', 'pict', 'object', 'ruby', 'contentPart']);
|
|
715
|
+
const TEXTS = new Set(['t', 'delText', 'instrText', 'delInstrText']);
|
|
716
|
+
/** Properties, which hold no bookmark and no content: not walked. */
|
|
717
|
+
const PROPERTIES = new Set(['PARENT', 'TYPE_NAME', 'name', 'otherAttributes', 'pPr', 'rPr', 'tblPr', 'tblPrEx', 'tblGrid', 'trPr', 'tcPr', 'sectPr', 'sdtPr', 'sdtEndPr']);
|
|
718
|
+
/**
|
|
719
|
+
* Every bookmark in a tree, by id, in document order. Deleted and inserted content both count as
|
|
720
|
+
* content, and so does a paragraph's mark, so a bookmark over a paragraph boundary is not empty.
|
|
721
|
+
*/
|
|
722
|
+
function bookmarksIn(root) {
|
|
723
|
+
const marks = new Map();
|
|
724
|
+
let open = [];
|
|
725
|
+
const fill = () => { for (const state of open)
|
|
726
|
+
state.filled = true; open = []; };
|
|
727
|
+
const visit = (o) => {
|
|
728
|
+
var _a, _b, _c, _d;
|
|
729
|
+
if (typeof o !== 'object' || o === null)
|
|
730
|
+
return;
|
|
731
|
+
if (Array.isArray(o)) {
|
|
732
|
+
for (const item of o)
|
|
733
|
+
visit(item);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const el = o;
|
|
737
|
+
const local = (_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
738
|
+
if (local === 'bookmarkStart' || local === 'bookmarkEnd') {
|
|
739
|
+
const id = String((_b = el.value) === null || _b === void 0 ? void 0 : _b.id);
|
|
740
|
+
let state = marks.get(id);
|
|
741
|
+
if (!state)
|
|
742
|
+
marks.set(id, state = { start: false, end: false, filled: false });
|
|
743
|
+
if (local === 'bookmarkStart') {
|
|
744
|
+
state.start = true;
|
|
745
|
+
open.push(state);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
state.end = true;
|
|
749
|
+
open = open.filter((other) => other !== state);
|
|
750
|
+
}
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (local !== undefined && (el.name.namespaceURI === W_NS || el.name.namespaceURI === MATH_NS)) {
|
|
754
|
+
if (TEXTS.has(local)) {
|
|
755
|
+
if (String((_d = (_c = el.value) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '') !== '')
|
|
756
|
+
fill();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (FILLS.has(local)) {
|
|
760
|
+
fill();
|
|
761
|
+
if (!HOLDS_CONTENT.has(local))
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
for (const key in o)
|
|
766
|
+
if (!PROPERTIES.has(key))
|
|
767
|
+
visit(o[key]);
|
|
768
|
+
if (o.TYPE_NAME === 'org_docx4j_wml.P')
|
|
769
|
+
fill();
|
|
770
|
+
};
|
|
771
|
+
visit(root);
|
|
772
|
+
return marks;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* The bookmarks whole before and broken now - a marker gone, or emptied - taken away, both markers.
|
|
776
|
+
* One broken already, or gone whole with the content it was in, is left as it is.
|
|
777
|
+
*/
|
|
778
|
+
function dropBookmarksBroken(root, before) {
|
|
779
|
+
if (before.size === 0)
|
|
780
|
+
return;
|
|
781
|
+
const after = bookmarksIn(root);
|
|
782
|
+
const drop = new Set();
|
|
783
|
+
for (const [id, was] of before) {
|
|
784
|
+
if (!was.start || !was.end)
|
|
785
|
+
continue;
|
|
786
|
+
const now = after.get(id);
|
|
787
|
+
if (!now)
|
|
788
|
+
continue;
|
|
789
|
+
if (!now.start || !now.end || (was.filled && !now.filled))
|
|
790
|
+
drop.add(id);
|
|
791
|
+
}
|
|
792
|
+
if (drop.size === 0)
|
|
793
|
+
return;
|
|
794
|
+
const visit = (o) => {
|
|
795
|
+
var _a, _b;
|
|
796
|
+
if (typeof o !== 'object' || o === null)
|
|
797
|
+
return;
|
|
798
|
+
if (Array.isArray(o)) {
|
|
799
|
+
for (let i = o.length - 1; i >= 0; i--) {
|
|
800
|
+
const el = o[i];
|
|
801
|
+
const local = (_a = el === null || el === void 0 ? void 0 : el.name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
802
|
+
if ((local === 'bookmarkStart' || local === 'bookmarkEnd') && drop.has(String((_b = el.value) === null || _b === void 0 ? void 0 : _b.id)))
|
|
803
|
+
o.splice(i, 1);
|
|
804
|
+
else
|
|
805
|
+
visit(el);
|
|
806
|
+
}
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
for (const key in o)
|
|
810
|
+
if (!PROPERTIES.has(key))
|
|
811
|
+
visit(o[key]);
|
|
812
|
+
};
|
|
813
|
+
visit(root);
|
|
380
814
|
}
|
|
381
|
-
/**
|
|
815
|
+
/**
|
|
816
|
+
* Queues the notes whose references `removed` holds to go when the package is saved, as a resolution's
|
|
817
|
+
* removals go: what a range deleted untracked takes with it (CR-014 phase 2; check 52 case 12, where Word took
|
|
818
|
+
* the footnote out of `footnotes.xml` with its reference). A note goes only where no reference to it stands
|
|
819
|
+
* by then (`flushPendingNoteRemovals`), so content queued and then kept costs nothing.
|
|
820
|
+
*/
|
|
821
|
+
export function queueNoteRemovals(pkg, removed) {
|
|
822
|
+
const notes = pkg === null || pkg === void 0 ? void 0 : pkg.pendingNoteRemovals;
|
|
823
|
+
if (notes)
|
|
824
|
+
noteReferencesIn(removed, notes);
|
|
825
|
+
}
|
|
826
|
+
/** Adds the ids of the `w:footnoteReference` and `w:endnoteReference` elements anywhere in a tree to `into`. */
|
|
827
|
+
function noteReferencesIn(tree, into) {
|
|
828
|
+
const visit = (o) => {
|
|
829
|
+
var _a, _b;
|
|
830
|
+
if (typeof o !== 'object' || o === null)
|
|
831
|
+
return;
|
|
832
|
+
if (Array.isArray(o)) {
|
|
833
|
+
for (const item of o)
|
|
834
|
+
visit(item);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const local = (_a = o.name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
838
|
+
if (local === 'footnoteReference' || local === 'endnoteReference') {
|
|
839
|
+
const id = Number((_b = (o.value)) === null || _b === void 0 ? void 0 : _b.id);
|
|
840
|
+
if (Number.isInteger(id))
|
|
841
|
+
into[local === 'footnoteReference' ? 'footnote' : 'endnote'].add(id);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
for (const [key, value] of Object.entries(o))
|
|
845
|
+
if (key !== 'PARENT')
|
|
846
|
+
visit(value);
|
|
847
|
+
};
|
|
848
|
+
visit(tree);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* The notes `pending` holds that `flushPendingNoteRemovals` will take away: those whose reference no
|
|
852
|
+
* longer stands in the main document's tree. A listing of every story leaves them out, as docx4j's,
|
|
853
|
+
* which removes them when its resolution ends, does (CR-011).
|
|
854
|
+
*/
|
|
855
|
+
export function notesGoing(mainTree, pending) {
|
|
856
|
+
const standing = { footnote: new Set(), endnote: new Set() };
|
|
857
|
+
if (pending.footnote.size + pending.endnote.size > 0)
|
|
858
|
+
noteReferencesIn(mainTree, standing);
|
|
859
|
+
return {
|
|
860
|
+
footnote: new Set([...pending.footnote].filter((id) => !standing.footnote.has(id))),
|
|
861
|
+
endnote: new Set([...pending.endnote].filter((id) => !standing.endnote.has(id))),
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* The notes `pending` holds taken out of the notes parts, each unless a reference to it still stands in
|
|
866
|
+
* the main document (an accepted move keeps the reference its source had, say); then `pending` is
|
|
867
|
+
* empty. A note's entry goes, Word's separators never: their ids are never referenced. The notes parts
|
|
868
|
+
* are read only when something is pending, so a save with none leaves them untouched. Word's own save
|
|
869
|
+
* also renumbers the notes left (the editor's `review-for-word` saves); their ids stay here.
|
|
870
|
+
*
|
|
871
|
+
* What a removed note held goes on with it, as docx4j's `Removals.flush` takes it (CR-002 section 50,
|
|
872
|
+
* docx4j's reading, not measured): the relationships its content referenced (a picture's image) are
|
|
873
|
+
* queued on `host` for the notes part, to go when nothing else uses them, and a comment whose reference
|
|
874
|
+
* was in it is queued for removal, its anchor being gone.
|
|
875
|
+
*/
|
|
876
|
+
export async function flushPendingNoteRemovals(main, pending, host) {
|
|
877
|
+
if (pending.footnote.size === 0 && pending.endnote.size === 0)
|
|
878
|
+
return;
|
|
879
|
+
const standing = { footnote: new Set(), endnote: new Set() };
|
|
880
|
+
noteReferencesIn(await main.getContents(), standing);
|
|
881
|
+
const prune = (part, notes, ids, kept) => {
|
|
882
|
+
if (!notes)
|
|
883
|
+
return;
|
|
884
|
+
const released = new Set();
|
|
885
|
+
for (let i = notes.length - 1; i >= 0; i--) {
|
|
886
|
+
const note = notes[i];
|
|
887
|
+
if ((note.type === undefined || note.type === 'normal') && ids.has(Number(note.id)) && !kept.has(Number(note.id))) {
|
|
888
|
+
relationshipIdsIn(note.content, released);
|
|
889
|
+
if (host === null || host === void 0 ? void 0 : host.pendingCommentRemovals)
|
|
890
|
+
commentReferencesIn(note.content, host.pendingCommentRemovals);
|
|
891
|
+
notes.splice(i, 1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
queueRelationshipRemovals(host === null || host === void 0 ? void 0 : host.pendingRelationshipRemovals, part, released);
|
|
895
|
+
};
|
|
896
|
+
if (pending.footnote.size > 0 && main.footnotesPart)
|
|
897
|
+
prune(main.footnotesPart, (await main.footnotesPart.getContents()).footnote, pending.footnote, standing.footnote);
|
|
898
|
+
if (pending.endnote.size > 0 && main.endnotesPart)
|
|
899
|
+
prune(main.endnotesPart, (await main.endnotesPart.getContents()).endnote, pending.endnote, standing.endnote);
|
|
900
|
+
pending.footnote.clear();
|
|
901
|
+
pending.endnote.clear();
|
|
902
|
+
}
|
|
903
|
+
/** Adds the ids of the `w:commentReference` elements anywhere in a tree to `into`. */
|
|
904
|
+
export function commentReferencesIn(tree, into) {
|
|
905
|
+
const visit = (o) => {
|
|
906
|
+
var _a, _b;
|
|
907
|
+
if (typeof o !== 'object' || o === null)
|
|
908
|
+
return;
|
|
909
|
+
if (Array.isArray(o)) {
|
|
910
|
+
for (const item of o)
|
|
911
|
+
visit(item);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (((_a = o.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentReference') {
|
|
915
|
+
const id = Number((_b = (o.value)) === null || _b === void 0 ? void 0 : _b.id);
|
|
916
|
+
if (Number.isInteger(id))
|
|
917
|
+
into.add(id);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
for (const [key, value] of Object.entries(o))
|
|
921
|
+
if (key !== 'PARENT')
|
|
922
|
+
visit(value);
|
|
923
|
+
};
|
|
924
|
+
visit(tree);
|
|
925
|
+
}
|
|
926
|
+
/** A holder's deletion accepted: the markers go, and the element with what it holds. */
|
|
927
|
+
function removeHolder(t) {
|
|
928
|
+
removeHolderMarkers(t);
|
|
929
|
+
remove(t.owner, t.holder);
|
|
930
|
+
}
|
|
931
|
+
/** A holder's four insertion (or deletion) markers taken away; the element stays. */
|
|
382
932
|
function removeHolderMarkers(t) {
|
|
383
933
|
var _a;
|
|
384
934
|
remove(t.owner, t.outer[0]);
|
|
@@ -404,26 +954,110 @@ function unwrapHolder(t) {
|
|
|
404
954
|
* The insertion of a content control or custom XML element, when `items[i]` is a
|
|
405
955
|
* `w:customXmlInsRangeStart` and what follows is Word's form for one (check 30): the element, whose
|
|
406
956
|
* content starts with that marker's `w:customXmlInsRangeEnd` and ends with a second start, and that
|
|
407
|
-
* one's end right after the element
|
|
957
|
+
* one's end right after the element; or its deletion, the same form of `w:customXmlDelRangeStart` and
|
|
958
|
+
* `End` (check 54). Anything else is not one (a marker on its own is left alone).
|
|
408
959
|
*/
|
|
409
|
-
export function
|
|
410
|
-
var _a, _b, _c, _d, _e, _f;
|
|
960
|
+
export function holderChangeAt(items, i, paragraph, body) {
|
|
961
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
411
962
|
const start = items[i];
|
|
412
|
-
|
|
963
|
+
const revision = ((_a = start.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'customXmlInsRangeStart' ? 'ins' : ((_b = start.name) === null || _b === void 0 ? void 0 : _b.localPart) === 'customXmlDelRangeStart' ? 'del' : undefined;
|
|
964
|
+
if (!revision)
|
|
413
965
|
return undefined;
|
|
966
|
+
const [startName, endName] = revision === 'ins' ? ['customXmlInsRangeStart', 'customXmlInsRangeEnd'] : ['customXmlDelRangeStart', 'customXmlDelRangeEnd'];
|
|
414
967
|
const holder = items[i + 1];
|
|
415
968
|
const outerEnd = items[i + 2];
|
|
416
|
-
if (!holder || !outerEnd || !HOLDER_TYPES.has((
|
|
969
|
+
if (!holder || !outerEnd || !HOLDER_TYPES.has((_c = typeNameOf(holder)) !== null && _c !== void 0 ? _c : '') || ((_d = outerEnd.name) === null || _d === void 0 ? void 0 : _d.localPart) !== endName)
|
|
417
970
|
return undefined;
|
|
418
|
-
const inner = (
|
|
971
|
+
const inner = (_e = childrenOf(holder.value)) !== null && _e !== void 0 ? _e : [];
|
|
419
972
|
const innerEnd = inner[0];
|
|
420
973
|
const innerStart = inner[inner.length - 1];
|
|
421
|
-
if (inner.length < 2 || !innerEnd || !innerStart || ((
|
|
974
|
+
if (inner.length < 2 || !innerEnd || !innerStart || ((_f = innerEnd.name) === null || _f === void 0 ? void 0 : _f.localPart) !== endName || ((_g = innerStart.name) === null || _g === void 0 ? void 0 : _g.localPart) !== startName)
|
|
422
975
|
return undefined;
|
|
423
976
|
const idOf = (marker) => marker.value.id;
|
|
424
977
|
if (idOf(innerEnd) !== idOf(start) || idOf(outerEnd) !== idOf(innerStart))
|
|
425
978
|
return undefined;
|
|
426
|
-
return new TrackedChange({ kind: 'holder', holder, owner: items, outer: [start, outerEnd], inner: [innerEnd, innerStart], value: start.value }, paragraph);
|
|
979
|
+
return new TrackedChange({ kind: 'holder', revision, holder, owner: items, outer: [start, outerEnd], inner: [innerEnd, innerStart], value: start.value }, paragraph, body);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* An inserted field rejected (Word's form, check 30 `field-inserted`): the `w:ins` pieces and the plain
|
|
983
|
+
* runs between them go, last first so that a comment's range is judged once the field's text is gone;
|
|
984
|
+
* a bookmark, a comment's range or a proofing mark between them stays where it stood.
|
|
985
|
+
*/
|
|
986
|
+
function removeField(t, paragraph) {
|
|
987
|
+
for (let i = t.span.length - 1; i >= 0; i--) {
|
|
988
|
+
const el = t.span[i];
|
|
989
|
+
if (revisionKindOf(el) === 'ins')
|
|
990
|
+
removeRevision({ kind: 'run', revision: 'ins', element: el, owner: currentOwner(t.owner, el), value: el.value }, paragraph);
|
|
991
|
+
else if (typeNameOf(el) === 'org_docx4j_wml.R')
|
|
992
|
+
remove(t.owner, el);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Word's form for a field inserted under tracking, when `items[i]` starts it: a `w:ins` that opens a
|
|
997
|
+
* field (a `w:fldChar` begin it does not end), then plain runs - the separator and the result - and
|
|
998
|
+
* markers, then a `w:ins` by the same author in which that field ends (check 30, `field-inserted`:
|
|
999
|
+
* begin and instruction in one, the end run in the other). Another revision, a run whose formatting
|
|
1000
|
+
* changed, a run holder, or a field that ends in a plain run is not this form, and is listed as before.
|
|
1001
|
+
* A field wholly inside one `w:ins` needs nothing of this: that `w:ins` already takes it whole.
|
|
1002
|
+
*/
|
|
1003
|
+
export function insertedFieldAt(items, i, paragraph) {
|
|
1004
|
+
var _a;
|
|
1005
|
+
const first = items[i];
|
|
1006
|
+
if (revisionKindOf(first) !== 'ins')
|
|
1007
|
+
return undefined;
|
|
1008
|
+
const author = first.value.author;
|
|
1009
|
+
let depth = 0;
|
|
1010
|
+
// the fldChars of an element's runs in order: 'closed' when the field the span opened ends
|
|
1011
|
+
const scan = (el) => {
|
|
1012
|
+
var _a, _b, _c;
|
|
1013
|
+
const runs = typeNameOf(el) === 'org_docx4j_wml.R' ? [el] : (_a = runItemsOf(el.value)) !== null && _a !== void 0 ? _a : [];
|
|
1014
|
+
for (const run of runs) {
|
|
1015
|
+
if (typeNameOf(run) !== 'org_docx4j_wml.R')
|
|
1016
|
+
continue;
|
|
1017
|
+
for (const item of (_b = run.value.content) !== null && _b !== void 0 ? _b : []) {
|
|
1018
|
+
if (((_c = item.name) === null || _c === void 0 ? void 0 : _c.localPart) !== 'fldChar')
|
|
1019
|
+
continue;
|
|
1020
|
+
const type = item.value.fldCharType;
|
|
1021
|
+
if (type === 'begin')
|
|
1022
|
+
depth++;
|
|
1023
|
+
else if (type === 'end') {
|
|
1024
|
+
depth--;
|
|
1025
|
+
if (depth < 0)
|
|
1026
|
+
return 'other';
|
|
1027
|
+
if (depth === 0)
|
|
1028
|
+
return 'closed';
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return 'open';
|
|
1033
|
+
};
|
|
1034
|
+
if (scan(first) !== 'open' || depth === 0)
|
|
1035
|
+
return undefined;
|
|
1036
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
1037
|
+
const el = items[j];
|
|
1038
|
+
const kind = revisionKindOf(el);
|
|
1039
|
+
if (kind === 'ins') {
|
|
1040
|
+
if (el.value.author !== author)
|
|
1041
|
+
return undefined;
|
|
1042
|
+
const state = scan(el);
|
|
1043
|
+
if (state === 'other')
|
|
1044
|
+
return undefined;
|
|
1045
|
+
if (state === 'closed')
|
|
1046
|
+
return new TrackedChange({ kind: 'field', span: items.slice(i, j + 1), owner: items, value: first.value }, paragraph);
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (kind !== undefined)
|
|
1050
|
+
return undefined;
|
|
1051
|
+
const v = el.value;
|
|
1052
|
+
if (typeNameOf(el) === 'org_docx4j_wml.R') {
|
|
1053
|
+
if (((_a = v.rPr) === null || _a === void 0 ? void 0 : _a.rPrChange) || scan(el) !== 'open')
|
|
1054
|
+
return undefined;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (typeof v === 'object' && v !== null && runItemsOf(v))
|
|
1058
|
+
return undefined;
|
|
1059
|
+
}
|
|
1060
|
+
return undefined;
|
|
427
1061
|
}
|
|
428
1062
|
/**
|
|
429
1063
|
* A row taken away, and its table with it when it was the last: a table has no revision of its own,
|
|
@@ -432,68 +1066,204 @@ export function holderInsertionAt(items, i, paragraph) {
|
|
|
432
1066
|
*/
|
|
433
1067
|
function removeRow(t) {
|
|
434
1068
|
remove(t.owner, t.tr);
|
|
435
|
-
|
|
1069
|
+
// a row in any branch of a kept row-level mc:AlternateContent keeps the table (CR-013 phase 2, docx4j's
|
|
1070
|
+
// Lister.hasRows): counted over the selected branch alone, the table went with every branch's rows
|
|
1071
|
+
if (t.table && rowsOf(t.table.element.value, 'every').length === 0)
|
|
436
1072
|
remove(t.table.container, t.table.element);
|
|
437
1073
|
}
|
|
438
1074
|
/**
|
|
439
1075
|
* A revision taken away with its runs - a rejected insertion, an accepted deletion - and the comments
|
|
440
|
-
* on it,
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
1076
|
+
* on it, as Word 365 resolves them (CR-002 section 54; docx4j CR-043 section 15, `Resolver.removeRevision`):
|
|
1077
|
+
* a comment whose range start was inside the revision, or whose markers lie either side of it in its
|
|
1078
|
+
* list, goes whole - markers, reference run and entry - once nothing with text is left in its range.
|
|
1079
|
+
* - Word writes a comment's range start inside the `w:ins` of the text it is on; current Word removed
|
|
1080
|
+
* the comment with that text (check 32: rejecting the insertion, rejecting all, and accepting the
|
|
1081
|
+
* deletion in the same shape).
|
|
1082
|
+
* - Every marker outside the revision (this package's own form before 0.3.1, a tracker's that wraps
|
|
1083
|
+
* only the text): Word 365's Reject All of the editor's `review-for-word.docx` and of docx4j-python's
|
|
1084
|
+
* artefact 8, and its Accept All of a deletion (docx4j's U21), removed the comment whole too. Word
|
|
1085
|
+
* 2010 and Word 15 kept it, its range markers dropped, which was the rule here until section 54.
|
|
1086
|
+
* - A range whose start and end are in different paragraphs is judged when the resolution ends, once
|
|
1087
|
+
* the paragraph marks taken away have joined what is left (`resolving`): Word 365's Reject All of two
|
|
1088
|
+
* inserted paragraphs commented whole removed the comment (docx4j's U23). A range still crossing a
|
|
1089
|
+
* paragraph mark stays (docx4j's U24, not measured).
|
|
1090
|
+
* The markers and the reference go now, the entries in the comment parts when the comments are next
|
|
1091
|
+
* read or the package saved (`pendingCommentRemovals`). A comment with text left in its range keeps its
|
|
1092
|
+
* markers where the text was (not measured), and so does one whose range was empty before.
|
|
453
1093
|
*/
|
|
454
1094
|
function removeRevision(t, paragraph) {
|
|
455
|
-
var _a, _b, _c;
|
|
1095
|
+
var _a, _b, _c, _d;
|
|
456
1096
|
const owner = currentOwner(t.owner, t.element);
|
|
457
1097
|
const i = owner.indexOf(t.element);
|
|
458
1098
|
if (i < 0)
|
|
459
1099
|
return;
|
|
460
1100
|
const kept = ((_a = runItemsOf(t.element.value)) !== null && _a !== void 0 ? _a : []).filter((item) => { var _a, _b; return ((_a = item.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart' || ((_b = item.name) === null || _b === void 0 ? void 0 : _b.localPart) === 'commentRangeEnd'; });
|
|
461
|
-
// the comments whose range held the revision: a start inside it (Word's form)
|
|
462
|
-
// with its end after it (the
|
|
463
|
-
|
|
464
|
-
const inside = new Set(kept.filter((m) => { var _a; return ((_a = m.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart'; }).map(
|
|
1101
|
+
// the comments whose range held the revision: a start inside it (Word's form); a start before it
|
|
1102
|
+
// with its end after it (the markers beside the runs) or in another paragraph; an end after it with
|
|
1103
|
+
// its start in another paragraph
|
|
1104
|
+
const inside = new Set(kept.filter((m) => { var _a; return ((_a = m.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart'; }).map(commentIdOf));
|
|
465
1105
|
const outside = new Set();
|
|
466
1106
|
for (let s = 0; s < i; s++) {
|
|
467
1107
|
const el = owner[s];
|
|
468
1108
|
if (((_b = el.name) === null || _b === void 0 ? void 0 : _b.localPart) !== 'commentRangeStart')
|
|
469
1109
|
continue;
|
|
470
|
-
const id =
|
|
471
|
-
if (owner
|
|
1110
|
+
const id = commentIdOf(el);
|
|
1111
|
+
if (markerAt(owner, s + 1, owner.length, 'commentRangeEnd', id) < 0 || markerAt(owner, i + 1, owner.length, 'commentRangeEnd', id) >= 0)
|
|
472
1112
|
outside.add(id);
|
|
473
1113
|
}
|
|
1114
|
+
for (let e = i + 1; e < owner.length; e++) {
|
|
1115
|
+
const el = owner[e];
|
|
1116
|
+
if (((_c = el.name) === null || _c === void 0 ? void 0 : _c.localPart) === 'commentRangeEnd' && markerAt(owner, 0, e, 'commentRangeStart', commentIdOf(el)) < 0)
|
|
1117
|
+
outside.add(commentIdOf(el));
|
|
1118
|
+
}
|
|
1119
|
+
discarding(t.element);
|
|
474
1120
|
owner.splice(i, 1, ...kept);
|
|
475
1121
|
linkParents(kept, t.element.value.PARENT);
|
|
476
1122
|
const body = paragraph === null || paragraph === void 0 ? void 0 : paragraph.parentBody;
|
|
477
1123
|
if (!body)
|
|
478
1124
|
return;
|
|
479
1125
|
for (const id of [...inside, ...outside]) {
|
|
480
|
-
const start = owner
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
removeMarkers(body.container, id);
|
|
485
|
-
const pkg = body.package_;
|
|
486
|
-
(_c = pkg === null || pkg === void 0 ? void 0 : pkg.pendingCommentRemovals) === null || _c === void 0 ? void 0 : _c.add(id);
|
|
1126
|
+
const start = markerAt(owner, 0, owner.length, 'commentRangeStart', id);
|
|
1127
|
+
if (start < 0 || markerAt(owner, start + 1, owner.length, 'commentRangeEnd', id) < 0) {
|
|
1128
|
+
if (resolution)
|
|
1129
|
+
((_d = resolution.comments) !== null && _d !== void 0 ? _d : (resolution.comments = new Map())).set(id, body); // start and end in different paragraphs
|
|
487
1130
|
}
|
|
488
|
-
else {
|
|
489
|
-
|
|
490
|
-
const end = owner.find((el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeEnd' && idOf(el) === id; });
|
|
491
|
-
for (const marker of [end, start])
|
|
492
|
-
if (marker)
|
|
493
|
-
owner.splice(owner.indexOf(marker), 1);
|
|
1131
|
+
else if (rangeIsEmpty(owner, owner[start], id)) {
|
|
1132
|
+
removeCommentWhole(body, id);
|
|
494
1133
|
}
|
|
495
1134
|
}
|
|
496
1135
|
}
|
|
1136
|
+
const commentIdOf = (el) => { var _a; return (_a = el.value) === null || _a === void 0 ? void 0 : _a.id; };
|
|
1137
|
+
/** The index in `list`, from `from` up to `to`, of a comment's range start or end (`local`), or -1. */
|
|
1138
|
+
function markerAt(list, from, to, local, id) {
|
|
1139
|
+
var _a;
|
|
1140
|
+
for (let i = from; i < to; i++)
|
|
1141
|
+
if (((_a = list[i].name) === null || _a === void 0 ? void 0 : _a.localPart) === local && commentIdOf(list[i]) === id)
|
|
1142
|
+
return i;
|
|
1143
|
+
return -1;
|
|
1144
|
+
}
|
|
1145
|
+
/** A comment taken away: its markers and reference run now, its entries when the comments are next read or the package saved. */
|
|
1146
|
+
function removeCommentWhole(body, id) {
|
|
1147
|
+
var _a;
|
|
1148
|
+
removeMarkers(body.container, id);
|
|
1149
|
+
const pkg = body.package_;
|
|
1150
|
+
(_a = pkg === null || pkg === void 0 ? void 0 : pkg.pendingCommentRemovals) === null || _a === void 0 ? void 0 : _a.add(id);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* The comments `removeRevision` left with their range's start and end in different lists, judged once
|
|
1154
|
+
* the resolution's joins are made (docx4j's `Removals.removeCommentIfEmptied`): one goes whole when its
|
|
1155
|
+
* start and end now stand in one list with nothing with text between them.
|
|
1156
|
+
*/
|
|
1157
|
+
function settleSpanningComments(comments) {
|
|
1158
|
+
for (const [id, body] of comments) {
|
|
1159
|
+
const list = listHolding(storyRootOf(body), (el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart' && commentIdOf(el) === id; });
|
|
1160
|
+
if (!list)
|
|
1161
|
+
continue;
|
|
1162
|
+
const start = markerAt(list, 0, list.length, 'commentRangeStart', id);
|
|
1163
|
+
if (rangeIsEmpty(list, list[start], id))
|
|
1164
|
+
removeCommentWhole(body, id);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* The comments whose reference or range marker the resolution removed, judged once it ends, as docx4j's
|
|
1169
|
+
* `Removals.flush` judges its candidates (its CR-043; core-ts check 32): one whose reference no longer stands
|
|
1170
|
+
* anywhere in the story goes whole, since Word keeps no comment without one; one with its reference and half its
|
|
1171
|
+
* range left loses that half. A tracked deletion holds a comment's reference where it took one (CR-014 section
|
|
1172
|
+
* 12.6, Word's case 28), so accepting it takes the comment, as the untracked deletion does.
|
|
1173
|
+
*/
|
|
1174
|
+
function settleCommentAnchors(root, body, candidates) {
|
|
1175
|
+
var _a;
|
|
1176
|
+
const pending = (_a = body.package_) === null || _a === void 0 ? void 0 : _a.pendingCommentRemovals;
|
|
1177
|
+
const references = new Set();
|
|
1178
|
+
const starts = new Set();
|
|
1179
|
+
const ends = new Set();
|
|
1180
|
+
forEachCommentAnchor(root, (name, id) => (name === 'commentReference' ? references : name === 'commentRangeStart' ? starts : ends).add(id));
|
|
1181
|
+
for (const id of candidates) {
|
|
1182
|
+
if (pending === null || pending === void 0 ? void 0 : pending.has(id))
|
|
1183
|
+
continue;
|
|
1184
|
+
if (!references.has(id))
|
|
1185
|
+
removeCommentWhole(body, id);
|
|
1186
|
+
else if (starts.has(id) !== ends.has(id))
|
|
1187
|
+
removeRangeMarkers(root, id);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
/** Adds the ids of the comment references and range markers anywhere in a tree to `into`. */
|
|
1191
|
+
function commentAnchorsIn(tree, into) {
|
|
1192
|
+
forEachCommentAnchor(tree, (_name, id) => into.add(id));
|
|
1193
|
+
}
|
|
1194
|
+
/** Calls `found` for each comment reference, range start and range end anywhere in a tree. */
|
|
1195
|
+
function forEachCommentAnchor(tree, found) {
|
|
1196
|
+
const visit = (o) => {
|
|
1197
|
+
var _a, _b;
|
|
1198
|
+
if (typeof o !== 'object' || o === null)
|
|
1199
|
+
return;
|
|
1200
|
+
if (Array.isArray(o)) {
|
|
1201
|
+
for (const item of o)
|
|
1202
|
+
visit(item);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const name = (_a = o.name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
1206
|
+
if (name === 'commentReference' || name === 'commentRangeStart' || name === 'commentRangeEnd') {
|
|
1207
|
+
const id = Number((_b = (o.value)) === null || _b === void 0 ? void 0 : _b.id);
|
|
1208
|
+
if (Number.isInteger(id))
|
|
1209
|
+
found(name, id);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
for (const [key, value] of Object.entries(o))
|
|
1213
|
+
if (key !== 'PARENT')
|
|
1214
|
+
visit(value);
|
|
1215
|
+
};
|
|
1216
|
+
visit(tree);
|
|
1217
|
+
}
|
|
1218
|
+
/** A comment's range markers taken out of a tree, its reference kept: what is left of half a range. */
|
|
1219
|
+
function removeRangeMarkers(tree, id) {
|
|
1220
|
+
const visit = (o) => {
|
|
1221
|
+
var _a;
|
|
1222
|
+
if (typeof o !== 'object' || o === null)
|
|
1223
|
+
return;
|
|
1224
|
+
if (Array.isArray(o)) {
|
|
1225
|
+
for (let i = o.length - 1; i >= 0; i--) {
|
|
1226
|
+
const el = o[i];
|
|
1227
|
+
const name = (_a = el === null || el === void 0 ? void 0 : el.name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
1228
|
+
if ((name === 'commentRangeStart' || name === 'commentRangeEnd') && commentIdOf(el) === id)
|
|
1229
|
+
o.splice(i, 1);
|
|
1230
|
+
else
|
|
1231
|
+
visit(el);
|
|
1232
|
+
}
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
for (const [key, value] of Object.entries(o))
|
|
1236
|
+
if (key !== 'PARENT')
|
|
1237
|
+
visit(value);
|
|
1238
|
+
};
|
|
1239
|
+
visit(tree);
|
|
1240
|
+
}
|
|
1241
|
+
/** The list under `root` (block, run and run-content lists, math's) holding an item `match` matches, or undefined. */
|
|
1242
|
+
function listHolding(root, match) {
|
|
1243
|
+
let found;
|
|
1244
|
+
const seen = new Set();
|
|
1245
|
+
const visit = (o) => {
|
|
1246
|
+
if (found || typeof o !== 'object' || o === null || seen.has(o))
|
|
1247
|
+
return;
|
|
1248
|
+
seen.add(o);
|
|
1249
|
+
if (Array.isArray(o)) {
|
|
1250
|
+
if (o.some((item) => typeof item === 'object' && item !== null && match(item))) {
|
|
1251
|
+
found = o;
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
for (const item of o)
|
|
1255
|
+
visit(item);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (o.nodeType !== undefined)
|
|
1259
|
+
return; // a DOM node
|
|
1260
|
+
for (const key in o)
|
|
1261
|
+
if (key !== 'PARENT')
|
|
1262
|
+
visit(o[key]);
|
|
1263
|
+
};
|
|
1264
|
+
visit(root);
|
|
1265
|
+
return found;
|
|
1266
|
+
}
|
|
497
1267
|
/** True when nothing with text stands between a comment's range start and its end in the same list. */
|
|
498
1268
|
function rangeIsEmpty(owner, start, id) {
|
|
499
1269
|
var _a;
|
|
@@ -524,11 +1294,13 @@ function hasText(el) {
|
|
|
524
1294
|
/** A `w:ins` or `w:moveTo` accepted: its runs take its place. */
|
|
525
1295
|
function unwrap(t) {
|
|
526
1296
|
var _a;
|
|
527
|
-
const
|
|
1297
|
+
const owner = currentOwner(t.owner, t.element);
|
|
1298
|
+
const i = owner.indexOf(t.element);
|
|
528
1299
|
if (i < 0)
|
|
529
1300
|
return;
|
|
530
|
-
|
|
531
|
-
t.
|
|
1301
|
+
// the revision keeps nothing, so a view listed inside it no longer finds its element there (section 53)
|
|
1302
|
+
const items = ((_a = runItemsOf(t.element.value)) !== null && _a !== void 0 ? _a : []).splice(0);
|
|
1303
|
+
owner.splice(i, 1, ...items);
|
|
532
1304
|
const parent = t.value.PARENT;
|
|
533
1305
|
for (const item of items)
|
|
534
1306
|
linkParents(item, parent);
|
|
@@ -558,27 +1330,84 @@ function dropMark(paragraph, which) {
|
|
|
558
1330
|
*
|
|
559
1331
|
* `fallbackToPrevious` is for the second case at the end of a container, where this package's
|
|
560
1332
|
* inserted paragraph carries its own mark (section 13) and there is nothing after it to join
|
|
561
|
-
* with: the paragraph then goes, its content joining the one before.
|
|
562
|
-
*
|
|
1333
|
+
* with: the paragraph then goes, its content joining the one before. Otherwise, with no paragraph
|
|
1334
|
+
* next, a join never crosses a container's edge, and Word 365's Accept All (check 54 and the text-left
|
|
1335
|
+
* probe, docx4j CR-043 section 17) does one of four things: an emptied paragraph with a table or a
|
|
1336
|
+
* block control next goes; text left with a table next joins the table's first cell's first paragraph,
|
|
1337
|
+
* which keeps its properties; with a block control next it takes the control's first paragraph's
|
|
1338
|
+
* properties, nothing joined; a control's last paragraph keeps a bare mark. Else the mark is only dropped.
|
|
563
1339
|
*/
|
|
564
1340
|
export function joinWithNext(paragraph, fallbackToPrevious = false) {
|
|
565
|
-
var _a, _b, _c, _d, _e;
|
|
1341
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
1342
|
+
discarding();
|
|
566
1343
|
const container = currentOwner(paragraph.container, paragraph.element);
|
|
567
1344
|
const i = container.indexOf(paragraph.element);
|
|
568
|
-
|
|
1345
|
+
// range markers between the two (a bookmark's end, a move's range end) go into the joined paragraph
|
|
1346
|
+
// where they stood, as docx4j's join (its CR-043, C10 and C10b) and Word's: found by CR-011's goldens
|
|
1347
|
+
let j = i + 1;
|
|
1348
|
+
while (i >= 0 && j < container.length && isRangeMarker(container[j]))
|
|
1349
|
+
j++;
|
|
1350
|
+
const next = i >= 0 ? container[j] : undefined;
|
|
569
1351
|
const p = paragraph.p;
|
|
570
1352
|
if (!next || typeNameOf(next) !== 'org_docx4j_wml.P') {
|
|
571
1353
|
const previous = fallbackToPrevious && i > 0 ? container[i - 1] : undefined;
|
|
572
1354
|
if (previous && typeNameOf(previous) === 'org_docx4j_wml.P') {
|
|
573
1355
|
const into = previous.value;
|
|
574
1356
|
const items = ((_a = into.content) !== null && _a !== void 0 ? _a : (into.content = []));
|
|
575
|
-
for (const item of (_b = p.content) !== null && _b !== void 0 ? _b : [])
|
|
576
|
-
items.push(item);
|
|
1357
|
+
for (const item of ((_b = p.content) !== null && _b !== void 0 ? _b : []).splice(0))
|
|
1358
|
+
items.push(item); // this paragraph keeps nothing
|
|
577
1359
|
linkParents(items, into);
|
|
1360
|
+
discarding(undefined, p.pPr);
|
|
578
1361
|
container.splice(i, 1);
|
|
579
1362
|
return;
|
|
580
1363
|
}
|
|
581
|
-
|
|
1364
|
+
// Word 365's Accept All where no paragraph follows (check 54 and the text-left probe; docx4j CR-043 section 17,
|
|
1365
|
+
// 29dbf5964 and e55a83b8e): a join never crosses a container's edge, and four shapes do something besides dropping
|
|
1366
|
+
// the mark, in docx4j's order
|
|
1367
|
+
const emptied = !((_c = p.content) !== null && _c !== void 0 ? _c : []).some((item) => !isRangeMarker(item));
|
|
1368
|
+
const table = next !== undefined && typeNameOf(next) === 'org_docx4j_wml.Tbl';
|
|
1369
|
+
const control = next !== undefined && HOLDER_TYPES.has((_d = typeNameOf(next)) !== null && _d !== void 0 ? _d : '');
|
|
1370
|
+
if (i >= 0 && emptied && (table || control)) {
|
|
1371
|
+
// check 54 case 9, the probe's case 3: an emptied paragraph with a table or a block control next goes, which
|
|
1372
|
+
// then follows what came before
|
|
1373
|
+
discarding(undefined, p.pPr);
|
|
1374
|
+
container.splice(i, 1);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (i >= 0 && table) {
|
|
1378
|
+
// the probe's case 1: text left with a table next joins the table's first cell's first paragraph, at its front,
|
|
1379
|
+
// the range markers after it with it; that paragraph keeps its own properties
|
|
1380
|
+
const first = firstCellParagraphOf(next.value);
|
|
1381
|
+
if (first) {
|
|
1382
|
+
const moved = [...((_e = p.content) !== null && _e !== void 0 ? _e : []).splice(0), ...container.slice(i + 1, j)];
|
|
1383
|
+
((_f = first.content) !== null && _f !== void 0 ? _f : (first.content = [])).splice(0, 0, ...moved);
|
|
1384
|
+
linkParents(moved, first);
|
|
1385
|
+
discarding(undefined, p.pPr);
|
|
1386
|
+
container.splice(i, j - i);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (control) {
|
|
1391
|
+
// check 54 case 19, the probe's case 5: text left with a block control next: nothing joins, but the paragraph
|
|
1392
|
+
// takes the properties a join would have given it, the control's first paragraph's
|
|
1393
|
+
const first = firstParagraphOf(childrenOf(next.value));
|
|
1394
|
+
if (first) {
|
|
1395
|
+
discarding(undefined, p.pPr);
|
|
1396
|
+
p.pPr = first.pPr ? deepCopy(first.pPr) : undefined;
|
|
1397
|
+
if (p.pPr)
|
|
1398
|
+
linkParents(p.pPr, p);
|
|
1399
|
+
else
|
|
1400
|
+
delete p.pPr;
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (i >= 0 && next === undefined && SDT_CONTENTS.has((_h = (_g = p.PARENT) === null || _g === void 0 ? void 0 : _g.TYPE_NAME) !== null && _h !== void 0 ? _h : '')) {
|
|
1405
|
+
// check 54 case 13, the probe's case 2: a control's last paragraph keeps a bare mark, its text with it, if any
|
|
1406
|
+
discarding(undefined, p.pPr);
|
|
1407
|
+
delete p.pPr;
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const rPr = (_j = p.pPr) === null || _j === void 0 ? void 0 : _j.rPr;
|
|
582
1411
|
if (rPr) {
|
|
583
1412
|
delete rPr.ins;
|
|
584
1413
|
delete rPr.del;
|
|
@@ -587,10 +1416,13 @@ export function joinWithNext(paragraph, fallbackToPrevious = false) {
|
|
|
587
1416
|
return;
|
|
588
1417
|
}
|
|
589
1418
|
const nextP = next.value;
|
|
590
|
-
const content = ((
|
|
591
|
-
for (const item of (
|
|
1419
|
+
const content = ((_k = p.content) !== null && _k !== void 0 ? _k : (p.content = []));
|
|
1420
|
+
for (const item of container.slice(i + 1, j))
|
|
592
1421
|
content.push(item);
|
|
1422
|
+
for (const item of ((_l = nextP.content) !== null && _l !== void 0 ? _l : []).splice(0))
|
|
1423
|
+
content.push(item); // the next keeps nothing
|
|
593
1424
|
linkParents(content, p);
|
|
1425
|
+
discarding(undefined, p.pPr); // a section break's sectPr goes with it
|
|
594
1426
|
p.pPr = nextP.pPr;
|
|
595
1427
|
if (p.pPr)
|
|
596
1428
|
linkParents(p.pPr, p);
|
|
@@ -605,7 +1437,42 @@ export function joinWithNext(paragraph, fallbackToPrevious = false) {
|
|
|
605
1437
|
else
|
|
606
1438
|
p[key] = value;
|
|
607
1439
|
}
|
|
608
|
-
container.splice(i + 1,
|
|
1440
|
+
container.splice(i + 1, j - i);
|
|
1441
|
+
}
|
|
1442
|
+
/** A block-level control's content, which holds paragraphs. */
|
|
1443
|
+
const SDT_CONTENTS = new Set(['org_docx4j_wml.SdtContentBlock', 'org_docx4j_wml.CTSdtContentBlock']);
|
|
1444
|
+
/** The first paragraph of a table's first cell, where a paragraph joining into the table lands; docx4j's `firstCellParagraph`. */
|
|
1445
|
+
export function firstCellParagraphOf(tbl) {
|
|
1446
|
+
var _a, _b;
|
|
1447
|
+
for (const row of ((_a = tbl.content) !== null && _a !== void 0 ? _a : [])) {
|
|
1448
|
+
if (typeNameOf(row) !== 'org_docx4j_wml.Tr')
|
|
1449
|
+
continue;
|
|
1450
|
+
for (const cell of ((_b = row.value.content) !== null && _b !== void 0 ? _b : [])) {
|
|
1451
|
+
if (typeNameOf(cell) !== 'org_docx4j_wml.Tc')
|
|
1452
|
+
continue;
|
|
1453
|
+
return firstParagraphOf(cell.value.content);
|
|
1454
|
+
}
|
|
1455
|
+
return undefined;
|
|
1456
|
+
}
|
|
1457
|
+
return undefined;
|
|
1458
|
+
}
|
|
1459
|
+
/** The first paragraph among a holder's blocks, before any table; docx4j's `firstParagraph`. */
|
|
1460
|
+
function firstParagraphOf(items) {
|
|
1461
|
+
for (const item of items !== null && items !== void 0 ? items : []) {
|
|
1462
|
+
const tn = typeNameOf(item);
|
|
1463
|
+
if (tn === 'org_docx4j_wml.P')
|
|
1464
|
+
return item.value;
|
|
1465
|
+
if (tn === 'org_docx4j_wml.Tbl')
|
|
1466
|
+
return undefined;
|
|
1467
|
+
}
|
|
1468
|
+
return undefined;
|
|
1469
|
+
}
|
|
1470
|
+
/** A marker that stands between two paragraphs without keeping them apart: a range's start or end, a bookmark's, a permission's, a proofing mark. */
|
|
1471
|
+
function isRangeMarker(el) {
|
|
1472
|
+
var _a, _b;
|
|
1473
|
+
const name = (_b = (_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : '';
|
|
1474
|
+
return name.endsWith('RangeStart') || name.endsWith('RangeEnd') || name === 'bookmarkStart' || name === 'bookmarkEnd'
|
|
1475
|
+
|| name === 'permStart' || name === 'permEnd' || name === 'proofErr';
|
|
609
1476
|
}
|
|
610
1477
|
/** A `w:p`'s own attributes, which go with its mark. */
|
|
611
1478
|
const PARAGRAPH_ATTRIBUTES = ['paraId', 'textId', 'rsidDel', 'rsidP', 'rsidR', 'rsidRDefault', 'rsidRPr', 'noSpellErr'];
|
|
@@ -618,39 +1485,175 @@ function pruneRowProperties(tr) {
|
|
|
618
1485
|
}
|
|
619
1486
|
/** The span of a run-level revision in the paragraph's accepted text; collapsed for a deletion. */
|
|
620
1487
|
function spanOf(paragraph, element) {
|
|
621
|
-
|
|
1488
|
+
// where the walk passes the element: a deletion stands there in the accepted text, before what follows it
|
|
1489
|
+
let at;
|
|
1490
|
+
const segs = paragraph.segments({ visit: (el, offset) => { if (el === element && at === undefined)
|
|
1491
|
+
at = offset; } });
|
|
622
1492
|
const inside = segs.filter((s) => { var _a; return ((_a = s.revision) === null || _a === void 0 ? void 0 : _a.element) === element; });
|
|
623
1493
|
if (inside.length > 0)
|
|
624
1494
|
return [inside[0].start, inside[inside.length - 1].end];
|
|
625
|
-
const
|
|
626
|
-
return [at, at];
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1495
|
+
const end = segs.length > 0 ? segs[segs.length - 1].end : 0;
|
|
1496
|
+
return [at !== null && at !== void 0 ? at : end, at !== null && at !== void 0 ? at : end];
|
|
1497
|
+
}
|
|
1498
|
+
// --- math (CR-002 section 51) ------------------------------------------------------------------
|
|
1499
|
+
/** True for a value of the math model (`m:oMath`, `m:r`, `m:sSup`, ...). */
|
|
1500
|
+
function isMathValue(value) {
|
|
1501
|
+
const tn = value === null || value === void 0 ? void 0 : value.TYPE_NAME;
|
|
1502
|
+
return typeof tn === 'string' && tn.startsWith('org_docx4j_math.');
|
|
1503
|
+
}
|
|
1504
|
+
/** The text of an `m:t` (or a `w:t`) in an `m:r`; undefined for anything else. */
|
|
1505
|
+
function mathTextOf(item) {
|
|
1506
|
+
var _a, _b, _c;
|
|
1507
|
+
if (((_a = item === null || item === void 0 ? void 0 : item.name) === null || _a === void 0 ? void 0 : _a.localPart) !== 't')
|
|
1508
|
+
return undefined;
|
|
1509
|
+
return String((_c = (_b = item.value) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : '');
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* A math run's revision resolved: `keep` (an insertion accepted, a deletion rejected) puts its content in
|
|
1513
|
+
* its place in the `m:r`; otherwise it goes, and the `m:r` with it when no text is left in it (docx4j's
|
|
1514
|
+
* reading of Word's Accept All and Reject All of `tracked-changes-equations.docx`).
|
|
1515
|
+
*/
|
|
1516
|
+
function resolveMathRun(t, keep) {
|
|
1517
|
+
var _a, _b;
|
|
1518
|
+
const content = ((_a = t.run.value.content) !== null && _a !== void 0 ? _a : []);
|
|
1519
|
+
const i = content.indexOf(t.item);
|
|
1520
|
+
if (i < 0)
|
|
1521
|
+
return;
|
|
1522
|
+
if (keep) {
|
|
1523
|
+
const moved = ((_b = t.value.rest) !== null && _b !== void 0 ? _b : []).splice(0);
|
|
1524
|
+
content.splice(i, 1, ...moved);
|
|
1525
|
+
linkParents(moved, t.run.value);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
discarding(t.item);
|
|
1529
|
+
content.splice(i, 1);
|
|
1530
|
+
if (!content.some((item) => { var _a; return mathTextOf(item) !== undefined || ((_a = item.value) === null || _a === void 0 ? void 0 : _a.TYPE_NAME) === 'org_docx4j_wml.CTMathRunTrackChange'; })) {
|
|
1531
|
+
const container = t.run.value.PARENT;
|
|
1532
|
+
remove(t.runOwner, t.run);
|
|
1533
|
+
dropEmptiedEquation(container);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* A structure's insertion or deletion resolved: `keep` drops the marker, its recorded run properties
|
|
1538
|
+
* becoming the control's when it has none, as Word's saves have them; otherwise the structure goes
|
|
1539
|
+
* whole from its list. An argument's own marker has no structure, and is dropped either way.
|
|
1540
|
+
*/
|
|
1541
|
+
function resolveMathStructure(t, keep) {
|
|
1542
|
+
var _a, _b;
|
|
1543
|
+
if (!keep && t.structure && t.owner) {
|
|
1544
|
+
const container = (_a = t.structure.value) === null || _a === void 0 ? void 0 : _a.PARENT;
|
|
1545
|
+
remove(t.owner, t.structure);
|
|
1546
|
+
dropEmptiedEquation(container);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const ctrlPr = t.ctrlPr;
|
|
1550
|
+
if (!ctrlPr.rPr && t.value.rPr) {
|
|
1551
|
+
ctrlPr.rPr = rPrFromElements((_b = t.value.rPr.egrPrBase) !== null && _b !== void 0 ? _b : []);
|
|
1552
|
+
linkParents(ctrlPr.rPr, ctrlPr);
|
|
1553
|
+
}
|
|
1554
|
+
if (t.revision === 'ins')
|
|
1555
|
+
delete ctrlPr.ins;
|
|
1556
|
+
else
|
|
1557
|
+
delete ctrlPr.del;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* An `m:oMath` that a resolution left with nothing in it goes, and an `m:oMathPara` left with none of its
|
|
1561
|
+
* `m:oMath` goes with it, as Word's Accept All of an equation deleted whole has it (check 57, CR-002 section
|
|
1562
|
+
* 62.2, F1; docx4j's `Resolver.mathRun` leaves them). A paragraph whose mark went with the equation is then
|
|
1563
|
+
* empty, or joined to the next one empty-handed. An argument left empty stays: its structure keeps its places.
|
|
1564
|
+
*/
|
|
1565
|
+
function dropEmptiedEquation(container) {
|
|
1566
|
+
var _a;
|
|
1567
|
+
const equation = container;
|
|
1568
|
+
if ((equation === null || equation === void 0 ? void 0 : equation.TYPE_NAME) !== 'org_docx4j_math.CTOMath' || ((_a = equation.egoMathElements) !== null && _a !== void 0 ? _a : []).length > 0)
|
|
1569
|
+
return;
|
|
1570
|
+
const holder = equation.PARENT;
|
|
1571
|
+
if ((holder === null || holder === void 0 ? void 0 : holder.TYPE_NAME) === 'org_docx4j_math.CTOMathPara') {
|
|
1572
|
+
const i = holder.oMath.indexOf(equation);
|
|
1573
|
+
if (i >= 0)
|
|
1574
|
+
holder.oMath.splice(i, 1);
|
|
1575
|
+
if (holder.oMath.length === 0)
|
|
1576
|
+
removeValue(holder);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
removeValue(equation);
|
|
1580
|
+
}
|
|
1581
|
+
/** A value's element taken from the list its `PARENT` keeps. */
|
|
1582
|
+
function removeValue(value) {
|
|
1583
|
+
var _a;
|
|
1584
|
+
const parent = value.PARENT;
|
|
1585
|
+
const list = parent ? (_a = runItemsOf(parent)) !== null && _a !== void 0 ? _a : childrenOf(parent) : undefined;
|
|
1586
|
+
const element = list === null || list === void 0 ? void 0 : list.find((e) => e === value || (e === null || e === void 0 ? void 0 : e.value) === value);
|
|
1587
|
+
if (list && element)
|
|
1588
|
+
remove(list, element);
|
|
1589
|
+
}
|
|
1590
|
+
/** The equation a math piece is in: the outermost level's element, the one in the paragraph. */
|
|
1591
|
+
function equationOf(stack) {
|
|
1592
|
+
var _a;
|
|
1593
|
+
const top = (_a = stack[0]) === null || _a === void 0 ? void 0 : _a.item;
|
|
1594
|
+
return top && typeof top === 'object' && 'name' in top ? { equation: top } : {};
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* An equation's single changes, in document order, docx4j's walk (its CR-043 `Lister.math`): a
|
|
1598
|
+
* revision inside an `m:r`; a structure's, in its `m:ctrlPr`, which comes before the structure's
|
|
1599
|
+
* arguments; and a run-level revision holding math. A math run with text and no revision keeps the
|
|
1600
|
+
* pieces either side apart, as plain text does.
|
|
1601
|
+
*/
|
|
1602
|
+
function mathTokens(paragraph, value, item, owner, stack, out) {
|
|
1603
|
+
var _a;
|
|
1604
|
+
const tn = value.TYPE_NAME;
|
|
1605
|
+
if (tn === 'org_docx4j_math.CTR') {
|
|
1606
|
+
for (const c of ((_a = value.content) !== null && _a !== void 0 ? _a : [])) {
|
|
1607
|
+
const cv = c === null || c === void 0 ? void 0 : c.value;
|
|
1608
|
+
if ((cv === null || cv === void 0 ? void 0 : cv.TYPE_NAME) === 'org_docx4j_wml.CTMathRunTrackChange' && owner) {
|
|
1609
|
+
out.push(new TrackedChange({ kind: 'mathRun', revision: c.name.localPart === 'del' ? 'del' : 'ins', item: c, run: item, runOwner: owner, value: cv, ...equationOf(stack) }, paragraph));
|
|
641
1610
|
}
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
1611
|
+
else if (mathTextOf(c) !== undefined)
|
|
1612
|
+
out.push(BREAK);
|
|
1613
|
+
}
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
if (tn === 'org_docx4j_math.CTCtrlPr') {
|
|
1617
|
+
const ctrlPr = value;
|
|
1618
|
+
if (!ctrlPr.ins && !ctrlPr.del)
|
|
1619
|
+
return;
|
|
1620
|
+
// in m:sSupPr in m:sSup, the structure is two up; an argument's (m:e's) own marks none
|
|
1621
|
+
const parent = stack[stack.length - 1];
|
|
1622
|
+
const grand = parent && parent.value.TYPE_NAME !== 'org_docx4j_math.CTOMathArg' ? stack[stack.length - 2] : undefined;
|
|
1623
|
+
const structure = { ...((grand === null || grand === void 0 ? void 0 : grand.owner) ? { structure: grand.item, owner: grand.owner } : {}), ...equationOf(stack) };
|
|
1624
|
+
if (ctrlPr.ins)
|
|
1625
|
+
out.push(new TrackedChange({ kind: 'mathStructure', revision: 'ins', ctrlPr, value: ctrlPr.ins, ...structure }, paragraph));
|
|
1626
|
+
if (ctrlPr.del)
|
|
1627
|
+
out.push(new TrackedChange({ kind: 'mathStructure', revision: 'del', ctrlPr, value: ctrlPr.del, ...structure }, paragraph));
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
stack.push({ value, item, owner });
|
|
1631
|
+
for (const key of Object.keys(value)) {
|
|
1632
|
+
if (key === 'PARENT' || key === 'TYPE_NAME')
|
|
1633
|
+
continue;
|
|
1634
|
+
const field = value[key];
|
|
1635
|
+
if (typeof field !== 'object' || field === null)
|
|
1636
|
+
continue;
|
|
1637
|
+
if (Array.isArray(field)) {
|
|
1638
|
+
const list = field;
|
|
1639
|
+
for (const c of list) {
|
|
1640
|
+
if (typeof c !== 'object' || c === null)
|
|
1641
|
+
continue;
|
|
1642
|
+
const kind = revisionKindOf(c);
|
|
1643
|
+
if (kind !== undefined) {
|
|
1644
|
+
// a w:ins or w:del holding m:r: as runs (schema-legal; docx4j's U7, in no fixture)
|
|
1645
|
+
out.push(new TrackedChange({ kind: 'run', revision: kind, element: c, owner: list, value: c.value }, paragraph));
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
const cv = c !== null && typeof c === 'object' && 'value' in c && 'name' in c ? c.value : c;
|
|
1649
|
+
if (isMathValue(cv))
|
|
1650
|
+
mathTokens(paragraph, cv, c, list, stack, out);
|
|
645
1651
|
}
|
|
646
|
-
const kind = revisionKindOf(el);
|
|
647
|
-
if (kind === 'del' || kind === 'moveFrom')
|
|
648
|
-
continue;
|
|
649
|
-
visit(runItemsOf(el.value));
|
|
650
1652
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1653
|
+
else if (isMathValue(field))
|
|
1654
|
+
mathTokens(paragraph, field, field, undefined, stack, out);
|
|
1655
|
+
}
|
|
1656
|
+
stack.pop();
|
|
654
1657
|
}
|
|
655
1658
|
const RANGE_MARKERS = {
|
|
656
1659
|
moveFromRangeStart: { kind: 'start', side: 'from' }, moveFromRangeEnd: { kind: 'end', side: 'from' },
|
|
@@ -671,7 +1674,7 @@ function resolveMove(change, action) {
|
|
|
671
1674
|
const t = change.target;
|
|
672
1675
|
if (t.kind !== 'run' || !change.paragraph)
|
|
673
1676
|
return false;
|
|
674
|
-
const story = storyOf(
|
|
1677
|
+
const story = storyOf(t.element.value);
|
|
675
1678
|
const pieces = movePiecesOf(story);
|
|
676
1679
|
const half = pieces.find((piece) => piece.kind === 'half' && piece.element === t.element);
|
|
677
1680
|
if (!half || half.kind !== 'half' || half.names.length === 0)
|
|
@@ -736,22 +1739,36 @@ function resolveMove(change, action) {
|
|
|
736
1739
|
dissolveBrokenMoves(story);
|
|
737
1740
|
return true;
|
|
738
1741
|
}
|
|
739
|
-
/**
|
|
740
|
-
|
|
1742
|
+
/**
|
|
1743
|
+
* The story a paragraph is in, for its moves: the outermost array of block content above it (a body, a
|
|
1744
|
+
* header, a note). A kept `mc:AlternateContent`'s selected branch is part of the story it stands in, as
|
|
1745
|
+
* Word reads it; another branch is a story of its own, its list (docx4j's twin stories, its CR-051 phase
|
|
1746
|
+
* 2; CR-013 phase 3), so a move is paired within the branch. `value` may be the paragraph or anything in
|
|
1747
|
+
* it, a run-level branch's revision among them.
|
|
1748
|
+
*/
|
|
1749
|
+
export function storyOf(value) {
|
|
741
1750
|
let story;
|
|
742
|
-
for (let o =
|
|
1751
|
+
for (let o = value; o !== undefined; o = o.PARENT) {
|
|
1752
|
+
const tn = o.TYPE_NAME;
|
|
1753
|
+
if (tn !== undefined && BRANCH_TYPES.has(tn)) {
|
|
1754
|
+
const ac = o.PARENT;
|
|
1755
|
+
const items = o.any;
|
|
1756
|
+
if (ac && items && selectedBranchOf(ac) !== items)
|
|
1757
|
+
return items;
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
743
1760
|
const content = o.content;
|
|
744
|
-
if (Array.isArray(content) &&
|
|
1761
|
+
if (Array.isArray(content) && tn !== 'org_docx4j_wml.P')
|
|
745
1762
|
story = content;
|
|
746
1763
|
}
|
|
747
1764
|
return story !== null && story !== void 0 ? story : [];
|
|
748
1765
|
}
|
|
749
1766
|
/** Every piece of move markup in a story, in document order, each half and mark with the names of the ranges open over it. */
|
|
750
|
-
function movePiecesOf(story) {
|
|
1767
|
+
export function movePiecesOf(story) {
|
|
751
1768
|
const out = [];
|
|
752
1769
|
const open = { from: new Map(), to: new Map() };
|
|
753
1770
|
const visit = (items, runLevel = false) => {
|
|
754
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1771
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
755
1772
|
for (const el of items) {
|
|
756
1773
|
const marker = RANGE_MARKERS[(_b = (_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : ''];
|
|
757
1774
|
const value = el.value;
|
|
@@ -781,11 +1798,18 @@ function movePiecesOf(story) {
|
|
|
781
1798
|
const v = el.value;
|
|
782
1799
|
if (typeof v !== 'object' || v === null)
|
|
783
1800
|
continue;
|
|
784
|
-
|
|
1801
|
+
// a kept mc:AlternateContent at block, row or cell level: its selected branch, part of this story
|
|
1802
|
+
// (`storyOf`); among runs it is one object, as before (CR-010 phase 3)
|
|
1803
|
+
if (typeNameOf(el) === ALTERNATE_CONTENT) {
|
|
1804
|
+
if (!runLevel)
|
|
1805
|
+
visit((_e = selectedBranchOf(v)) !== null && _e !== void 0 ? _e : []);
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
const children = (_f = childrenOf(v)) !== null && _f !== void 0 ? _f : runItemsOf(v);
|
|
785
1809
|
if (children)
|
|
786
1810
|
visit(children, typeNameOf(el) === 'org_docx4j_wml.P');
|
|
787
1811
|
if (typeNameOf(el) === 'org_docx4j_wml.P') {
|
|
788
|
-
const rPr = (
|
|
1812
|
+
const rPr = (_g = v.pPr) === null || _g === void 0 ? void 0 : _g.rPr;
|
|
789
1813
|
for (const side of ['from', 'to']) {
|
|
790
1814
|
if (rPr === null || rPr === void 0 ? void 0 : rPr[side === 'from' ? 'moveFrom' : 'moveTo']) {
|
|
791
1815
|
out.push({ kind: 'mark', side, paragraph: el, container: items, names: [...open[side].values()] });
|
|
@@ -809,7 +1833,7 @@ function resolveRangelessHalf(change, action) {
|
|
|
809
1833
|
const paragraph = change.paragraph;
|
|
810
1834
|
if (t.kind !== 'run' || (t.revision !== 'moveTo' && t.revision !== 'moveFrom') || !paragraph)
|
|
811
1835
|
return false;
|
|
812
|
-
const story = storyOf(
|
|
1836
|
+
const story = storyOf(t.element.value);
|
|
813
1837
|
const pieces = movePiecesOf(story);
|
|
814
1838
|
const half = pieces.find((piece) => piece.kind === 'half' && piece.element === t.element);
|
|
815
1839
|
if (!half || half.kind !== 'half' || half.names.length > 0)
|
|
@@ -945,18 +1969,76 @@ function markBefore(paragraph) {
|
|
|
945
1969
|
}
|
|
946
1970
|
/** Between two tracked changes: something unrevised - text, a paragraph mark, a table - that keeps them apart. */
|
|
947
1971
|
export const BREAK = 'break';
|
|
1972
|
+
/** The public listing's (`Listing`). */
|
|
1973
|
+
export function listedListing() {
|
|
1974
|
+
return { mode: 'listed', extra: [] };
|
|
1975
|
+
}
|
|
1976
|
+
/** The changes in the other branches each listed change resolves too (`TrackedChange.resolveTwins`). */
|
|
1977
|
+
const twinsOf = new WeakMap();
|
|
1978
|
+
/**
|
|
1979
|
+
* A kept `mc:AlternateContent`'s changes: its selected branch's into `into`, as Word reads them, keeping
|
|
1980
|
+
* nothing apart (CR-013 phase 2); each other branch's paired with them as twins or, resolve-all's,
|
|
1981
|
+
* listed on their own after the story's (`Listing`). `visit` lists one branch's items, at the element's
|
|
1982
|
+
* level (blocks, rows, cells or runs).
|
|
1983
|
+
*/
|
|
1984
|
+
export function alternateTokens(ac, visit, into, listing) {
|
|
1985
|
+
const read = selectedBranchOf(ac);
|
|
1986
|
+
const primary = [];
|
|
1987
|
+
visit(read !== null && read !== void 0 ? read : [], primary);
|
|
1988
|
+
into.push(...primary);
|
|
1989
|
+
for (const branch of branchesOf(ac)) {
|
|
1990
|
+
if (branch === read)
|
|
1991
|
+
continue;
|
|
1992
|
+
const tokens = [];
|
|
1993
|
+
visit(branch, tokens);
|
|
1994
|
+
if (listing.mode === 'every')
|
|
1995
|
+
listing.extra.push(BREAK, ...tokens, BREAK);
|
|
1996
|
+
else
|
|
1997
|
+
pairTwins(primary, tokens);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Another branch's changes attached to the listed branch's as twins, piece by piece: the n-th single
|
|
2002
|
+
* change of a kind with the n-th of that kind, never by id, as docx4j pairs a text box's `mc:Fallback`
|
|
2003
|
+
* changes and a branch's (its `Lister.pairTwins`; Word gives twins different ids). docx4j pairs the
|
|
2004
|
+
* listed changes, groups included; pieces are paired here, since this listing groups the selected
|
|
2005
|
+
* branch's with what touches the element, so a piece resolved on its own resolves its twin too.
|
|
2006
|
+
*/
|
|
2007
|
+
export function pairTwins(primary, twins) {
|
|
2008
|
+
var _a, _b;
|
|
2009
|
+
const piecesOf = (tokens) => tokens.flatMap((token) => (token === BREAK ? [] : token.pieces));
|
|
2010
|
+
const byKind = new Map();
|
|
2011
|
+
for (const twin of piecesOf(twins)) {
|
|
2012
|
+
let list = byKind.get(twin.kind);
|
|
2013
|
+
if (!list)
|
|
2014
|
+
byKind.set(twin.kind, list = []);
|
|
2015
|
+
list.push(twin);
|
|
2016
|
+
}
|
|
2017
|
+
const seen = new Map();
|
|
2018
|
+
for (const piece of piecesOf(primary)) {
|
|
2019
|
+
const n = (_a = seen.get(piece.kind)) !== null && _a !== void 0 ? _a : 0;
|
|
2020
|
+
seen.set(piece.kind, n + 1);
|
|
2021
|
+
const twin = (_b = byKind.get(piece.kind)) === null || _b === void 0 ? void 0 : _b[n];
|
|
2022
|
+
if (!twin)
|
|
2023
|
+
continue;
|
|
2024
|
+
let list = twinsOf.get(piece);
|
|
2025
|
+
if (!list)
|
|
2026
|
+
twinsOf.set(piece, list = []);
|
|
2027
|
+
list.push(twin);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
948
2030
|
/**
|
|
949
2031
|
* A paragraph's single changes in document order, its mark last, with a `BREAK` wherever unrevised
|
|
950
2032
|
* content stands between two of them: `groupTouching` makes the groups from these.
|
|
951
2033
|
*/
|
|
952
|
-
export function trackedChangeTokensOfParagraph(paragraph) {
|
|
2034
|
+
export function trackedChangeTokensOfParagraph(paragraph, listing = listedListing()) {
|
|
953
2035
|
var _a, _b, _c, _d;
|
|
954
2036
|
const out = [];
|
|
955
2037
|
const p = paragraph.p;
|
|
956
2038
|
const pPrChange = (_a = p.pPr) === null || _a === void 0 ? void 0 : _a.pPrChange;
|
|
957
2039
|
if (pPrChange)
|
|
958
2040
|
out.push(new TrackedChange({ kind: 'paragraphProperties', value: pPrChange }, paragraph));
|
|
959
|
-
tokensOfRunLevel(paragraph, ((_b = p.content) !== null && _b !== void 0 ? _b : []), out);
|
|
2041
|
+
tokensOfRunLevel(paragraph, ((_b = p.content) !== null && _b !== void 0 ? _b : []), out, listing);
|
|
960
2042
|
const rPr = (_c = p.pPr) === null || _c === void 0 ? void 0 : _c.rPr;
|
|
961
2043
|
// a mark whose formatting changed is a piece, grouping with its runs' (check 27: "Before.\r")
|
|
962
2044
|
if (rPr === null || rPr === void 0 ? void 0 : rPr.rPrChange)
|
|
@@ -983,20 +2065,32 @@ function sectionText(items, end) {
|
|
|
983
2065
|
while (from > 0 && !(typeNameOf(items[from - 1]) === 'org_docx4j_wml.P' && ((_a = items[from - 1].value.pPr) === null || _a === void 0 ? void 0 : _a.sectPr)))
|
|
984
2066
|
from--;
|
|
985
2067
|
return items.slice(Math.max(from, 0), end).filter((el) => typeNameOf(el) === 'org_docx4j_wml.P')
|
|
986
|
-
.map((el) => textOfView(el.value, { view: 'accepted' })).join('\r');
|
|
2068
|
+
.map((el) => textOfView(el.value, { view: 'accepted', characters: 'textOf' })).join('\r');
|
|
987
2069
|
}
|
|
988
|
-
function tokensOfRunLevel(paragraph, items, out) {
|
|
2070
|
+
function tokensOfRunLevel(paragraph, items, out, listing) {
|
|
989
2071
|
var _a, _b, _c;
|
|
990
2072
|
for (let i = 0; i < items.length; i++) {
|
|
991
2073
|
const el = items[i];
|
|
992
2074
|
// an inline control or custom XML element that is itself an insertion: a piece, then what is in it
|
|
993
|
-
const inserted =
|
|
2075
|
+
const inserted = holderChangeAt(items, i, paragraph);
|
|
994
2076
|
if (inserted) {
|
|
995
2077
|
out.push(inserted);
|
|
996
|
-
tokensOfRunLevel(paragraph, (_a = childrenOf(items[i + 1].value)) !== null && _a !== void 0 ? _a : [], out);
|
|
2078
|
+
tokensOfRunLevel(paragraph, (_a = childrenOf(items[i + 1].value)) !== null && _a !== void 0 ? _a : [], out, listing);
|
|
997
2079
|
i += 2;
|
|
998
2080
|
continue;
|
|
999
2081
|
}
|
|
2082
|
+
// a field Word inserted, its separator and result plain between two insertions: one piece
|
|
2083
|
+
const field = insertedFieldAt(items, i, paragraph);
|
|
2084
|
+
if (field && field.target.kind === 'field') {
|
|
2085
|
+
out.push(field);
|
|
2086
|
+
i += field.target.span.length - 1;
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
// an equation: the revisions in its runs and on its structures (section 51)
|
|
2090
|
+
if (isMathValue(el.value)) {
|
|
2091
|
+
mathTokens(paragraph, el.value, el, items, [], out);
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
1000
2094
|
const kind = revisionKindOf(el);
|
|
1001
2095
|
if (kind !== undefined) {
|
|
1002
2096
|
// moved text is content Office JS cannot list (checks 16, 17), and is kept apart
|
|
@@ -1010,7 +2104,7 @@ function tokensOfRunLevel(paragraph, items, out) {
|
|
|
1010
2104
|
if (nested) {
|
|
1011
2105
|
// a revision inside a revision (a deletion of inserted text) was not measured: listed apart
|
|
1012
2106
|
const inner = [];
|
|
1013
|
-
tokensOfRunLevel(paragraph, nested, inner);
|
|
2107
|
+
tokensOfRunLevel(paragraph, nested, inner, listing);
|
|
1014
2108
|
if (inner.some((token) => token !== BREAK && token.target.kind === 'run'))
|
|
1015
2109
|
out.push(BREAK, ...inner, BREAK);
|
|
1016
2110
|
else
|
|
@@ -1029,6 +2123,12 @@ function tokensOfRunLevel(paragraph, items, out) {
|
|
|
1029
2123
|
out.push(BREAK);
|
|
1030
2124
|
continue;
|
|
1031
2125
|
}
|
|
2126
|
+
// a kept mc:AlternateContent among runs: its selected branch, as Word reads it, keeping nothing apart
|
|
2127
|
+
// (CR-013 phase 2; docx4j CR-051 section 10.5 item 3), the other branches' changes its twins (phase 3)
|
|
2128
|
+
if (typeNameOf(el) === ALTERNATE_CONTENT) {
|
|
2129
|
+
alternateTokens(el.value, (branch, into) => tokensOfRunLevel(paragraph, branch, into, listing), out, listing);
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
1032
2132
|
// a run holder that is not itself an insertion - a hyperlink, a field, a control or custom XML
|
|
1033
2133
|
// element that was there - keeps the insertions inside it apart from those outside: Office JS
|
|
1034
2134
|
// listed "before ", "link" and " after" as three changes over a w:ins in a w:hyperlink, and the same
|
|
@@ -1037,7 +2137,7 @@ function tokensOfRunLevel(paragraph, items, out) {
|
|
|
1037
2137
|
const nested = runItemsOf(el.value);
|
|
1038
2138
|
if (nested) {
|
|
1039
2139
|
out.push(BREAK);
|
|
1040
|
-
tokensOfRunLevel(paragraph, nested, out);
|
|
2140
|
+
tokensOfRunLevel(paragraph, nested, out, listing);
|
|
1041
2141
|
out.push(BREAK);
|
|
1042
2142
|
}
|
|
1043
2143
|
}
|
|
@@ -1056,7 +2156,11 @@ function groupable(change) {
|
|
|
1056
2156
|
if (t.kind === 'runProperties' || t.kind === 'markProperties')
|
|
1057
2157
|
return 'format';
|
|
1058
2158
|
if (t.kind === 'holder')
|
|
2159
|
+
return t.revision;
|
|
2160
|
+
if (t.kind === 'field')
|
|
1059
2161
|
return 'ins';
|
|
2162
|
+
if (t.kind === 'mathRun' || t.kind === 'mathStructure')
|
|
2163
|
+
return t.revision;
|
|
1060
2164
|
// an inserted or deleted row groups with rows and text touching it: Office JS lists an inserted
|
|
1061
2165
|
// table of three rows, with the inserted paragraphs before and after it, as one change, and rows
|
|
1062
2166
|
// added to a table that was there as one change per touching run of them (check 31)
|
|
@@ -1099,61 +2203,69 @@ export function groupTouching(tokens) {
|
|
|
1099
2203
|
const first = item[0];
|
|
1100
2204
|
if (item.length === 1)
|
|
1101
2205
|
return first;
|
|
1102
|
-
return new TrackedChange({ kind: 'group', revision: groupable(first), pieces: item, value: first.target.value }, first.paragraph);
|
|
2206
|
+
return new TrackedChange({ kind: 'group', revision: groupable(first), pieces: item, value: first.target.value }, first.paragraph, first.body);
|
|
1103
2207
|
});
|
|
1104
2208
|
}
|
|
1105
2209
|
/**
|
|
1106
2210
|
* The `w:trPr/w:ins` and `w:trPr/w:del` revisions of a table row. `inner` is the revision markup
|
|
1107
2211
|
* of the row's own cells, which the row's change owns rather than reporting separately.
|
|
1108
2212
|
*/
|
|
1109
|
-
export function trackedChangesOfRow(tr, owner, inner = [], table) {
|
|
2213
|
+
export function trackedChangesOfRow(tr, owner, inner = [], table, body) {
|
|
1110
2214
|
const trPr = tr.value.trPr;
|
|
1111
2215
|
const out = [];
|
|
1112
2216
|
if (trPr === null || trPr === void 0 ? void 0 : trPr.ins)
|
|
1113
|
-
out.push(new TrackedChange({ kind: 'row', row: 'ins', tr, owner, value: trPr.ins, inner, table }, undefined));
|
|
2217
|
+
out.push(new TrackedChange({ kind: 'row', row: 'ins', tr, owner, value: trPr.ins, inner, table }, undefined, body));
|
|
1114
2218
|
if (trPr === null || trPr === void 0 ? void 0 : trPr.del)
|
|
1115
|
-
out.push(new TrackedChange({ kind: 'row', row: 'del', tr, owner, value: trPr.del, inner, table }, undefined));
|
|
2219
|
+
out.push(new TrackedChange({ kind: 'row', row: 'del', tr, owner, value: trPr.del, inner, table }, undefined, body));
|
|
1116
2220
|
return out;
|
|
1117
2221
|
}
|
|
1118
2222
|
// --- CR-002 section 35: tables, cells, marks and sections --------------------------------------------
|
|
1119
2223
|
/** The change a table's property records make, one per table, or undefined when they make none. */
|
|
1120
|
-
export function tablePropertiesChangeOf(tbl) {
|
|
2224
|
+
export function tablePropertiesChangeOf(tbl, body) {
|
|
1121
2225
|
const records = tableRecordsOf(tbl.value);
|
|
1122
|
-
return records ? new TrackedChange({ kind: 'tableProperties', tbl, value: records.value }, undefined) : undefined;
|
|
2226
|
+
return records ? new TrackedChange({ kind: 'tableProperties', tbl, value: records.value }, undefined, body) : undefined;
|
|
1123
2227
|
}
|
|
1124
2228
|
/** A cell's own revision, `w:cellIns` or `w:cellDel`, or undefined. */
|
|
1125
|
-
export function cellChangeOf(tc, owner, tr) {
|
|
2229
|
+
export function cellChangeOf(tc, owner, tr, body) {
|
|
1126
2230
|
const tcPr = tc.value.tcPr;
|
|
1127
2231
|
if (tcPr === null || tcPr === void 0 ? void 0 : tcPr.cellIns)
|
|
1128
|
-
return new TrackedChange({ kind: 'cell', cell: 'ins', tc, owner, tr, value: tcPr.cellIns }, undefined);
|
|
2232
|
+
return new TrackedChange({ kind: 'cell', cell: 'ins', tc, owner, tr, value: tcPr.cellIns }, undefined, body);
|
|
1129
2233
|
if (tcPr === null || tcPr === void 0 ? void 0 : tcPr.cellDel)
|
|
1130
|
-
return new TrackedChange({ kind: 'cell', cell: 'del', tc, owner, tr, value: tcPr.cellDel }, undefined);
|
|
2234
|
+
return new TrackedChange({ kind: 'cell', cell: 'del', tc, owner, tr, value: tcPr.cellDel }, undefined, body);
|
|
1131
2235
|
return undefined;
|
|
1132
2236
|
}
|
|
1133
2237
|
/** The change to the body's last section (`w:body/w:sectPr/w:sectPrChange`), its text the section's paragraphs'. */
|
|
1134
|
-
export function bodySectionChangeOf(container) {
|
|
2238
|
+
export function bodySectionChangeOf(container, body) {
|
|
1135
2239
|
var _a;
|
|
1136
2240
|
const sectPr = container.sectPr;
|
|
1137
2241
|
if (!(sectPr === null || sectPr === void 0 ? void 0 : sectPr.sectPrChange))
|
|
1138
2242
|
return undefined;
|
|
1139
2243
|
const items = (_a = container.content) !== null && _a !== void 0 ? _a : [];
|
|
1140
|
-
return new TrackedChange({ kind: 'sectionProperties', sectPr, value: sectPr.sectPrChange, text: sectionText(items, items.length) }, undefined);
|
|
2244
|
+
return new TrackedChange({ kind: 'sectionProperties', sectPr, value: sectPr.sectPrChange, text: sectionText(items, items.length) }, undefined, body);
|
|
1141
2245
|
}
|
|
1142
2246
|
/**
|
|
1143
2247
|
* A cell taken out of its row, the cell before it (else after it) widened over its grid columns, as
|
|
1144
2248
|
* Word did rejecting an inserted cell and accepting a deleted one (check 27: `w:gridSpan` 2 and the
|
|
1145
|
-
* two widths added).
|
|
2249
|
+
* two widths added). The neighbour is the row's as a reader sees it, through cell-level controls and a
|
|
2250
|
+
* kept `mc:AlternateContent`'s selected branch; a cell in another branch (a twin) has its own branch's
|
|
2251
|
+
* cells for neighbours, and one alone there widens none, the row's neighbour having been widened once
|
|
2252
|
+
* when the selected branch's cell went, every branch spanning the same columns (CR-013 phase 3, docx4j's
|
|
2253
|
+
* `Tables.removeCell`, its 60bd4b9ad and 901a922a3).
|
|
1146
2254
|
*/
|
|
1147
|
-
function removeCell(tc, owner) {
|
|
2255
|
+
function removeCell(tc, owner, tr) {
|
|
1148
2256
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1149
2257
|
var _h;
|
|
1150
|
-
const
|
|
2258
|
+
const list = currentOwner(owner, tc);
|
|
2259
|
+
const i = list.indexOf(tc);
|
|
1151
2260
|
if (i < 0)
|
|
1152
2261
|
return;
|
|
1153
|
-
|
|
2262
|
+
let cells = cellsOf(tr, 'read').map((cell) => cell.element);
|
|
2263
|
+
if (!cells.includes(tc))
|
|
2264
|
+
cells = list.filter((el) => typeNameOf(el) === 'org_docx4j_wml.Tc');
|
|
1154
2265
|
const at = cells.indexOf(tc);
|
|
1155
2266
|
const neighbour = (_a = cells[at - 1]) !== null && _a !== void 0 ? _a : cells[at + 1];
|
|
1156
|
-
|
|
2267
|
+
discarding(tc);
|
|
2268
|
+
list.splice(i, 1);
|
|
1157
2269
|
if (!neighbour)
|
|
1158
2270
|
return;
|
|
1159
2271
|
const removed = tc.value.tcPr;
|
|
@@ -1209,7 +2321,8 @@ function restoreSection(sectPr, change) {
|
|
|
1209
2321
|
* writes and drops when one is put in, and whose record is the old number's text, so that a reject
|
|
1210
2322
|
* too can only drop it; and a table's records that change nothing. A merge rejected gets the merge it
|
|
1211
2323
|
* recorded back (docx4j's reading; unmeasured). A `w:customXmlInsRangeStart` or `End` left without
|
|
1212
|
-
* the form `
|
|
2324
|
+
* the form `holderChangeAt` lists marks nothing that can be found, and goes either way. Every branch
|
|
2325
|
+
* of a kept `mc:AlternateContent` at block, row and cell level is swept (CR-013 phase 3).
|
|
1213
2326
|
*/
|
|
1214
2327
|
export function resolveUnlisted(items, what) {
|
|
1215
2328
|
const visit = (list) => {
|
|
@@ -1218,7 +2331,7 @@ export function resolveUnlisted(items, what) {
|
|
|
1218
2331
|
return;
|
|
1219
2332
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
1220
2333
|
const name = (_a = list[i].name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
1221
|
-
if (name
|
|
2334
|
+
if (HOLDER_MARKERS.has(name !== null && name !== void 0 ? name : ''))
|
|
1222
2335
|
list.splice(i, 1);
|
|
1223
2336
|
}
|
|
1224
2337
|
for (const el of list) {
|
|
@@ -1234,8 +2347,8 @@ export function resolveUnlisted(items, what) {
|
|
|
1234
2347
|
const tbl = el.value;
|
|
1235
2348
|
if (hasTableRecords(tbl) && !tableRecordsOf(tbl))
|
|
1236
2349
|
acceptTable(tbl);
|
|
1237
|
-
for (const row of rowsOf(tbl)) {
|
|
1238
|
-
for (const cell of cellsOf(row.element.value)) {
|
|
2350
|
+
for (const row of rowsOf(tbl, 'every')) {
|
|
2351
|
+
for (const cell of cellsOf(row.element.value, 'every')) {
|
|
1239
2352
|
const tcPr = cell.element.value.tcPr;
|
|
1240
2353
|
if (tcPr === null || tcPr === void 0 ? void 0 : tcPr.cellMerge)
|
|
1241
2354
|
resolveCellMerge(tcPr, what);
|
|
@@ -1245,25 +2358,38 @@ export function resolveUnlisted(items, what) {
|
|
|
1245
2358
|
continue;
|
|
1246
2359
|
}
|
|
1247
2360
|
const v = el.value;
|
|
1248
|
-
if (typeof v
|
|
2361
|
+
if (typeof v !== 'object' || v === null)
|
|
2362
|
+
continue;
|
|
2363
|
+
// every branch of a kept mc:AlternateContent (CR-013 phase 3)
|
|
2364
|
+
if (tn === ALTERNATE_CONTENT)
|
|
2365
|
+
for (const branch of branchesOf(v))
|
|
2366
|
+
visit(branch);
|
|
2367
|
+
else
|
|
1249
2368
|
visit(childrenOf(v));
|
|
1250
2369
|
}
|
|
1251
2370
|
};
|
|
1252
2371
|
visit(items);
|
|
1253
2372
|
}
|
|
1254
|
-
/**
|
|
2373
|
+
/** A holder's insertion and deletion markers: one left without the form `holderChangeAt` lists goes either way. */
|
|
2374
|
+
const HOLDER_MARKERS = new Set(['customXmlInsRangeStart', 'customXmlInsRangeEnd', 'customXmlDelRangeStart', 'customXmlDelRangeEnd']);
|
|
2375
|
+
/** Stray `w:customXmlInsRangeStart` / `End` markers (or `Del`) taken out of run-level content, through its holders and revisions. */
|
|
1255
2376
|
function sweepMarkers(items) {
|
|
1256
2377
|
var _a;
|
|
1257
2378
|
if (!items)
|
|
1258
2379
|
return;
|
|
1259
2380
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
1260
2381
|
const name = (_a = items[i].name) === null || _a === void 0 ? void 0 : _a.localPart;
|
|
1261
|
-
if (name
|
|
2382
|
+
if (HOLDER_MARKERS.has(name !== null && name !== void 0 ? name : '')) {
|
|
1262
2383
|
items.splice(i, 1);
|
|
1263
2384
|
continue;
|
|
1264
2385
|
}
|
|
1265
2386
|
const v = items[i].value;
|
|
1266
|
-
if (typeof v
|
|
2387
|
+
if (typeof v !== 'object' || v === null)
|
|
2388
|
+
continue;
|
|
2389
|
+
if (typeNameOf(items[i]) === ALTERNATE_CONTENT)
|
|
2390
|
+
for (const branch of branchesOf(v))
|
|
2391
|
+
sweepMarkers(branch);
|
|
2392
|
+
else
|
|
1267
2393
|
sweepMarkers(runItemsOf(v));
|
|
1268
2394
|
}
|
|
1269
2395
|
}
|