@docx4j/core-ts 0.5.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.
Files changed (155) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +32 -4
  3. package/dist/html/convert.mjs +2 -1
  4. package/dist/html/elements.mjs +2 -1
  5. package/dist/model/color/ColorMath.d.mts +29 -0
  6. package/dist/model/color/ColorMath.mjs +146 -0
  7. package/dist/model/color/ColorTables.d.mts +6 -0
  8. package/dist/model/color/ColorTables.mjs +83 -0
  9. package/dist/model/color/DrawingMLColors.d.mts +52 -0
  10. package/dist/model/color/DrawingMLColors.mjs +374 -0
  11. package/dist/model/color/ResolvedColor.d.mts +36 -0
  12. package/dist/model/color/ResolvedColor.mjs +65 -0
  13. package/dist/model/color/SpreadsheetColors.d.mts +36 -0
  14. package/dist/model/color/SpreadsheetColors.mjs +84 -0
  15. package/dist/model/color/StyleFill.d.mts +28 -0
  16. package/dist/model/color/StyleFill.mjs +21 -0
  17. package/dist/model/color/ThemeColors.d.mts +86 -0
  18. package/dist/model/color/ThemeColors.mjs +304 -0
  19. package/dist/model/color/WordColors.d.mts +71 -0
  20. package/dist/model/color/WordColors.mjs +121 -0
  21. package/dist/model/color/index.d.mts +6 -0
  22. package/dist/model/color/index.mjs +30 -0
  23. package/dist/model/content/Body.d.mts +145 -23
  24. package/dist/model/content/Body.mjs +509 -141
  25. package/dist/model/content/Comment.d.mts +21 -3
  26. package/dist/model/content/Comment.mjs +195 -19
  27. package/dist/model/content/ContentControl.d.mts +197 -18
  28. package/dist/model/content/ContentControl.mjs +664 -95
  29. package/dist/model/content/Font.d.mts +67 -17
  30. package/dist/model/content/Font.mjs +114 -35
  31. package/dist/model/content/InlinePicture.d.mts +11 -0
  32. package/dist/model/content/InlinePicture.mjs +21 -1
  33. package/dist/model/content/List.mjs +3 -0
  34. package/dist/model/content/Paragraph.d.mts +189 -13
  35. package/dist/model/content/Paragraph.mjs +870 -74
  36. package/dist/model/content/Range.d.mts +227 -12
  37. package/dist/model/content/Range.mjs +785 -59
  38. package/dist/model/content/ResolvedView.d.mts +104 -0
  39. package/dist/model/content/ResolvedView.mjs +596 -0
  40. package/dist/model/content/Table.d.mts +29 -3
  41. package/dist/model/content/Table.mjs +109 -33
  42. package/dist/model/content/TrackedChange.d.mts +172 -13
  43. package/dist/model/content/TrackedChange.mjs +728 -167
  44. package/dist/model/content/comments.d.mts +20 -6
  45. package/dist/model/content/comments.mjs +67 -57
  46. package/dist/model/content/generalException.d.mts +18 -0
  47. package/dist/model/content/generalException.mjs +24 -0
  48. package/dist/model/content/index.d.mts +8 -3
  49. package/dist/model/content/index.mjs +7 -2
  50. package/dist/model/content/mathText.d.mts +21 -0
  51. package/dist/model/content/mathText.mjs +267 -0
  52. package/dist/model/content/officeText.d.mts +27 -0
  53. package/dist/model/content/officeText.mjs +74 -0
  54. package/dist/model/content/outline.d.mts +5 -0
  55. package/dist/model/content/outline.mjs +13 -0
  56. package/dist/model/content/placeholders.d.mts +110 -0
  57. package/dist/model/content/placeholders.mjs +253 -0
  58. package/dist/model/content/search.d.mts +72 -0
  59. package/dist/model/content/search.mjs +204 -2
  60. package/dist/model/content/spanEdits.d.mts +159 -0
  61. package/dist/model/content/spanEdits.mjs +728 -0
  62. package/dist/model/content/story.d.mts +44 -0
  63. package/dist/model/content/story.mjs +92 -0
  64. package/dist/model/content/tableColumns.d.mts +1 -1
  65. package/dist/model/content/tableColumns.mjs +108 -80
  66. package/dist/model/content/tableRevisions.d.mts +11 -3
  67. package/dist/model/content/tableRevisions.mjs +72 -42
  68. package/dist/model/content/tableRules.d.mts +30 -0
  69. package/dist/model/content/tableRules.mjs +100 -0
  70. package/dist/model/content/textBoxes.d.mts +18 -0
  71. package/dist/model/content/textBoxes.mjs +105 -0
  72. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  73. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  74. package/dist/model/content/tracking.d.mts +28 -0
  75. package/dist/model/content/tracking.mjs +149 -10
  76. package/dist/model/content/tree.d.mts +171 -22
  77. package/dist/model/content/tree.mjs +329 -53
  78. package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
  79. package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
  80. package/dist/model/customxml/XmlMapping.d.mts +1 -1
  81. package/dist/model/customxml/XmlMapping.mjs +11 -0
  82. package/dist/model/customxml/bindings.d.mts +21 -2
  83. package/dist/model/customxml/bindings.mjs +76 -32
  84. package/dist/model/customxml/kinds.d.mts +8 -3
  85. package/dist/model/customxml/kinds.mjs +26 -5
  86. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  87. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  88. package/dist/model/customxml/pictures.d.mts +4 -3
  89. package/dist/model/customxml/pictures.mjs +18 -6
  90. package/dist/model/customxml/xhtml.mjs +8 -2
  91. package/dist/model/index.d.mts +2 -0
  92. package/dist/model/index.mjs +4 -0
  93. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  94. package/dist/model/listnumbering/Emulator.mjs +63 -9
  95. package/dist/model/properties/PropertyResolver.d.mts +60 -1
  96. package/dist/model/properties/PropertyResolver.mjs +115 -2
  97. package/dist/model/properties/catalogue.d.mts +19 -5
  98. package/dist/model/properties/catalogue.mjs +41 -15
  99. package/dist/model/properties/log.d.mts +1 -1
  100. package/dist/model/properties/log.mjs +5 -5
  101. package/dist/model/properties/table.mjs +4 -2
  102. package/dist/model/text/Extractor.d.mts +289 -0
  103. package/dist/model/text/Extractor.mjs +2162 -0
  104. package/dist/model/text/LinkTargets.d.mts +45 -0
  105. package/dist/model/text/LinkTargets.mjs +170 -0
  106. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  107. package/dist/model/text/MarkdownExporter.mjs +255 -0
  108. package/dist/model/text/PlainText.d.mts +29 -0
  109. package/dist/model/text/PlainText.mjs +539 -0
  110. package/dist/model/text/TextContext.d.mts +161 -0
  111. package/dist/model/text/TextContext.mjs +425 -0
  112. package/dist/model/text/TextOptions.d.mts +150 -0
  113. package/dist/model/text/TextOptions.mjs +278 -0
  114. package/dist/model/text/TextResult.d.mts +504 -0
  115. package/dist/model/text/TextResult.mjs +526 -0
  116. package/dist/model/text/Texts.d.mts +99 -0
  117. package/dist/model/text/Texts.mjs +187 -0
  118. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  119. package/dist/model/text/WmlToMarkdown.mjs +911 -0
  120. package/dist/model/text/commonmark.d.mts +121 -0
  121. package/dist/model/text/commonmark.mjs +655 -0
  122. package/dist/model/text/index.d.mts +7 -0
  123. package/dist/model/text/index.mjs +8 -0
  124. package/dist/model/text/normalize.d.mts +8 -0
  125. package/dist/model/text/normalize.mjs +13 -0
  126. package/dist/model/text/outline.d.mts +4 -0
  127. package/dist/model/text/outline.mjs +71 -0
  128. package/dist/office-js/fragment.d.mts +7 -5
  129. package/dist/office-js/fragment.mjs +63 -14
  130. package/dist/office-js/proxy.d.mts +8 -0
  131. package/dist/office-js/proxy.mjs +29 -0
  132. package/dist/office-js/run.mjs +2 -0
  133. package/dist/office-js/supported.generated.mjs +16 -0
  134. package/dist/office-js/toApiScript.mjs +21 -5
  135. package/dist/opc/Save.d.mts +17 -0
  136. package/dist/opc/Save.mjs +12 -2
  137. package/dist/opc/index.d.mts +3 -3
  138. package/dist/opc/index.mjs +1 -1
  139. package/dist/opc/mce/preprocessor.d.mts +28 -3
  140. package/dist/opc/mce/preprocessor.mjs +4 -4
  141. package/dist/opc/mce/understood.d.mts +30 -6
  142. package/dist/opc/mce/understood.mjs +37 -7
  143. package/dist/packages/OpcPackage.d.mts +13 -0
  144. package/dist/packages/OpcPackage.mjs +1 -0
  145. package/dist/packages/WordprocessingMLPackage.d.mts +45 -8
  146. package/dist/packages/WordprocessingMLPackage.mjs +155 -20
  147. package/dist/parts/DefaultXmlPart.mjs +2 -2
  148. package/dist/parts/Part.d.mts +10 -2
  149. package/dist/parts/XmlPart.d.mts +12 -30
  150. package/dist/parts/XmlPart.mjs +44 -93
  151. package/dist/parts/wml/comments.mjs +30 -0
  152. package/dist/parts/wml/index.mjs +12 -0
  153. package/dist/xml/dom.d.mts +7 -0
  154. package/dist/xml/dom.mjs +18 -0
  155. package/package.json +2 -2
@@ -1,8 +1,9 @@
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';
@@ -10,8 +11,9 @@ import { deepCopy } from '@docx4j/generated-objects-ts';
10
11
  import { relationshipIdsIn, dropRelationshipsUnused, queueRelationshipRemovals } from './relationships.mjs';
11
12
  /**
12
13
  * A subset of Office JS `Word.TrackedChange`: `type`, `author`, `date`, `text`, `accept()`,
13
- * `reject()` and `getRange()`, plus `element`, `id` and `target` as extensions. A view, like
14
- * `Paragraph` and `Range`: it holds the markup it is over, nothing is cached.
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.
15
17
  */
16
18
  export class TrackedChange {
17
19
  constructor(target,
@@ -32,7 +34,7 @@ export class TrackedChange {
32
34
  case 'row': return t.row === 'ins' ? 'Added' : 'Deleted';
33
35
  case 'group': return t.revision === 'ins' ? 'Added' : t.revision === 'del' ? 'Deleted' : 'Formatted';
34
36
  case 'cell': return t.cell === 'ins' ? 'Added' : 'Deleted';
35
- case 'holder': return 'Added';
37
+ case 'holder': return t.revision === 'ins' ? 'Added' : 'Deleted';
36
38
  case 'field': return 'Added';
37
39
  case 'mathRun':
38
40
  case 'mathStructure': return t.revision === 'ins' ? 'Added' : 'Deleted';
@@ -54,6 +56,52 @@ export class TrackedChange {
54
56
  get id() {
55
57
  return this.target.value.id;
56
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
+ }
57
105
  /** The markup this change is over (an extension): the `w:ins` element, the `w:rPrChange`, ... */
58
106
  get element() {
59
107
  var _a;
@@ -83,10 +131,10 @@ export class TrackedChange {
83
131
  case 'tableProperties': return tableChangeText(t.tbl.value);
84
132
  case 'cell': return tableRowText(t.tr);
85
133
  case 'sectionProperties': return t.text;
86
- 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('');
87
135
  case 'runProperties': return textOf(t.run);
88
136
  // the field's result: its instruction is no text, as Office JS reports it (check 30: "1")
89
- case 'field': return segmentsOf({ content: t.span }, { view: 'accepted' }).map((s) => s.text).join('');
137
+ case 'field': return segmentsOf({ content: t.span }, { view: 'accepted', characters: 'textOf' }).map((s) => s.text).join('');
90
138
  case 'paragraphProperties': return (_b = (_a = this.paragraph) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
91
139
  // a deleted row's content is `w:delText` inside `w:del`, which the accepted view skips:
92
140
  // its text is what the row said before, as for a run deletion
@@ -98,6 +146,7 @@ export class TrackedChange {
98
146
  }
99
147
  /** The range the change covers; a deletion is a collapsed range where it sits in the accepted text. */
100
148
  getRange() {
149
+ var _a;
101
150
  const p = this.paragraph;
102
151
  if (!p)
103
152
  return undefined;
@@ -107,15 +156,55 @@ export class TrackedChange {
107
156
  return new Range(p, start, end);
108
157
  }
109
158
  if (t.kind === 'group') {
110
- // a range is one paragraph's: the group's text in the paragraph it starts in
111
- const here = t.pieces.filter((piece) => { var _a; return (piece.target.kind === 'run' || piece.target.kind === 'runProperties' || piece.target.kind === 'field') && ((_a = piece.paragraph) === null || _a === void 0 ? void 0 : _a.element) === p.element; }).map((piece) => piece.getRange());
112
- if (here.length === 0)
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)
113
169
  return t.pieces[0].getRange();
114
- return new Range(p, Math.min(...here.map((r) => r.start)), Math.max(...here.map((r) => r.end)));
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');
115
203
  }
116
204
  if (t.kind === 'field') {
117
- const start = acceptedOffsetOf(p.p, t.span[0]);
118
- return new Range(p, start, start + this.text.length);
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);
119
208
  }
120
209
  if (t.kind === 'runProperties') {
121
210
  const segs = p.segments().filter((s) => s.run === t.run);
@@ -123,9 +212,13 @@ export class TrackedChange {
123
212
  return p.getRange('Start');
124
213
  return new Range(p, segs[0].start, segs[segs.length - 1].end);
125
214
  }
126
- // an equation's text is not in the paragraph's: collapsed where the equation stands
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
127
217
  if ((t.kind === 'mathRun' || t.kind === 'mathStructure') && t.equation) {
128
- const at = acceptedOffsetOf(p.p, 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);
129
222
  return new Range(p, at, at);
130
223
  }
131
224
  return p.getRange('Whole');
@@ -133,9 +226,27 @@ export class TrackedChange {
133
226
  /**
134
227
  * Keeps the change: a `w:ins` is unwrapped, a `w:del` removed, a deleted mark joins the paragraphs.
135
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.
136
232
  */
137
233
  accept() {
138
- resolving(this.package_, this.body_, () => this.acceptHere());
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]();
139
250
  }
140
251
  acceptHere() {
141
252
  var _a, _b, _c, _d, _e, _f;
@@ -186,7 +297,7 @@ export class TrackedChange {
186
297
  if (t.cell === 'ins')
187
298
  (_d = t.tc.value.tcPr) === null || _d === void 0 ? true : delete _d.cellIns;
188
299
  else
189
- removeCell(t.tc, t.owner);
300
+ removeCell(t.tc, t.owner, t.tr);
190
301
  return;
191
302
  case 'markProperties': {
192
303
  const p = this.requireParagraph().p;
@@ -198,8 +309,12 @@ export class TrackedChange {
198
309
  delete t.sectPr.sectPrChange;
199
310
  return;
200
311
  case 'holder':
201
- // the element stays, the markers go (check 30: accept all leaves the control, no marker)
202
- removeHolderMarkers(t);
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);
203
318
  return;
204
319
  case 'field':
205
320
  for (const el of t.span) {
@@ -218,9 +333,59 @@ export class TrackedChange {
218
333
  /**
219
334
  * Puts back what was there: a `w:ins` is removed, a `w:del` restored, a `w:rPrChange` re-applied.
220
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.
221
338
  */
222
339
  reject() {
223
- resolving(this.package_, this.body_, () => this.rejectHere());
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
+ }
224
389
  }
225
390
  rejectHere() {
226
391
  var _a, _b;
@@ -271,7 +436,7 @@ export class TrackedChange {
271
436
  if (t.cell === 'del')
272
437
  (_b = t.tc.value.tcPr) === null || _b === void 0 ? true : delete _b.cellDel;
273
438
  else
274
- removeCell(t.tc, t.owner);
439
+ removeCell(t.tc, t.owner, t.tr);
275
440
  return;
276
441
  case 'markProperties':
277
442
  restoreMarkProperties(this.requireParagraph().p, t.value);
@@ -280,10 +445,13 @@ export class TrackedChange {
280
445
  restoreSection(t.sectPr, t.value);
281
446
  return;
282
447
  case 'holder':
283
- // the element goes, its content stays where it stood: what is left of it after the pieces
284
- // inside were rejected (nothing, for content inserted with it; check 30: reject all removes the
285
- // control, and an existing control, which has no markers, stays)
286
- unwrapHolder(t);
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);
287
455
  return;
288
456
  case 'field':
289
457
  removeField(t, this.paragraph);
@@ -329,19 +497,20 @@ function rowText(tr, view) {
329
497
  for (const el of (_a = childrenOf(tc)) !== null && _a !== void 0 ? _a : []) {
330
498
  const tn = typeNameOf(el);
331
499
  if (tn === 'org_docx4j_wml.P') {
332
- out += (afterParagraph ? '\r' : '') + textOfView(el.value, { view });
500
+ out += (afterParagraph ? '\r' : '') + textOfView(el.value, { view, characters: 'textOf' });
333
501
  afterParagraph = true;
334
502
  }
335
503
  else if (tn === 'org_docx4j_wml.Tbl') {
336
- for (const row of rowsOf(el.value))
504
+ for (const row of rowsOf(el.value, 'read'))
337
505
  out += rowText(row.element.value, view);
338
506
  afterParagraph = false;
339
507
  }
340
508
  else if (typeof el.value === 'object' && el.value !== null) {
341
- // a block control or custom XML element: what it holds, as if it were not there
342
- for (const inner of (_b = childrenOf(el.value)) !== null && _b !== void 0 ? _b : []) {
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 : []) {
343
512
  if (typeNameOf(inner) === 'org_docx4j_wml.P') {
344
- out += (afterParagraph ? '\r' : '') + textOfView(inner.value, { view });
513
+ out += (afterParagraph ? '\r' : '') + textOfView(inner.value, { view, characters: 'textOf' });
345
514
  afterParagraph = true;
346
515
  }
347
516
  }
@@ -349,7 +518,7 @@ function rowText(tr, view) {
349
518
  }
350
519
  return out;
351
520
  };
352
- 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';
353
522
  }
354
523
  /** The changes inside a row, last first, so that a paragraph join never disturbs one still to do. */
355
524
  function applyInner(inner, what) {
@@ -412,7 +581,7 @@ function indentWithin(ind, level) {
412
581
  * never strands a piece still to do. Office JS and Word's Review tab resolve the group as one (checks
413
582
  * 22 and 23), and so, piece by piece, does this.
414
583
  */
415
- function resolvePieces(pieces, what) {
584
+ export function resolvePieces(pieces, what) {
416
585
  const runs = pieces.filter((piece) => piece.target.kind !== 'mark');
417
586
  const marks = pieces.filter((piece) => piece.target.kind === 'mark');
418
587
  for (let i = runs.length - 1; i >= 0; i--)
@@ -421,14 +590,13 @@ function resolvePieces(pieces, what) {
421
590
  marks[i][what]();
422
591
  }
423
592
  /**
424
- * The list an element is in now: `owner` as the change was listed, else the list its `PARENT` keeps -
425
- * a piece listed inside a holder that was unwrapped since (its content moved out into the owner,
426
- * check 30) is still found.
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).
427
597
  */
428
598
  function currentOwner(owner, element) {
429
599
  var _a, _b;
430
- if (owner.includes(element))
431
- return owner;
432
600
  const parent = (_a = element.value) === null || _a === void 0 ? void 0 : _a.PARENT;
433
601
  const actual = parent ? (_b = runItemsOf(parent)) !== null && _b !== void 0 ? _b : childrenOf(parent) : undefined;
434
602
  return (actual === null || actual === void 0 ? void 0 : actual.includes(element)) ? actual : owner;
@@ -462,17 +630,21 @@ let resolution;
462
630
  * goes when nothing left in the part references it; its part goes when no relationship in the package
463
631
  * targets it any more, and what that part alone related to goes with it. The kinds beyond pictures,
464
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).
465
636
  * A change resolved inside another's (a row's cells, a group's pieces, `acceptAll`'s changes) shares the
466
637
  * outer resolution, so the bookmarks are read twice for the lot, and not at all when nothing is removed.
467
638
  */
468
639
  export function resolving(pkg, body, resolve) {
469
- var _a, _b, _c;
640
+ var _a, _b, _c, _d, _e, _f;
470
641
  const notes = pkg === null || pkg === void 0 ? void 0 : pkg.pendingNoteRemovals;
471
642
  const root = storyRootOf(body);
472
643
  const outer = resolution;
473
644
  if (outer) {
474
645
  (_a = outer.root) !== null && _a !== void 0 ? _a : (outer.root = root);
475
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);
476
648
  const previous = outer.notes;
477
649
  outer.notes = notes !== null && notes !== void 0 ? notes : previous;
478
650
  try {
@@ -484,12 +656,18 @@ export function resolving(pkg, body, resolve) {
484
656
  return;
485
657
  }
486
658
  resolution = { notes, root, part: body === null || body === void 0 ? void 0 : body.part };
659
+ if (body)
660
+ resolution.body = body;
487
661
  try {
488
662
  resolve();
489
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);
490
668
  if (done.root && done.before)
491
669
  dropBookmarksBroken(done.root, done.before);
492
- if (done.part && ((_c = done.relationships) === null || _c === void 0 ? void 0 : _c.size))
670
+ if (done.part && ((_f = done.relationships) === null || _f === void 0 ? void 0 : _f.size))
493
671
  dropRelationshipsUnused(done.part, done.relationships);
494
672
  }
495
673
  finally {
@@ -509,15 +687,17 @@ function storyRootOf(body) {
509
687
  * whose `w:sectPr` references its section's headers and footers.
510
688
  */
511
689
  function discarding(element, properties) {
512
- var _a, _b;
690
+ var _a, _b, _c;
513
691
  if (!resolution)
514
692
  return;
515
693
  if (resolution.root)
516
694
  (_a = resolution.before) !== null && _a !== void 0 ? _a : (resolution.before = bookmarksIn(resolution.root));
517
695
  if (element && resolution.notes)
518
696
  noteReferencesIn(element, resolution.notes);
697
+ if (element)
698
+ commentAnchorsIn(element, ((_b = resolution.commentCandidates) !== null && _b !== void 0 ? _b : (resolution.commentCandidates = new Set())));
519
699
  if (resolution.part && (element || properties)) {
520
- const into = ((_b = resolution.relationships) !== null && _b !== void 0 ? _b : (resolution.relationships = new Set()));
700
+ const into = ((_c = resolution.relationships) !== null && _c !== void 0 ? _c : (resolution.relationships = new Set()));
521
701
  if (element)
522
702
  relationshipIdsIn(element, into);
523
703
  if (properties)
@@ -632,6 +812,17 @@ function dropBookmarksBroken(root, before) {
632
812
  };
633
813
  visit(root);
634
814
  }
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
+ }
635
826
  /** Adds the ids of the `w:footnoteReference` and `w:endnoteReference` elements anywhere in a tree to `into`. */
636
827
  function noteReferencesIn(tree, into) {
637
828
  const visit = (o) => {
@@ -656,6 +847,20 @@ function noteReferencesIn(tree, into) {
656
847
  };
657
848
  visit(tree);
658
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
+ }
659
864
  /**
660
865
  * The notes `pending` holds taken out of the notes parts, each unless a reference to it still stands in
661
866
  * the main document (an accepted move keeps the reference its source had, say); then `pending` is
@@ -696,7 +901,7 @@ export async function flushPendingNoteRemovals(main, pending, host) {
696
901
  pending.endnote.clear();
697
902
  }
698
903
  /** Adds the ids of the `w:commentReference` elements anywhere in a tree to `into`. */
699
- function commentReferencesIn(tree, into) {
904
+ export function commentReferencesIn(tree, into) {
700
905
  const visit = (o) => {
701
906
  var _a, _b;
702
907
  if (typeof o !== 'object' || o === null)
@@ -718,7 +923,12 @@ function commentReferencesIn(tree, into) {
718
923
  };
719
924
  visit(tree);
720
925
  }
721
- /** A holder's four insertion markers taken away; the element stays. */
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. */
722
932
  function removeHolderMarkers(t) {
723
933
  var _a;
724
934
  remove(t.owner, t.outer[0]);
@@ -744,26 +954,29 @@ function unwrapHolder(t) {
744
954
  * The insertion of a content control or custom XML element, when `items[i]` is a
745
955
  * `w:customXmlInsRangeStart` and what follows is Word's form for one (check 30): the element, whose
746
956
  * content starts with that marker's `w:customXmlInsRangeEnd` and ends with a second start, and that
747
- * one's end right after the element. Anything else is not one (a marker on its own is left alone).
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).
748
959
  */
749
- export function holderInsertionAt(items, i, paragraph) {
750
- var _a, _b, _c, _d, _e, _f;
960
+ export function holderChangeAt(items, i, paragraph, body) {
961
+ var _a, _b, _c, _d, _e, _f, _g;
751
962
  const start = items[i];
752
- if (((_a = start.name) === null || _a === void 0 ? void 0 : _a.localPart) !== 'customXmlInsRangeStart')
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)
753
965
  return undefined;
966
+ const [startName, endName] = revision === 'ins' ? ['customXmlInsRangeStart', 'customXmlInsRangeEnd'] : ['customXmlDelRangeStart', 'customXmlDelRangeEnd'];
754
967
  const holder = items[i + 1];
755
968
  const outerEnd = items[i + 2];
756
- if (!holder || !outerEnd || !HOLDER_TYPES.has((_b = typeNameOf(holder)) !== null && _b !== void 0 ? _b : '') || ((_c = outerEnd.name) === null || _c === void 0 ? void 0 : _c.localPart) !== 'customXmlInsRangeEnd')
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)
757
970
  return undefined;
758
- const inner = (_d = childrenOf(holder.value)) !== null && _d !== void 0 ? _d : [];
971
+ const inner = (_e = childrenOf(holder.value)) !== null && _e !== void 0 ? _e : [];
759
972
  const innerEnd = inner[0];
760
973
  const innerStart = inner[inner.length - 1];
761
- if (inner.length < 2 || !innerEnd || !innerStart || ((_e = innerEnd.name) === null || _e === void 0 ? void 0 : _e.localPart) !== 'customXmlInsRangeEnd' || ((_f = innerStart.name) === null || _f === void 0 ? void 0 : _f.localPart) !== 'customXmlInsRangeStart')
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)
762
975
  return undefined;
763
976
  const idOf = (marker) => marker.value.id;
764
977
  if (idOf(innerEnd) !== idOf(start) || idOf(outerEnd) !== idOf(innerStart))
765
978
  return undefined;
766
- 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);
767
980
  }
768
981
  /**
769
982
  * An inserted field rejected (Word's form, check 30 `field-inserted`): the `w:ins` pieces and the plain
@@ -853,45 +1066,56 @@ export function insertedFieldAt(items, i, paragraph) {
853
1066
  */
854
1067
  function removeRow(t) {
855
1068
  remove(t.owner, t.tr);
856
- if (t.table && rowsOf(t.table.element.value).length === 0)
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)
857
1072
  remove(t.table.container, t.table.element);
858
1073
  }
859
1074
  /**
860
1075
  * A revision taken away with its runs - a rejected insertion, an accepted deletion - and the comments
861
- * on it, by where their markers were (CR-002 section 38 item 1):
862
- * - Word writes a comment's range start inside the `w:ins` of the text it is on; when that text goes,
863
- * current Word removes the comment with it - markers, reference run and entry (check 32, run
864
- * 2026-10-02: rejecting the insertion, rejecting all, and accepting the deletion in the same shape,
865
- * each left no comment). A comment whose start comes out of the revision with no text left in its
866
- * range is removed here: the markers and the reference now, the entries in the comment parts when
867
- * the comments are next read or the package saved (`pendingCommentRemovals`).
868
- * - A comment whose markers lie outside the revision (this package's own form before 0.3.1, and any
869
- * file's) is kept when its text goes: Word 2010 and Word 15, rejecting all in the editor's
870
- * `review-for-word.docx`, left its reference run alone, the range markers dropped and the entry kept
871
- * (the editor's finding of 2026-10-02). The range markers go here, the reference and the entry stay.
872
- * A comment with text left in its range keeps its markers where the text was (not measured), and
873
- * current Word's answer for the outside form, and the older Words' for the inside one, are not measured.
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.
874
1093
  */
875
1094
  function removeRevision(t, paragraph) {
876
- var _a, _b, _c;
1095
+ var _a, _b, _c, _d;
877
1096
  const owner = currentOwner(t.owner, t.element);
878
1097
  const i = owner.indexOf(t.element);
879
1098
  if (i < 0)
880
1099
  return;
881
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'; });
882
- // the comments whose range held the revision: a start inside it (Word's form), or one before it
883
- // with its end after it (the engine's own, the markers beside the runs)
884
- const idOf = (el) => el.value.id;
885
- const inside = new Set(kept.filter((m) => { var _a; return ((_a = m.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart'; }).map(idOf));
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));
886
1105
  const outside = new Set();
887
1106
  for (let s = 0; s < i; s++) {
888
1107
  const el = owner[s];
889
1108
  if (((_b = el.name) === null || _b === void 0 ? void 0 : _b.localPart) !== 'commentRangeStart')
890
1109
  continue;
891
- const id = idOf(el);
892
- if (owner.some((other, e) => { var _a; return e > i && ((_a = other.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeEnd' && idOf(other) === id; }))
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)
893
1112
  outside.add(id);
894
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
+ }
895
1119
  discarding(t.element);
896
1120
  owner.splice(i, 1, ...kept);
897
1121
  linkParents(kept, t.element.value.PARENT);
@@ -899,23 +1123,147 @@ function removeRevision(t, paragraph) {
899
1123
  if (!body)
900
1124
  return;
901
1125
  for (const id of [...inside, ...outside]) {
902
- const start = owner.find((el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeStart' && idOf(el) === id; });
903
- if (!start || !rangeIsEmpty(owner, start, id))
904
- continue;
905
- if (inside.has(id)) {
906
- removeMarkers(body.container, id);
907
- const pkg = body.package_;
908
- (_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
909
1130
  }
910
- else {
911
- // the range markers only: the reference run and the entry stay (Word 2010 and 15)
912
- const end = owner.find((el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentRangeEnd' && idOf(el) === id; });
913
- for (const marker of [end, start])
914
- if (marker)
915
- owner.splice(owner.indexOf(marker), 1);
1131
+ else if (rangeIsEmpty(owner, owner[start], id)) {
1132
+ removeCommentWhole(body, id);
916
1133
  }
917
1134
  }
918
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
+ }
919
1267
  /** True when nothing with text stands between a comment's range start and its end in the same list. */
920
1268
  function rangeIsEmpty(owner, start, id) {
921
1269
  var _a;
@@ -946,11 +1294,13 @@ function hasText(el) {
946
1294
  /** A `w:ins` or `w:moveTo` accepted: its runs take its place. */
947
1295
  function unwrap(t) {
948
1296
  var _a;
949
- const i = t.owner.indexOf(t.element);
1297
+ const owner = currentOwner(t.owner, t.element);
1298
+ const i = owner.indexOf(t.element);
950
1299
  if (i < 0)
951
1300
  return;
952
- const items = (_a = runItemsOf(t.element.value)) !== null && _a !== void 0 ? _a : [];
953
- t.owner.splice(i, 1, ...items);
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);
954
1304
  const parent = t.value.PARENT;
955
1305
  for (const item of items)
956
1306
  linkParents(item, parent);
@@ -980,29 +1330,84 @@ function dropMark(paragraph, which) {
980
1330
  *
981
1331
  * `fallbackToPrevious` is for the second case at the end of a container, where this package's
982
1332
  * inserted paragraph carries its own mark (section 13) and there is nothing after it to join
983
- * with: the paragraph then goes, its content joining the one before. With neither neighbour the
984
- * mark is simply dropped.
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.
985
1339
  */
986
1340
  export function joinWithNext(paragraph, fallbackToPrevious = false) {
987
- var _a, _b, _c, _d, _e;
1341
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
988
1342
  discarding();
989
1343
  const container = currentOwner(paragraph.container, paragraph.element);
990
1344
  const i = container.indexOf(paragraph.element);
991
- const next = i >= 0 ? container[i + 1] : undefined;
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;
992
1351
  const p = paragraph.p;
993
1352
  if (!next || typeNameOf(next) !== 'org_docx4j_wml.P') {
994
1353
  const previous = fallbackToPrevious && i > 0 ? container[i - 1] : undefined;
995
1354
  if (previous && typeNameOf(previous) === 'org_docx4j_wml.P') {
996
1355
  const into = previous.value;
997
1356
  const items = ((_a = into.content) !== null && _a !== void 0 ? _a : (into.content = []));
998
- for (const item of (_b = p.content) !== null && _b !== void 0 ? _b : [])
999
- 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
1000
1359
  linkParents(items, into);
1001
1360
  discarding(undefined, p.pPr);
1002
1361
  container.splice(i, 1);
1003
1362
  return;
1004
1363
  }
1005
- const rPr = (_c = p.pPr) === null || _c === void 0 ? void 0 : _c.rPr;
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;
1006
1411
  if (rPr) {
1007
1412
  delete rPr.ins;
1008
1413
  delete rPr.del;
@@ -1011,9 +1416,11 @@ export function joinWithNext(paragraph, fallbackToPrevious = false) {
1011
1416
  return;
1012
1417
  }
1013
1418
  const nextP = next.value;
1014
- const content = ((_d = p.content) !== null && _d !== void 0 ? _d : (p.content = []));
1015
- for (const item of (_e = nextP.content) !== null && _e !== void 0 ? _e : [])
1419
+ const content = ((_k = p.content) !== null && _k !== void 0 ? _k : (p.content = []));
1420
+ for (const item of container.slice(i + 1, j))
1016
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
1017
1424
  linkParents(content, p);
1018
1425
  discarding(undefined, p.pPr); // a section break's sectPr goes with it
1019
1426
  p.pPr = nextP.pPr;
@@ -1030,7 +1437,42 @@ export function joinWithNext(paragraph, fallbackToPrevious = false) {
1030
1437
  else
1031
1438
  p[key] = value;
1032
1439
  }
1033
- container.splice(i + 1, 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';
1034
1476
  }
1035
1477
  /** A `w:p`'s own attributes, which go with its mark. */
1036
1478
  const PARAGRAPH_ATTRIBUTES = ['paraId', 'textId', 'rsidDel', 'rsidP', 'rsidR', 'rsidRDefault', 'rsidRPr', 'noSpellErr'];
@@ -1043,39 +1485,15 @@ function pruneRowProperties(tr) {
1043
1485
  }
1044
1486
  /** The span of a run-level revision in the paragraph's accepted text; collapsed for a deletion. */
1045
1487
  function spanOf(paragraph, element) {
1046
- const segs = paragraph.segments();
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; } });
1047
1492
  const inside = segs.filter((s) => { var _a; return ((_a = s.revision) === null || _a === void 0 ? void 0 : _a.element) === element; });
1048
1493
  if (inside.length > 0)
1049
1494
  return [inside[0].start, inside[inside.length - 1].end];
1050
- const at = acceptedOffsetOf(paragraph.p, element);
1051
- return [at, at];
1052
- }
1053
- /** How much accepted text comes before `target` in the paragraph. */
1054
- function acceptedOffsetOf(p, target) {
1055
- let pos = 0;
1056
- let found = -1;
1057
- const visit = (items) => {
1058
- if (!items || found >= 0)
1059
- return;
1060
- for (const el of items) {
1061
- if (found >= 0)
1062
- return;
1063
- if (el === target) {
1064
- found = pos;
1065
- return;
1066
- }
1067
- if (typeNameOf(el) === 'org_docx4j_wml.R') {
1068
- pos += segmentsOf({ content: [el] }).map((s) => s.text).join('').length;
1069
- continue;
1070
- }
1071
- const kind = revisionKindOf(el);
1072
- if (kind === 'del' || kind === 'moveFrom')
1073
- continue;
1074
- visit(runItemsOf(el.value));
1075
- }
1076
- };
1077
- visit(runItemsOf(p));
1078
- return found >= 0 ? found : pos;
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];
1079
1497
  }
1080
1498
  // --- math (CR-002 section 51) ------------------------------------------------------------------
1081
1499
  /** True for a value of the math model (`m:oMath`, `m:r`, `m:sSup`, ...). */
@@ -1102,7 +1520,7 @@ function resolveMathRun(t, keep) {
1102
1520
  if (i < 0)
1103
1521
  return;
1104
1522
  if (keep) {
1105
- const moved = ((_b = t.value.rest) !== null && _b !== void 0 ? _b : []);
1523
+ const moved = ((_b = t.value.rest) !== null && _b !== void 0 ? _b : []).splice(0);
1106
1524
  content.splice(i, 1, ...moved);
1107
1525
  linkParents(moved, t.run.value);
1108
1526
  return;
@@ -1110,7 +1528,9 @@ function resolveMathRun(t, keep) {
1110
1528
  discarding(t.item);
1111
1529
  content.splice(i, 1);
1112
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;
1113
1532
  remove(t.runOwner, t.run);
1533
+ dropEmptiedEquation(container);
1114
1534
  }
1115
1535
  }
1116
1536
  /**
@@ -1119,14 +1539,16 @@ function resolveMathRun(t, keep) {
1119
1539
  * whole from its list. An argument's own marker has no structure, and is dropped either way.
1120
1540
  */
1121
1541
  function resolveMathStructure(t, keep) {
1122
- var _a;
1542
+ var _a, _b;
1123
1543
  if (!keep && t.structure && t.owner) {
1544
+ const container = (_a = t.structure.value) === null || _a === void 0 ? void 0 : _a.PARENT;
1124
1545
  remove(t.owner, t.structure);
1546
+ dropEmptiedEquation(container);
1125
1547
  return;
1126
1548
  }
1127
1549
  const ctrlPr = t.ctrlPr;
1128
1550
  if (!ctrlPr.rPr && t.value.rPr) {
1129
- ctrlPr.rPr = rPrFromElements((_a = t.value.rPr.egrPrBase) !== null && _a !== void 0 ? _a : []);
1551
+ ctrlPr.rPr = rPrFromElements((_b = t.value.rPr.egrPrBase) !== null && _b !== void 0 ? _b : []);
1130
1552
  linkParents(ctrlPr.rPr, ctrlPr);
1131
1553
  }
1132
1554
  if (t.revision === 'ins')
@@ -1134,6 +1556,37 @@ function resolveMathStructure(t, keep) {
1134
1556
  else
1135
1557
  delete ctrlPr.del;
1136
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
+ }
1137
1590
  /** The equation a math piece is in: the outermost level's element, the one in the paragraph. */
1138
1591
  function equationOf(stack) {
1139
1592
  var _a;
@@ -1221,7 +1674,7 @@ function resolveMove(change, action) {
1221
1674
  const t = change.target;
1222
1675
  if (t.kind !== 'run' || !change.paragraph)
1223
1676
  return false;
1224
- const story = storyOf(change.paragraph.p);
1677
+ const story = storyOf(t.element.value);
1225
1678
  const pieces = movePiecesOf(story);
1226
1679
  const half = pieces.find((piece) => piece.kind === 'half' && piece.element === t.element);
1227
1680
  if (!half || half.kind !== 'half' || half.names.length === 0)
@@ -1286,22 +1739,36 @@ function resolveMove(change, action) {
1286
1739
  dissolveBrokenMoves(story);
1287
1740
  return true;
1288
1741
  }
1289
- /** The story a paragraph is in: the outermost array of block content above it (a body, a header, a note). */
1290
- function storyOf(p) {
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) {
1291
1750
  let story;
1292
- for (let o = p; o !== undefined; o = o.PARENT) {
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
+ }
1293
1760
  const content = o.content;
1294
- if (Array.isArray(content) && o.TYPE_NAME !== 'org_docx4j_wml.P')
1761
+ if (Array.isArray(content) && tn !== 'org_docx4j_wml.P')
1295
1762
  story = content;
1296
1763
  }
1297
1764
  return story !== null && story !== void 0 ? story : [];
1298
1765
  }
1299
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. */
1300
- function movePiecesOf(story) {
1767
+ export function movePiecesOf(story) {
1301
1768
  const out = [];
1302
1769
  const open = { from: new Map(), to: new Map() };
1303
1770
  const visit = (items, runLevel = false) => {
1304
- var _a, _b, _c, _d, _e, _f;
1771
+ var _a, _b, _c, _d, _e, _f, _g;
1305
1772
  for (const el of items) {
1306
1773
  const marker = RANGE_MARKERS[(_b = (_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : ''];
1307
1774
  const value = el.value;
@@ -1331,11 +1798,18 @@ function movePiecesOf(story) {
1331
1798
  const v = el.value;
1332
1799
  if (typeof v !== 'object' || v === null)
1333
1800
  continue;
1334
- const children = (_e = childrenOf(v)) !== null && _e !== void 0 ? _e : runItemsOf(v);
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);
1335
1809
  if (children)
1336
1810
  visit(children, typeNameOf(el) === 'org_docx4j_wml.P');
1337
1811
  if (typeNameOf(el) === 'org_docx4j_wml.P') {
1338
- const rPr = (_f = v.pPr) === null || _f === void 0 ? void 0 : _f.rPr;
1812
+ const rPr = (_g = v.pPr) === null || _g === void 0 ? void 0 : _g.rPr;
1339
1813
  for (const side of ['from', 'to']) {
1340
1814
  if (rPr === null || rPr === void 0 ? void 0 : rPr[side === 'from' ? 'moveFrom' : 'moveTo']) {
1341
1815
  out.push({ kind: 'mark', side, paragraph: el, container: items, names: [...open[side].values()] });
@@ -1359,7 +1833,7 @@ function resolveRangelessHalf(change, action) {
1359
1833
  const paragraph = change.paragraph;
1360
1834
  if (t.kind !== 'run' || (t.revision !== 'moveTo' && t.revision !== 'moveFrom') || !paragraph)
1361
1835
  return false;
1362
- const story = storyOf(paragraph.p);
1836
+ const story = storyOf(t.element.value);
1363
1837
  const pieces = movePiecesOf(story);
1364
1838
  const half = pieces.find((piece) => piece.kind === 'half' && piece.element === t.element);
1365
1839
  if (!half || half.kind !== 'half' || half.names.length > 0)
@@ -1495,18 +1969,76 @@ function markBefore(paragraph) {
1495
1969
  }
1496
1970
  /** Between two tracked changes: something unrevised - text, a paragraph mark, a table - that keeps them apart. */
1497
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
+ }
1498
2030
  /**
1499
2031
  * A paragraph's single changes in document order, its mark last, with a `BREAK` wherever unrevised
1500
2032
  * content stands between two of them: `groupTouching` makes the groups from these.
1501
2033
  */
1502
- export function trackedChangeTokensOfParagraph(paragraph) {
2034
+ export function trackedChangeTokensOfParagraph(paragraph, listing = listedListing()) {
1503
2035
  var _a, _b, _c, _d;
1504
2036
  const out = [];
1505
2037
  const p = paragraph.p;
1506
2038
  const pPrChange = (_a = p.pPr) === null || _a === void 0 ? void 0 : _a.pPrChange;
1507
2039
  if (pPrChange)
1508
2040
  out.push(new TrackedChange({ kind: 'paragraphProperties', value: pPrChange }, paragraph));
1509
- tokensOfRunLevel(paragraph, ((_b = p.content) !== null && _b !== void 0 ? _b : []), out);
2041
+ tokensOfRunLevel(paragraph, ((_b = p.content) !== null && _b !== void 0 ? _b : []), out, listing);
1510
2042
  const rPr = (_c = p.pPr) === null || _c === void 0 ? void 0 : _c.rPr;
1511
2043
  // a mark whose formatting changed is a piece, grouping with its runs' (check 27: "Before.\r")
1512
2044
  if (rPr === null || rPr === void 0 ? void 0 : rPr.rPrChange)
@@ -1533,17 +2065,17 @@ function sectionText(items, end) {
1533
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)))
1534
2066
  from--;
1535
2067
  return items.slice(Math.max(from, 0), end).filter((el) => typeNameOf(el) === 'org_docx4j_wml.P')
1536
- .map((el) => textOfView(el.value, { view: 'accepted' })).join('\r');
2068
+ .map((el) => textOfView(el.value, { view: 'accepted', characters: 'textOf' })).join('\r');
1537
2069
  }
1538
- function tokensOfRunLevel(paragraph, items, out) {
2070
+ function tokensOfRunLevel(paragraph, items, out, listing) {
1539
2071
  var _a, _b, _c;
1540
2072
  for (let i = 0; i < items.length; i++) {
1541
2073
  const el = items[i];
1542
2074
  // an inline control or custom XML element that is itself an insertion: a piece, then what is in it
1543
- const inserted = holderInsertionAt(items, i, paragraph);
2075
+ const inserted = holderChangeAt(items, i, paragraph);
1544
2076
  if (inserted) {
1545
2077
  out.push(inserted);
1546
- 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);
1547
2079
  i += 2;
1548
2080
  continue;
1549
2081
  }
@@ -1572,7 +2104,7 @@ function tokensOfRunLevel(paragraph, items, out) {
1572
2104
  if (nested) {
1573
2105
  // a revision inside a revision (a deletion of inserted text) was not measured: listed apart
1574
2106
  const inner = [];
1575
- tokensOfRunLevel(paragraph, nested, inner);
2107
+ tokensOfRunLevel(paragraph, nested, inner, listing);
1576
2108
  if (inner.some((token) => token !== BREAK && token.target.kind === 'run'))
1577
2109
  out.push(BREAK, ...inner, BREAK);
1578
2110
  else
@@ -1591,6 +2123,12 @@ function tokensOfRunLevel(paragraph, items, out) {
1591
2123
  out.push(BREAK);
1592
2124
  continue;
1593
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
+ }
1594
2132
  // a run holder that is not itself an insertion - a hyperlink, a field, a control or custom XML
1595
2133
  // element that was there - keeps the insertions inside it apart from those outside: Office JS
1596
2134
  // listed "before ", "link" and " after" as three changes over a w:ins in a w:hyperlink, and the same
@@ -1599,7 +2137,7 @@ function tokensOfRunLevel(paragraph, items, out) {
1599
2137
  const nested = runItemsOf(el.value);
1600
2138
  if (nested) {
1601
2139
  out.push(BREAK);
1602
- tokensOfRunLevel(paragraph, nested, out);
2140
+ tokensOfRunLevel(paragraph, nested, out, listing);
1603
2141
  out.push(BREAK);
1604
2142
  }
1605
2143
  }
@@ -1617,7 +2155,9 @@ function groupable(change) {
1617
2155
  return t.revision;
1618
2156
  if (t.kind === 'runProperties' || t.kind === 'markProperties')
1619
2157
  return 'format';
1620
- if (t.kind === 'holder' || t.kind === 'field')
2158
+ if (t.kind === 'holder')
2159
+ return t.revision;
2160
+ if (t.kind === 'field')
1621
2161
  return 'ins';
1622
2162
  if (t.kind === 'mathRun' || t.kind === 'mathStructure')
1623
2163
  return t.revision;
@@ -1663,7 +2203,7 @@ export function groupTouching(tokens) {
1663
2203
  const first = item[0];
1664
2204
  if (item.length === 1)
1665
2205
  return first;
1666
- 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);
1667
2207
  });
1668
2208
  }
1669
2209
  /**
@@ -1681,9 +2221,9 @@ export function trackedChangesOfRow(tr, owner, inner = [], table, body) {
1681
2221
  }
1682
2222
  // --- CR-002 section 35: tables, cells, marks and sections --------------------------------------------
1683
2223
  /** The change a table's property records make, one per table, or undefined when they make none. */
1684
- export function tablePropertiesChangeOf(tbl) {
2224
+ export function tablePropertiesChangeOf(tbl, body) {
1685
2225
  const records = tableRecordsOf(tbl.value);
1686
- 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;
1687
2227
  }
1688
2228
  /** A cell's own revision, `w:cellIns` or `w:cellDel`, or undefined. */
1689
2229
  export function cellChangeOf(tc, owner, tr, body) {
@@ -1695,30 +2235,37 @@ export function cellChangeOf(tc, owner, tr, body) {
1695
2235
  return undefined;
1696
2236
  }
1697
2237
  /** The change to the body's last section (`w:body/w:sectPr/w:sectPrChange`), its text the section's paragraphs'. */
1698
- export function bodySectionChangeOf(container) {
2238
+ export function bodySectionChangeOf(container, body) {
1699
2239
  var _a;
1700
2240
  const sectPr = container.sectPr;
1701
2241
  if (!(sectPr === null || sectPr === void 0 ? void 0 : sectPr.sectPrChange))
1702
2242
  return undefined;
1703
2243
  const items = (_a = container.content) !== null && _a !== void 0 ? _a : [];
1704
- 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);
1705
2245
  }
1706
2246
  /**
1707
2247
  * A cell taken out of its row, the cell before it (else after it) widened over its grid columns, as
1708
2248
  * Word did rejecting an inserted cell and accepting a deleted one (check 27: `w:gridSpan` 2 and the
1709
- * 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).
1710
2254
  */
1711
- function removeCell(tc, owner) {
2255
+ function removeCell(tc, owner, tr) {
1712
2256
  var _a, _b, _c, _d, _e, _f, _g;
1713
2257
  var _h;
1714
- const i = owner.indexOf(tc);
2258
+ const list = currentOwner(owner, tc);
2259
+ const i = list.indexOf(tc);
1715
2260
  if (i < 0)
1716
2261
  return;
1717
- const cells = owner.filter((el) => typeNameOf(el) === 'org_docx4j_wml.Tc');
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');
1718
2265
  const at = cells.indexOf(tc);
1719
2266
  const neighbour = (_a = cells[at - 1]) !== null && _a !== void 0 ? _a : cells[at + 1];
1720
2267
  discarding(tc);
1721
- owner.splice(i, 1);
2268
+ list.splice(i, 1);
1722
2269
  if (!neighbour)
1723
2270
  return;
1724
2271
  const removed = tc.value.tcPr;
@@ -1774,7 +2321,8 @@ function restoreSection(sectPr, change) {
1774
2321
  * writes and drops when one is put in, and whose record is the old number's text, so that a reject
1775
2322
  * too can only drop it; and a table's records that change nothing. A merge rejected gets the merge it
1776
2323
  * recorded back (docx4j's reading; unmeasured). A `w:customXmlInsRangeStart` or `End` left without
1777
- * the form `holderInsertionAt` lists marks nothing that can be found, and goes either way.
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).
1778
2326
  */
1779
2327
  export function resolveUnlisted(items, what) {
1780
2328
  const visit = (list) => {
@@ -1783,7 +2331,7 @@ export function resolveUnlisted(items, what) {
1783
2331
  return;
1784
2332
  for (let i = list.length - 1; i >= 0; i--) {
1785
2333
  const name = (_a = list[i].name) === null || _a === void 0 ? void 0 : _a.localPart;
1786
- if (name === 'customXmlInsRangeStart' || name === 'customXmlInsRangeEnd')
2334
+ if (HOLDER_MARKERS.has(name !== null && name !== void 0 ? name : ''))
1787
2335
  list.splice(i, 1);
1788
2336
  }
1789
2337
  for (const el of list) {
@@ -1799,8 +2347,8 @@ export function resolveUnlisted(items, what) {
1799
2347
  const tbl = el.value;
1800
2348
  if (hasTableRecords(tbl) && !tableRecordsOf(tbl))
1801
2349
  acceptTable(tbl);
1802
- for (const row of rowsOf(tbl)) {
1803
- 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')) {
1804
2352
  const tcPr = cell.element.value.tcPr;
1805
2353
  if (tcPr === null || tcPr === void 0 ? void 0 : tcPr.cellMerge)
1806
2354
  resolveCellMerge(tcPr, what);
@@ -1810,25 +2358,38 @@ export function resolveUnlisted(items, what) {
1810
2358
  continue;
1811
2359
  }
1812
2360
  const v = el.value;
1813
- if (typeof v === 'object' && v !== null)
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
1814
2368
  visit(childrenOf(v));
1815
2369
  }
1816
2370
  };
1817
2371
  visit(items);
1818
2372
  }
1819
- /** Stray `w:customXmlInsRangeStart` / `End` markers taken out of run-level content, through its holders and revisions. */
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. */
1820
2376
  function sweepMarkers(items) {
1821
2377
  var _a;
1822
2378
  if (!items)
1823
2379
  return;
1824
2380
  for (let i = items.length - 1; i >= 0; i--) {
1825
2381
  const name = (_a = items[i].name) === null || _a === void 0 ? void 0 : _a.localPart;
1826
- if (name === 'customXmlInsRangeStart' || name === 'customXmlInsRangeEnd') {
2382
+ if (HOLDER_MARKERS.has(name !== null && name !== void 0 ? name : '')) {
1827
2383
  items.splice(i, 1);
1828
2384
  continue;
1829
2385
  }
1830
2386
  const v = items[i].value;
1831
- if (typeof v === 'object' && v !== null)
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
1832
2393
  sweepMarkers(runItemsOf(v));
1833
2394
  }
1834
2395
  }