@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.
Files changed (166) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +34 -3
  3. package/dist/html/convert.mjs +40 -15
  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 +153 -19
  24. package/dist/model/content/Body.mjs +570 -131
  25. package/dist/model/content/Comment.d.mts +21 -3
  26. package/dist/model/content/Comment.mjs +202 -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 +82 -7
  32. package/dist/model/content/InlinePicture.mjs +221 -28
  33. package/dist/model/content/List.mjs +13 -1
  34. package/dist/model/content/Paragraph.d.mts +213 -14
  35. package/dist/model/content/Paragraph.mjs +936 -100
  36. package/dist/model/content/Range.d.mts +242 -11
  37. package/dist/model/content/Range.mjs +975 -119
  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 +301 -16
  43. package/dist/model/content/TrackedChange.mjs +1284 -158
  44. package/dist/model/content/binder.d.mts +2 -1
  45. package/dist/model/content/binder.mjs +48 -1
  46. package/dist/model/content/comments.d.mts +20 -6
  47. package/dist/model/content/comments.mjs +67 -57
  48. package/dist/model/content/generalException.d.mts +18 -0
  49. package/dist/model/content/generalException.mjs +24 -0
  50. package/dist/model/content/index.d.mts +8 -3
  51. package/dist/model/content/index.mjs +7 -2
  52. package/dist/model/content/mathText.d.mts +21 -0
  53. package/dist/model/content/mathText.mjs +267 -0
  54. package/dist/model/content/officeText.d.mts +27 -0
  55. package/dist/model/content/officeText.mjs +74 -0
  56. package/dist/model/content/outline.d.mts +5 -0
  57. package/dist/model/content/outline.mjs +13 -0
  58. package/dist/model/content/placeholders.d.mts +110 -0
  59. package/dist/model/content/placeholders.mjs +253 -0
  60. package/dist/model/content/relationships.d.mts +30 -0
  61. package/dist/model/content/relationships.mjs +133 -0
  62. package/dist/model/content/search.d.mts +72 -0
  63. package/dist/model/content/search.mjs +204 -2
  64. package/dist/model/content/spanEdits.d.mts +159 -0
  65. package/dist/model/content/spanEdits.mjs +728 -0
  66. package/dist/model/content/story.d.mts +44 -0
  67. package/dist/model/content/story.mjs +92 -0
  68. package/dist/model/content/tableColumns.d.mts +1 -1
  69. package/dist/model/content/tableColumns.mjs +108 -80
  70. package/dist/model/content/tableRevisions.d.mts +11 -3
  71. package/dist/model/content/tableRevisions.mjs +72 -42
  72. package/dist/model/content/tableRules.d.mts +30 -0
  73. package/dist/model/content/tableRules.mjs +100 -0
  74. package/dist/model/content/textBoxes.d.mts +18 -0
  75. package/dist/model/content/textBoxes.mjs +105 -0
  76. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  77. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  78. package/dist/model/content/tracking.d.mts +28 -0
  79. package/dist/model/content/tracking.mjs +149 -10
  80. package/dist/model/content/tree.d.mts +212 -13
  81. package/dist/model/content/tree.mjs +356 -35
  82. package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
  83. package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
  84. package/dist/model/customxml/XmlMapping.d.mts +1 -1
  85. package/dist/model/customxml/XmlMapping.mjs +11 -0
  86. package/dist/model/customxml/bindings.d.mts +21 -2
  87. package/dist/model/customxml/bindings.mjs +76 -32
  88. package/dist/model/customxml/kinds.d.mts +8 -3
  89. package/dist/model/customxml/kinds.mjs +26 -5
  90. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  91. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  92. package/dist/model/customxml/pictures.d.mts +9 -5
  93. package/dist/model/customxml/pictures.mjs +31 -13
  94. package/dist/model/customxml/xhtml.mjs +8 -2
  95. package/dist/model/fonts/families.generated.mjs +2 -2
  96. package/dist/model/fonts/registry.mjs +7 -1
  97. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  98. package/dist/model/fonts/themes.generated.mjs +1 -1
  99. package/dist/model/index.d.mts +2 -0
  100. package/dist/model/index.mjs +4 -0
  101. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  102. package/dist/model/listnumbering/Emulator.mjs +63 -9
  103. package/dist/model/listnumbering/definitions.mjs +18 -0
  104. package/dist/model/properties/PropertyResolver.d.mts +87 -7
  105. package/dist/model/properties/PropertyResolver.mjs +197 -17
  106. package/dist/model/properties/catalogue.d.mts +32 -4
  107. package/dist/model/properties/catalogue.mjs +115 -36
  108. package/dist/model/properties/log.d.mts +1 -1
  109. package/dist/model/properties/log.mjs +5 -5
  110. package/dist/model/properties/table.d.mts +16 -2
  111. package/dist/model/properties/table.mjs +17 -5
  112. package/dist/model/text/Extractor.d.mts +289 -0
  113. package/dist/model/text/Extractor.mjs +2162 -0
  114. package/dist/model/text/LinkTargets.d.mts +45 -0
  115. package/dist/model/text/LinkTargets.mjs +170 -0
  116. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  117. package/dist/model/text/MarkdownExporter.mjs +255 -0
  118. package/dist/model/text/PlainText.d.mts +29 -0
  119. package/dist/model/text/PlainText.mjs +539 -0
  120. package/dist/model/text/TextContext.d.mts +161 -0
  121. package/dist/model/text/TextContext.mjs +425 -0
  122. package/dist/model/text/TextOptions.d.mts +150 -0
  123. package/dist/model/text/TextOptions.mjs +278 -0
  124. package/dist/model/text/TextResult.d.mts +504 -0
  125. package/dist/model/text/TextResult.mjs +526 -0
  126. package/dist/model/text/Texts.d.mts +99 -0
  127. package/dist/model/text/Texts.mjs +187 -0
  128. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  129. package/dist/model/text/WmlToMarkdown.mjs +911 -0
  130. package/dist/model/text/commonmark.d.mts +121 -0
  131. package/dist/model/text/commonmark.mjs +655 -0
  132. package/dist/model/text/index.d.mts +7 -0
  133. package/dist/model/text/index.mjs +8 -0
  134. package/dist/model/text/normalize.d.mts +8 -0
  135. package/dist/model/text/normalize.mjs +13 -0
  136. package/dist/model/text/outline.d.mts +4 -0
  137. package/dist/model/text/outline.mjs +71 -0
  138. package/dist/office-js/fragment.d.mts +7 -5
  139. package/dist/office-js/fragment.mjs +63 -14
  140. package/dist/office-js/proxy.d.mts +8 -0
  141. package/dist/office-js/proxy.mjs +29 -0
  142. package/dist/office-js/run.mjs +2 -0
  143. package/dist/office-js/supported.generated.mjs +16 -0
  144. package/dist/office-js/toApiScript.mjs +21 -5
  145. package/dist/opc/Load.d.mts +9 -2
  146. package/dist/opc/Save.d.mts +17 -0
  147. package/dist/opc/Save.mjs +12 -2
  148. package/dist/opc/index.d.mts +3 -3
  149. package/dist/opc/index.mjs +2 -2
  150. package/dist/opc/mce/preprocessor.d.mts +88 -7
  151. package/dist/opc/mce/preprocessor.mjs +144 -37
  152. package/dist/opc/mce/understood.d.mts +34 -0
  153. package/dist/opc/mce/understood.mjs +55 -3
  154. package/dist/packages/OpcPackage.d.mts +20 -0
  155. package/dist/packages/OpcPackage.mjs +16 -3
  156. package/dist/packages/WordprocessingMLPackage.d.mts +77 -7
  157. package/dist/packages/WordprocessingMLPackage.mjs +242 -11
  158. package/dist/parts/DefaultXmlPart.mjs +2 -2
  159. package/dist/parts/Part.d.mts +10 -2
  160. package/dist/parts/XmlPart.d.mts +35 -32
  161. package/dist/parts/XmlPart.mjs +107 -107
  162. package/dist/parts/wml/comments.mjs +30 -0
  163. package/dist/parts/wml/index.mjs +12 -0
  164. package/dist/xml/dom.d.mts +7 -0
  165. package/dist/xml/dom.mjs +18 -0
  166. package/package.json +2 -2
@@ -1,25 +1,31 @@
1
1
  import { deepCopy, marshalString } from '@docx4j/generated-objects-ts';
2
2
  import { Docx4JException } from '../../opc/exceptions.mjs';
3
- import { r as textRun, t as textItem, br as breakItem } from '@docx4j/generated-objects-ts/builders/wml';
4
- import { segmentsOf, runsOf, runItemsOf, linkParents, textOf, textOfView, typeNameOf, W_NS, runOf, paragraphOf, revisionKindOf } from './tree.mjs';
3
+ import { NotSupportedError } from '../../office-js/errors.mjs';
4
+ import { t as textItem, br as breakItem } from '@docx4j/generated-objects-ts/builders/wml';
5
+ import { segmentsOf, runsOf, runItemsOf, inShowingPlaceholder, linkParents, typeNameOf, W_NS, runOf, paragraphOf, revisionKindOf, objectInSpan, WHOLE, isAlternateContent, selectedBranchOf, inBranch, assertOutsideBranch } from './tree.mjs';
6
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
5
7
  function withRPr(run, rPr) {
6
8
  if (rPr)
7
9
  run.value.rPr = rPr;
8
10
  return run;
9
11
  }
10
- import { Font } from './Font.mjs';
12
+ import { Font, markHolderOf } from './Font.mjs';
11
13
  import { runFontSelectorOf } from '../fonts/lookup.mjs';
12
14
  import { Range } from './Range.mjs';
13
- import { searchPattern, matchesOf, expandReplacement } from './search.mjs';
15
+ import { officeMatches, expandReplacement, paragraphMarkPieces, refuseMarkReplacement, refuseWildcardMarkReplacement } from './search.mjs';
14
16
  import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
15
17
  import { cellOf } from './Table.mjs';
16
- import { ContentControl, collectRunControls } from './ContentControl.mjs';
17
- import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
18
+ import { ContentControl, collectRunControls, controlViewOf, plainTextLinesOf, textForRunControl, assertControlHolds, controlsOfTypes, holdsOneParagraph, showsPlaceholderWhenMade, BLOCK_CONTROL } from './ContentControl.mjs';
19
+ import { breakParagraphAt, BREAKABLE } from './spanEdits.mjs';
20
+ import { endsCell, endsWithParagraph, wrappingCellOf } from './tableRules.mjs';
21
+ import { dropExcluded, forWriter, linesOf, officeItems, oneParagraphRun, holdsElementCharacters } from './officeText.mjs';
22
+ import { sdt as sdtOf, nextSdtId, sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
18
23
  import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
19
- import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
24
+ import { InlinePicture, picturesIn, addImage, writableWidthEmu } from './InlinePicture.mjs';
25
+ import { showPicturePlaceholder } from '../customxml/picturePlaceholder.mjs';
20
26
  import { contentOf } from './ooxml.mjs';
21
27
  import { commentApi } from './comments.mjs';
22
- import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties } from './tracking.mjs';
28
+ import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties, markEquationDeleted, equationRevisionIn } from './tracking.mjs';
23
29
  import { trackedChangesOfParagraph } from './TrackedChange.mjs';
24
30
  import { isListItem, listOf, listOrNullObjectOf, listItemOf, listItemOrNullObjectOf, startNewList, attachToList, detachFromList, } from './List.mjs';
25
31
  const TWIPS_PER_POINT = 20;
@@ -61,22 +67,57 @@ export class Paragraph {
61
67
  get p() {
62
68
  return this.element.value;
63
69
  }
70
+ /** @internal A view of another paragraph of this one's body, for a `Range` across paragraphs (CR-014). */
71
+ viewOf(element, container) {
72
+ return new Paragraph(element, container, this.parentBody);
73
+ }
74
+ /**
75
+ * @internal A range from this paragraph's start to the end of `last`, a later paragraph of the same story, with
76
+ * `last`'s mark where `mark` is set, or `endMark` in its place (CR-014 phase 1).
77
+ */
78
+ rangeTo(last, mark, endMark) {
79
+ return new Range(this, 0, last.text.length, true, mark, last.element === this.element ? undefined : last, endMark);
80
+ }
81
+ /** How many tables deep the paragraph is: 0 outside a table, 1 in a cell, 2 in a nested table's (Office JS `tableNestingLevel`). */
82
+ get tableNestingLevel() {
83
+ let level = 0;
84
+ for (let cell = this.parentTableCell; cell; cell = cell.parentTable.parentTableCell)
85
+ level++;
86
+ return level;
87
+ }
64
88
  get index() {
65
89
  return this.container.indexOf(this.element);
66
90
  }
67
- /** The paragraph's text: runs joined, tabs as \t, breaks as \n; deleted text excluded. */
91
+ /**
92
+ * The paragraph's text as Office JS gives it in Word's default view (CR-012 phase 4, section 6.2):
93
+ * the accepted text, hidden text left out, in Word's characters (check 48: a note reference U+0002,
94
+ * a line break U+000B, a page break U+000C, a column break U+000E, a `w:cr` U+000D, the hyphens
95
+ * U+001E and U+001F, a symbol `(`, an equation as Word draws it). A kept `mc:AlternateContent`
96
+ * reads as its selected branch (check 49). Every offset a `Range` or `search` takes counts in it.
97
+ */
68
98
  get text() {
69
- return textOf(this.p);
99
+ return this.segments().map((s) => s.text).join('');
70
100
  }
71
101
  /** Replaces the content with one run of the text, keeping the first run's formatting. */
72
102
  set text(value) {
73
103
  var _a;
104
+ this.assertWritable();
105
+ // one paragraph, its line breaks w:br and w:cr (CR-002 section 60 E2), or as a plain-text control takes them (F3)
106
+ const lines = this.blockControl ? plainTextLinesOf(this.blockControl) : undefined;
107
+ const mode = lines === 'multi' ? 'multi-line' : lines === 'single' ? 'single-line' : 'one-paragraph';
108
+ // in a block control showing its placeholder, the text in the placeholder's place (CR-002 section 63)
109
+ const placeholder = this.placeholderControl;
110
+ if (placeholder) {
111
+ placeholder.writePlaceholder(value, { lineBreaks: mode });
112
+ return;
113
+ }
74
114
  if (this.changeTracker) {
75
- this.splice(0, this.text.length, value);
115
+ this.splice(0, this.text.length, value, 'before', WHOLE, mode);
76
116
  return;
77
117
  }
78
118
  const rPr = (_a = runsOf(this.p)[0]) === null || _a === void 0 ? void 0 : _a.value.rPr;
79
- this.p.content = [withRPr(textRun(value), rPr ? deepCopy(rPr) : undefined)];
119
+ this.releaseRelationships(this.p.content);
120
+ this.p.content = [oneParagraphRun(value, rPr ? deepCopy(rPr) : undefined, mode)];
80
121
  linkParents(this.p.content, this.p);
81
122
  }
82
123
  /**
@@ -84,7 +125,7 @@ export class Paragraph {
84
125
  * reads the document as it was before the tracked changes: `w:del` included, `w:ins` excluded.
85
126
  */
86
127
  getText(options) {
87
- return (options === null || options === void 0 ? void 0 : options.view) === 'original' ? textOfView(this.p, options) : this.text;
128
+ return (options === null || options === void 0 ? void 0 : options.view) === 'original' ? this.segments({ view: 'original' }).map((s) => s.text).join('') : this.text;
88
129
  }
89
130
  /** The style id (w:pStyle; docx4j's name for it); 'Normal' when none is set. Extension: Office JS has `style` and `styleBuiltIn` only. */
90
131
  get styleId() {
@@ -225,24 +266,36 @@ export class Paragraph {
225
266
  else
226
267
  pPr.outlineLvl = { val: level - 1 }; this.settleFormatting(); }
227
268
  /**
228
- * The runs' formatting: reads the first run's *effective* properties, writes direct
229
- * formatting to all (extension: `Font` is over runs, not the paragraph mark).
269
+ * The runs' formatting: reads the *effective* properties of the runs holding the paragraph's text, `null` (or `''`,
270
+ * `Mixed`) where they differ, and the mark's where it has no text, as Office JS reads them (CR-002 section 59); writes
271
+ * direct formatting to every run (extension: `Font` is over runs, not the paragraph mark).
230
272
  */
231
273
  get font() {
232
274
  return this.getFont();
233
275
  }
234
- /** `font`, with `{ direct: true }` for the direct formatting of the first run (extension). */
276
+ /** `font`, with `{ direct: true }` for the runs' direct formatting (extension). */
235
277
  getFont(options) {
236
- const holders = () => runsOf(this.p).map((r) => r.value);
278
+ const holders = (write) => {
279
+ if (write) {
280
+ this.assertWritable();
281
+ return runsOf(this.p).map((r) => r.value);
282
+ }
283
+ // a read takes the runs of Paragraph.text, Word's default view, as a range over it does; with none, a placeholder's
284
+ // runs, which Word's font reads though its text doesn't (check 52 case 13, after Accept All: Word's bold filler)
285
+ const runs = this.getRange('Content').runs.map((r) => r.value);
286
+ return runs.length > 0 ? runs : runsOf(this.p).map((r) => r.value).filter((r) => inShowingPlaceholder(r));
287
+ };
288
+ const mark = () => markHolderOf(this.p);
237
289
  if ((options === null || options === void 0 ? void 0 : options.direct) === true)
238
- return new Font(holders, () => this.fontTracking());
239
- return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr, this.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
290
+ return new Font(holders, () => this.fontTracking(), undefined, undefined, mark);
291
+ return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr, this.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); }, mark, () => this.parentBody.propertyResolver.getThemeColors());
240
292
  }
241
293
  /** w14:paraId, the stable address Word gives paragraphs. */
242
294
  get paraId() {
243
295
  return this.p.paraId;
244
296
  }
245
297
  set paraId(id) {
298
+ this.assertWritable();
246
299
  if (id === undefined)
247
300
  delete this.p.paraId;
248
301
  else
@@ -262,45 +315,82 @@ export class Paragraph {
262
315
  collectRunControls(this.p, this.parentBody, out);
263
316
  return out;
264
317
  }
265
- /** The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures). */
318
+ /**
319
+ * Office JS `getContentControls` (WordApi 1.5): the content controls `contentControls` lists, those of `options.types`
320
+ * where given. Word's `contentControls` lists rich-text controls only; this package's lists every kind, a recorded
321
+ * departure (CR-002 section 60 G1, check 55b), so this is how add-in code that asks for plain-text controls finds them.
322
+ */
323
+ getContentControls(options) {
324
+ return controlsOfTypes(this.contentControls, options);
325
+ }
326
+ /**
327
+ * The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures): each
328
+ * `w:drawing` whose `wp:inline` holds a `pic:pic`, in a run or in the selected branch of a kept
329
+ * `mc:AlternateContent` (`picturesIn`). An inline chart, text box or group is not one (CR-010
330
+ * section 12.6).
331
+ */
266
332
  get inlinePictures() {
267
- var _a, _b;
333
+ var _a;
268
334
  const out = [];
269
335
  for (const run of runsOf(this.p)) {
270
336
  for (const item of ((_a = run.value.content) !== null && _a !== void 0 ? _a : [])) {
271
- if (item.name.localPart !== 'drawing')
272
- continue;
273
- const inline = (_b = item.value.anchorOrInline) === null || _b === void 0 ? void 0 : _b[0];
274
- if (inline && inline.TYPE_NAME === 'org_docx4j_dml_wordprocessingDrawing.Inline') {
275
- out.push(new InlinePicture(item, run, this));
276
- }
337
+ for (const drawing of picturesIn(item))
338
+ out.push(new InlinePicture(drawing, run, this, drawing === item ? undefined : item));
277
339
  }
278
340
  }
279
341
  return out;
280
342
  }
281
343
  /** Text inserted at the start, the end, or replacing the whole paragraph's text. */
282
344
  insertText(text, location) {
345
+ // in a block control showing its placeholder the text takes the placeholder's place wherever it is put (CR-002
346
+ // section 63; check 63's run 2, cases 7 to 9 and 33 to 35)
347
+ const placeholder = this.placeholderControl;
348
+ if (placeholder)
349
+ return placeholder.writePlaceholder(text);
283
350
  const length = this.text.length;
284
351
  if (location === 'Start')
285
352
  return this.splice(0, 0, text);
286
353
  if (location === 'End')
287
- return this.splice(length, length, text);
288
- return this.splice(0, length, text);
354
+ return this.writeAtEnd(text);
355
+ // tracked, the insertion goes first, before the deletion, as a range's Replace writes it and Word wrote this one
356
+ // (check 63 cases 45 and 52; CR-002 section 63 D3)
357
+ if (this.changeTracker && length > 0)
358
+ return this.getRange('Content').insertText(text, 'Replace');
359
+ return this.splice(0, length, text, 'before', WHOLE);
289
360
  }
290
- /** A new paragraph of the text before or after this one, with this paragraph's properties (as Word). */
361
+ /**
362
+ * A new paragraph of the text before or after this one, with this paragraph's properties (as Word). Beside the only
363
+ * paragraph of a block control showing its placeholder it goes outside the control, as Word's went, and the control
364
+ * stays at block level (CR-002 section 63, D6 (b); check 63 cases 10, 11, 36 and 37).
365
+ */
291
366
  insertParagraph(text, location) {
367
+ var _a, _b;
368
+ this.assertWritable();
292
369
  const pPr = this.p.pPr ? deepCopy(this.p.pPr) : undefined;
293
370
  if (pPr) {
294
371
  delete pPr.rPr;
295
372
  delete pPr.sectPr;
296
373
  delete pPr.pPrChange;
297
374
  }
298
- const el = paragraphOf(text === '' ? [] : [textRun(text)], pPr);
375
+ const run = oneParagraphRun(text); // E2
376
+ const el = paragraphOf(((_a = run.value.content) === null || _a === void 0 ? void 0 : _a.length) ? [run] : [], pPr);
377
+ const control = this.blockControl;
378
+ if ((control === null || control === void 0 ? void 0 : control.isShowingPlaceholder) && holdsOneParagraph(control)) {
379
+ // the body holding the control: this paragraph's is the control's content
380
+ const owner = (_b = control.element.value.PARENT) !== null && _b !== void 0 ? _b : this.parentBody.container;
381
+ return this.parentBody.sub(owner).insertElement(el, location, { element: control.element, container: control.container });
382
+ }
299
383
  return this.parentBody.insertElement(el, location, this);
300
384
  }
385
+ /** The block-level control holding this paragraph directly while it shows its placeholder (CR-002 section 63). */
386
+ get placeholderControl() {
387
+ const control = this.blockControl;
388
+ return (control === null || control === void 0 ? void 0 : control.showsPlaceholderContent) ? control : undefined;
389
+ }
301
390
  insertBreak(type, location) {
302
391
  var _a;
303
392
  var _b;
393
+ this.assertWritable();
304
394
  const brk = breakItem(type === 'Page' ? 'page' : undefined);
305
395
  if (location === 'Before' || location === 'After') {
306
396
  const p = this.insertParagraph('', location);
@@ -328,18 +418,29 @@ export class Paragraph {
328
418
  insertInlinePictureFromBase64(base64, location, options) {
329
419
  var _a;
330
420
  var _b;
421
+ this.assertWritable();
331
422
  const part = this.parentBody.part;
332
423
  if (!part)
333
424
  throw new Docx4JException('This body has no part, so an image cannot be related to it');
425
+ const tracker = this.changeTracker;
426
+ // refused before the image part is added, not after (CR-012 section 12.4)
427
+ if (location === 'Replace' && tracker)
428
+ this.assertPlaceable(0, this.text.length, 'replace');
334
429
  const { run, drawing } = addImage(part, base64, this.parentBody.container, writableWidthEmu(this.parentBody.container), options);
430
+ // under tracking the picture is an insertion, and what it replaces is deleted as insertText's is
431
+ if (location === 'Replace' && tracker)
432
+ this.splice(0, this.text.length, '', 'before', WHOLE);
335
433
  const content = ((_a = (_b = this.p).content) !== null && _a !== void 0 ? _a : (_b.content = []));
336
- if (location === 'Replace')
434
+ if (location === 'Replace' && !tracker) {
435
+ this.releaseRelationships(content);
337
436
  content.length = 0;
437
+ }
438
+ const item = tracker ? tracker.ins([run]) : run;
338
439
  if (location === 'Start')
339
- content.unshift(run);
440
+ content.unshift(item);
340
441
  else
341
- content.push(run);
342
- linkParents(run, this.p);
442
+ content.push(item);
443
+ linkParents(item, this.p);
343
444
  return new InlinePicture(drawing, run, this);
344
445
  }
345
446
  /**
@@ -350,6 +451,7 @@ export class Paragraph {
350
451
  */
351
452
  async insertOoxml(ooxml, location) {
352
453
  var _a, _b;
454
+ this.assertWritable();
353
455
  const preprocess = (_a = this.parentBody.package_) === null || _a === void 0 ? void 0 : _a.loadOptions.preprocessor;
354
456
  const elements = await contentOf(ooxml, { preprocess: preprocess ? (doc) => preprocess(doc) : undefined, target: this.parentBody.part });
355
457
  if (elements.length === 0)
@@ -360,12 +462,12 @@ export class Paragraph {
360
462
  return [this];
361
463
  }
362
464
  if (location === 'Replace') {
363
- const inserted = this.parentBody.insertElement(elements, 'Before', this);
465
+ const inserted = this.parentBody.insertElement(elements, 'Before', this, 'GeneralException', this.element);
364
466
  void inserted;
365
467
  this.delete();
366
468
  }
367
469
  else {
368
- this.parentBody.insertElement(elements, location === 'Start' ? 'Before' : location === 'End' ? 'After' : location, this);
470
+ this.parentBody.insertElement(elements, location === 'Start' ? 'Before' : location === 'End' ? 'After' : location, this, 'GeneralException');
369
471
  }
370
472
  return elements.map((el) => typeNameOf(el) === 'org_docx4j_wml.P'
371
473
  ? new Paragraph(el, this.container, this.parentBody)
@@ -373,6 +475,10 @@ export class Paragraph {
373
475
  }
374
476
  /** Matches within this paragraph. */
375
477
  search(text, options) {
478
+ // Word's paragraph mark, ^p, is searched in the paragraph's whole range, which holds its mark (CR-014 phase 3), and so
479
+ // is a wildcard query, whose ? or class matches the mark too (check 59; CR-002 section 32, K7)
480
+ if ((options === null || options === void 0 ? void 0 : options.matchWildcards) || (!(options === null || options === void 0 ? void 0 : options.matchRegExp) && paragraphMarkPieces(text)))
481
+ return this.getRange('Whole').search(text, options);
376
482
  return this.searchMatches(text, options).map((m) => m.range);
377
483
  }
378
484
  /**
@@ -381,8 +487,8 @@ export class Paragraph {
381
487
  * and `Body.replaceText` the other.
382
488
  */
383
489
  searchMatches(text, options) {
384
- return matchesOf(this.text, searchPattern(text, options))
385
- .map((match) => ({ range: new Range(this, match.index, match.index + match[0].length), match }));
490
+ return officeMatches(this.segments({ objects: true }), text, options)
491
+ .map(({ start, end, match }) => ({ range: new Range(this, start, end), match }));
386
492
  }
387
493
  /**
388
494
  * Replaces every match of `find` with `replace`, last match first so that the offsets of the
@@ -390,6 +496,9 @@ export class Paragraph {
390
496
  * the package's `changeTrackingMode` is on.
391
497
  */
392
498
  replaceText(find, replace, options) {
499
+ refuseMarkReplacement('Paragraph', find, options);
500
+ if (options === null || options === void 0 ? void 0 : options.matchWildcards)
501
+ refuseWildcardMarkReplacement('Paragraph', this.search(find, options));
393
502
  const matches = this.searchMatches(find, options);
394
503
  for (let i = matches.length - 1; i >= 0; i--) {
395
504
  const { range, match } = matches[i];
@@ -401,44 +510,93 @@ export class Paragraph {
401
510
  getTrackedChanges() {
402
511
  return trackedChangesOfParagraph(this);
403
512
  }
513
+ /**
514
+ * The paragraph's range: 'Whole' and 'Content' hold its whole content, the pictures at its start and
515
+ * end among them, so deleting it deletes them (CR-002 section 48); 'Start' and 'End' are collapsed.
516
+ * 'Whole' holds the paragraph's mark too, so its `text` ends with `markText`, as Office JS's does
517
+ * (check 48); 'Content' ends with the paragraph's text.
518
+ */
404
519
  getRange(location = 'Whole') {
405
520
  const length = this.text.length;
406
521
  if (location === 'Start')
407
522
  return new Range(this, 0, 0);
408
523
  if (location === 'End')
409
- return new Range(this, length, length);
410
- return new Range(this, 0, length);
524
+ return new Range(this, length, length).atEndOf('End');
525
+ const range = new Range(this, 0, length, true, location === 'Whole');
526
+ return location === 'Content' ? range.atEndOf('Content') : range;
527
+ }
528
+ /**
529
+ * The character Office JS gives for this paragraph's mark at the end of its 'Whole' range (check 48):
530
+ * `\t` for the last paragraph of a table cell, the cell's end; nothing for a mark tracked as deleted, which
531
+ * Word's default view doesn't show (CR-012 section 19, check 58); and `\r` for any other.
532
+ */
533
+ get markText() {
534
+ var _a, _b;
535
+ if (lastInCell(this.p))
536
+ return '\t';
537
+ return ((_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.rPr) === null || _b === void 0 ? void 0 : _b.del) !== undefined ? '' : '\r';
411
538
  }
412
539
  /**
413
540
  * Removes the paragraph from its container. While the package tracks changes, the content
414
541
  * becomes a `w:del` and the mark is marked deleted (`w:pPr/w:rPr/w:del`) instead, unless the
415
542
  * whole paragraph was this author's own insertion, which Word simply takes back.
543
+ * The paragraph a table cell ends with, as Word reads it, stays where removing it would leave the
544
+ * cell without one, emptied, its properties kept; tracked, its mark is never marked deleted, as Word
545
+ * keeps a cell's last paragraph (check 52, cases 88 to 91; CR-002 section 55.6).
416
546
  */
417
547
  delete() {
548
+ this.remove(true);
549
+ }
550
+ /**
551
+ * `delete()`; with `keepCellEnd` false a cell's last paragraph goes as any other, for `Body.clearContent`'s
552
+ * callers, which put their own paragraph in next.
553
+ * @internal
554
+ */
555
+ remove(keepCellEnd) {
556
+ this.assertWritable();
557
+ const cellEnd = keepCellEnd && endsCell(this.p);
418
558
  const tracker = this.changeTracker;
419
559
  if (tracker) {
420
- this.trackedDelete(tracker);
560
+ this.trackedDelete(tracker, cellEnd);
421
561
  return;
422
562
  }
423
563
  const i = this.index;
424
- if (i >= 0)
425
- this.container.splice(i, 1);
564
+ if (i < 0)
565
+ return;
566
+ this.container.splice(i, 1);
567
+ this.releaseRelationships(this.element);
568
+ if (cellEnd && !this.cellStillEnds()) {
569
+ this.p.content = [];
570
+ this.container.splice(i, 0, this.element);
571
+ }
572
+ }
573
+ /** Whether the cell this paragraph was in ends with a paragraph without it, as Word reads it (CR-002 section 55). */
574
+ cellStillEnds() {
575
+ var _a;
576
+ return endsWithParagraph((_a = wrappingCellOf(this.p.PARENT)) === null || _a === void 0 ? void 0 : _a.content);
426
577
  }
427
- trackedDelete(tracker) {
578
+ trackedDelete(tracker, cellEnd) {
428
579
  var _a, _b;
429
580
  if (markDeleted(this.p))
430
581
  throw new Docx4JException('This paragraph is already marked deleted');
431
- const length = this.text.length;
432
- if (length > 0)
433
- this.deleteText(tracker, 0, length);
582
+ // the whole content, its pictures too: before, a picture's run, having no text, stayed out of the
583
+ // w:del, and accepting joined it into the next paragraph (CR-002 section 48)
584
+ this.deleteText(tracker, 0, this.text.length, WHOLE);
434
585
  const rPr = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.rPr;
435
586
  const ownMark = (rPr === null || rPr === void 0 ? void 0 : rPr.ins) !== undefined && rPr.ins.author === tracker.author;
436
587
  if (ownMark && ((_b = this.p.content) !== null && _b !== void 0 ? _b : []).length === 0) {
437
588
  const i = this.index;
438
- if (i >= 0)
589
+ if (i >= 0) {
439
590
  this.container.splice(i, 1);
591
+ if (!cellEnd || this.cellStillEnds())
592
+ return;
593
+ this.container.splice(i, 0, this.element);
594
+ }
440
595
  return;
441
596
  }
597
+ // a cell's last mark: Word deletes the paragraph's text and never marks the mark (check 52, cases 90 and 91)
598
+ if (cellEnd)
599
+ return;
442
600
  tracker.markParagraphDeleted(this.p);
443
601
  // Word, deleting a paragraph mark ALONE under tracking, gives the next paragraph this one's
444
602
  // properties as a w:pPrChange (CR-002 section 38 item 2, the editor's measure-word15.docx), so
@@ -453,15 +611,27 @@ export class Paragraph {
453
611
  * what the cells of a deleted table row need (a `w:tc` must keep a block-level child, and Word
454
612
  * writes `w:del` around every run and `w:pPr/w:rPr/w:del` on every mark inside a deleted row).
455
613
  * The same primitives `delete()` uses, without its "take back my own insertion" step.
456
- * Does nothing when the mark is already deleted.
614
+ * A mark already deleted is left as it is, and the text is deleted still: a paragraph another author's
615
+ * deletion ended keeps text a range's deletion across it must take (CR-014 phase 2).
457
616
  */
458
617
  markDeletedInPlace(tracker) {
459
- if (markDeleted(this.p))
618
+ this.assertWritable();
619
+ this.deleteText(tracker, 0, this.text.length, WHOLE);
620
+ if (!markDeleted(this.p))
621
+ tracker.markParagraphDeleted(this.p);
622
+ }
623
+ /**
624
+ * Queues the relationships that content removed outright referenced (a picture's image, a link's
625
+ * target), to be settled when the package is saved: each goes then if nothing references it, and
626
+ * its part with it when nothing else targets that (CR-002 sections 47 and 48).
627
+ */
628
+ releaseRelationships(removed) {
629
+ const ids = new Set();
630
+ relationshipIdsIn(removed, ids);
631
+ if (ids.size === 0)
460
632
  return;
461
- const length = this.text.length;
462
- if (length > 0)
463
- this.deleteText(tracker, 0, length);
464
- tracker.markParagraphDeleted(this.p);
633
+ const pkg = this.parentBody.package_;
634
+ queueRelationshipRemovals(pkg === null || pkg === void 0 ? void 0 : pkg.pendingRelationshipRemovals, this.parentBody.part, ids);
465
635
  }
466
636
  /** The paragraph as XML (extension; Office JS getOoxml wraps it in a package). */
467
637
  getXml() {
@@ -485,6 +655,7 @@ export class Paragraph {
485
655
  pPr() {
486
656
  var _a, _b;
487
657
  var _c;
658
+ this.assertWritable();
488
659
  const pPr = ((_a = (_c = this.p).pPr) !== null && _a !== void 0 ? _a : (_c.pPr = { TYPE_NAME: 'org_docx4j_wml.PPr' }));
489
660
  linkParents(pPr, this.p);
490
661
  (_b = this.changeTracker) === null || _b === void 0 ? void 0 : _b.recordPPrChange(pPr);
@@ -563,40 +734,387 @@ export class Paragraph {
563
734
  ownInsertions.add(seg.run);
564
735
  return { tracker, ownInsertions };
565
736
  }
566
- /** The text segments (w:t, w:tab, ...) with their offsets. */
737
+ /**
738
+ * The text segments (w:t, w:tab, ...) with their offsets in `text`; `{ objects: true }` adds the
739
+ * pictures and the hidden runs' items.
740
+ */
567
741
  segments(options) {
568
- return segmentsOf(this.p, options);
742
+ return segmentsOf(this.p, { hidden: this.hiddenRuns(), ...options });
743
+ }
744
+ /**
745
+ * True for a paragraph in a text box (`w:txbxContent`), at a `textbox:` address (CR-012 D4). Such a view
746
+ * reads as any paragraph does and is read-only (D5): every write throws `NotSupportedError`.
747
+ */
748
+ get inTextBox() {
749
+ let o = this.p.PARENT;
750
+ for (let guard = 0; o && guard < 256; guard++, o = o.PARENT) {
751
+ if (o.TYPE_NAME === 'org_docx4j_wml.CTTxbxContent')
752
+ return true;
753
+ }
754
+ return false;
755
+ }
756
+ /**
757
+ * Refuses a write to a paragraph in a text box (CR-012 D5): a box is in each branch of its
758
+ * `mc:AlternateContent`, `wps:txbx` in the Choice and `v:textbox` in the Fallback, and nothing maps the
759
+ * one's paragraphs to the other's, so an edit could not reach every branch as CR-010's D2 has mutators do.
760
+ * And a write to a paragraph in a branch of a kept `mc:AlternateContent` at block, row or cell level
761
+ * (CR-013 D4), for the same reason. Internal: the content API's mutators call it first.
762
+ */
763
+ assertWritable() {
764
+ if (this.inTextBox) {
765
+ throw new NotSupportedError('Paragraph', 'edit', 'this paragraph is in a text box, whose paragraphs are read-only (CR-012 D5): '
766
+ + "writes into a text box would have to reach its other branch's box too, which nothing maps yet");
767
+ }
768
+ assertOutsideBranch(this.p, 'Paragraph');
769
+ }
770
+ /**
771
+ * True for a paragraph in the selected branch of a kept `mc:AlternateContent` at block, row or cell
772
+ * level, which readers see as Word does (CR-013 phase 2). Such a view is read-only (D4).
773
+ */
774
+ get inBranch() {
775
+ return inBranch(this.p);
776
+ }
777
+ /** Which of this paragraph's runs are hidden: a run's own `w:vanish`, else its styles' (CR-012 phase 4). */
778
+ hiddenRuns() {
779
+ return this.parentBody.hiddenRuns(this.p);
569
780
  }
570
781
  /**
571
782
  * Replaces the text in [start, end) with `text` and returns the range of the inserted text.
572
783
  * Runs are edited in place: a w:t loses or gains characters, a tab or break inside the range
573
784
  * is removed, a run left empty is removed. Text is inserted into the w:t at `start`,
574
785
  * else into a new run with the formatting of the nearest run.
786
+ *
787
+ * The pictures in the span go with its text: those strictly inside it, and those at an edge `edges`
788
+ * says the span holds (`WHOLE` for a paragraph's whole content), so a span of no text can still
789
+ * delete a picture (CR-002 section 48).
790
+ */
791
+ splice(start, end, text, prefer = 'before', edges, lineBreaks) {
792
+ this.assertWritable();
793
+ // text into a block control showing its placeholder takes the placeholder's place (CR-002 section 63)
794
+ const placeholder = text === '' ? undefined : this.placeholderControl;
795
+ if (placeholder)
796
+ return placeholder.writePlaceholder(text, lineBreaks ? { lineBreaks } : {});
797
+ // every text write into a bound control writes its node (CR-002 section 63, D7), as ContentControl.insertText did
798
+ const bound = this.boundControlsAt(start, end);
799
+ const range = this.spliceWrite(start, end, text, prefer, edges, lineBreaks);
800
+ for (const control of bound)
801
+ control.syncBoundNode();
802
+ return range;
803
+ }
804
+ /**
805
+ * The controls bound by `w:dataBinding` that [start, end) is in: those holding the paragraph, and the run-level ones
806
+ * holding text there.
807
+ */
808
+ boundControlsAt(start, end, ancestorsOnly = false) {
809
+ const out = [];
810
+ const seen = new Set();
811
+ const add = (o) => {
812
+ const tn = o.TYPE_NAME;
813
+ if (tn === undefined || !SDT_VALUE_TYPES.has(tn) || seen.has(o))
814
+ return;
815
+ seen.add(o);
816
+ if (sdtProperty(o.sdtPr, 'dataBinding') === undefined)
817
+ return;
818
+ try {
819
+ out.push(controlViewOf(o, this.parentBody));
820
+ }
821
+ catch {
822
+ // not where its PARENT says: nothing to write
823
+ }
824
+ };
825
+ for (let o = this.p.PARENT; o; o = o.PARENT)
826
+ add(o);
827
+ if (ancestorsOnly || !holdsBoundRunControl(this.p))
828
+ return out;
829
+ for (const seg of this.segments({ objects: true })) {
830
+ if (seg.end < start || seg.start > end)
831
+ continue;
832
+ for (let o = seg.run.PARENT; o && o !== this.p; o = o.PARENT)
833
+ add(o);
834
+ }
835
+ return out;
836
+ }
837
+ /**
838
+ * @internal Text written at the paragraph's End by Office JS's routes to it: `insertText('End')`, its End range at any
839
+ * location, its Content range's End and After, and so a body's or a cell's `insertText('End')` (CR-002 sections 63.24
840
+ * to 63.27; checks 69 and 70). As Word writes it (D12): after everything the paragraph holds, into none of it - a
841
+ * run-level control, a link, a revision, a field, a bookmark's or a comment's markers, a text box - in a run of its
842
+ * own, or the last run extended. The text takes the last item's run properties where that item is a run holding text,
843
+ * so after a hidden run it is hidden and its range reads nothing (D13); otherwise it has none, and with `plain` (the End
844
+ * range's After, D14) none either. Word's proofing marks, `w:proofErr`, are no item (a choice: Word redoes them).
845
+ * Tracked, the run is a `w:ins`, or joins this author's own `w:ins` where one ends the paragraph. A line break makes
846
+ * paragraphs, as `splice` does (CR-002 section 60 E1); text so broken after a hidden run is written visible (a choice,
847
+ * unmeasured).
848
+ */
849
+ writeAtEnd(text, plain = false) {
850
+ var _a, _b;
851
+ var _c;
852
+ this.assertWritable();
853
+ const placeholder = this.placeholderControl;
854
+ if (placeholder)
855
+ return placeholder.writePlaceholder(text);
856
+ const length = this.text.length;
857
+ text = dropExcluded(text);
858
+ if (text === '')
859
+ return new Range(this, length, length);
860
+ let mode = 'paragraphs';
861
+ const lines = this.blockControl ? plainTextLinesOf(this.blockControl) : undefined;
862
+ if (lines)
863
+ mode = lines === 'multi' ? 'multi-line' : 'single-line'; // section 61 item 3, F3
864
+ text = forWriter(text, mode);
865
+ const broken = mode === 'paragraphs' && /[\r\n]/.test(text) ? linesOf(text) : undefined;
866
+ const joined = broken ? broken.join('') : text;
867
+ if (joined === '' && !broken)
868
+ return new Range(this, length, length);
869
+ const bound = this.boundControlsAt(length, length, true);
870
+ const content = ((_a = (_c = this.p).content) !== null && _a !== void 0 ? _a : (_c.content = []));
871
+ let at = content.length;
872
+ while (at > 0 && typeNameOf(content[at - 1]) === 'org_docx4j_wml.ProofErr')
873
+ at--;
874
+ const last = at > 0 ? content[at - 1] : undefined;
875
+ const textRun = (item) => {
876
+ var _a;
877
+ if (!item || typeNameOf(item) !== 'org_docx4j_wml.R')
878
+ return undefined;
879
+ const run = item.value;
880
+ return ((_a = run.content) !== null && _a !== void 0 ? _a : []).some((c) => { var _a; return ((_a = c.name) === null || _a === void 0 ? void 0 : _a.localPart) === 't'; }) ? run : undefined;
881
+ };
882
+ const hidden = this.hiddenRuns();
883
+ // the run whose properties the text takes, and which it may extend: a run holding text, unless `plain`
884
+ let source = plain ? undefined : textRun(last);
885
+ if (source && broken && hidden(source))
886
+ source = undefined;
887
+ const items = officeItems(joined);
888
+ const tracker = this.changeTracker;
889
+ let written;
890
+ const own = tracker && last && typeNameOf(last) === 'org_docx4j_wml.RunIns' && tracker.ownInsertion({ kind: 'ins', value: last.value });
891
+ if (items.length === 0) {
892
+ // nothing but line breaks: only the paragraphs they make
893
+ }
894
+ else if (own) {
895
+ // this author's own insertion ends the paragraph: the text joins it, after its last run, in that run's look
896
+ const value = last.value;
897
+ const holder = (_b = runItemsOf(value)) !== null && _b !== void 0 ? _b : (value.customXmlOrSmartTagOrSdt = []);
898
+ const inner = plain ? undefined : textRun(holder[holder.length - 1]);
899
+ written = this.appendTo(holder, inner, items, value);
900
+ }
901
+ else if (tracker) {
902
+ const ins = tracker.ins([runOf(items, copyRPr(source === null || source === void 0 ? void 0 : source.rPr))]);
903
+ content.splice(at, 0, ins);
904
+ linkParents(ins, this.p);
905
+ written = runsOf(ins.value)[0].value;
906
+ }
907
+ else {
908
+ written = this.appendTo(content, source, items, this.p, at);
909
+ }
910
+ for (const control of bound)
911
+ control.syncBoundNode();
912
+ if (broken) {
913
+ const offsets = [];
914
+ let end = length;
915
+ for (const line of broken.slice(0, -1))
916
+ offsets.push(end += line.length);
917
+ const { first, last: tail } = breakParagraphAt(this, offsets);
918
+ return new Range(first, length, broken[broken.length - 1].length, false, false, tail);
919
+ }
920
+ return written && hidden(written) ? new Range(this, length, length) : new Range(this, length, length + joined.length);
921
+ }
922
+ /**
923
+ * `items` put at `at` in `list` (its end by default): onto the end of `run`, which is in `list` right before `at`,
924
+ * merging into its last `w:t`, or as a new run in its properties where `run` is undefined. Returns the run written.
925
+ */
926
+ appendTo(list, run, items, parent, at = list.length) {
927
+ var _a, _b, _c, _d, _e;
928
+ if (run) {
929
+ const own = ((_a = run.content) !== null && _a !== void 0 ? _a : (run.content = []));
930
+ const tail = own[own.length - 1];
931
+ const head = items[0];
932
+ if (((_b = tail === null || tail === void 0 ? void 0 : tail.name) === null || _b === void 0 ? void 0 : _b.localPart) === 't' && ((_c = head === null || head === void 0 ? void 0 : head.name) === null || _c === void 0 ? void 0 : _c.localPart) === 't') {
933
+ setText(tail.value, ((_d = tail.value.value) !== null && _d !== void 0 ? _d : '') + ((_e = head.value.value) !== null && _e !== void 0 ? _e : ''));
934
+ items = items.slice(1);
935
+ }
936
+ own.push(...items);
937
+ linkParents(items, run);
938
+ return run;
939
+ }
940
+ const added = runOf(items);
941
+ list.splice(at, 0, added);
942
+ linkParents(added, parent);
943
+ return added.value;
944
+ }
945
+ /** `splice`'s write. */
946
+ spliceWrite(start, end, text, prefer, edges, lineBreaks) {
947
+ // Office JS's characters written back (CR-002 section 60): the characters XML 1.0 excludes go (E3), a line feed or
948
+ // carriage return is a paragraph break (E1) unless the writer or the control takes it otherwise, and every
949
+ // character that is an element is written as it (D1)
950
+ text = dropExcluded(text);
951
+ if (text === '')
952
+ return this.spliceText(start, end, text, prefer, edges);
953
+ const from = Math.max(0, Math.min(start, this.text.length));
954
+ const landing = this.landingSegment(from, prefer, end > from);
955
+ const runControl = this.runControlOf(landing);
956
+ let mode = lineBreaks !== null && lineBreaks !== void 0 ? lineBreaks : 'paragraphs';
957
+ if (runControl) {
958
+ text = textForRunControl(runControl, text); // F4 and F3
959
+ }
960
+ else if (lineBreaks === undefined) {
961
+ const lines = this.blockControl ? plainTextLinesOf(this.blockControl) : undefined;
962
+ if (lines)
963
+ mode = lines === 'multi' ? 'multi-line' : 'single-line'; // section 61 item 3, F3
964
+ }
965
+ text = forWriter(text, mode);
966
+ if (mode === 'paragraphs' && /[\r\n]/.test(text))
967
+ return this.spliceLines(start, end, linesOf(text), prefer, edges, landing);
968
+ const range = this.spliceText(start, end, text, prefer, edges);
969
+ this.writeElements(range.start, range.start + text.length);
970
+ return range;
971
+ }
972
+ /**
973
+ * Text of several lines put in place of [start, end) as Word's insertText puts it (CR-002 section 60 E1): the lines'
974
+ * text, then a paragraph break after each line but the last, the paragraphs keeping this one's properties; tracked,
975
+ * each new mark inserted. Refused before anything changes where a paragraph can't be split there.
976
+ */
977
+ spliceLines(start, end, lines, prefer, edges, landing) {
978
+ this.assertBreakable(landing);
979
+ const joined = lines.join('');
980
+ const range = this.spliceText(start, end, joined, prefer, edges);
981
+ const from = range.start;
982
+ this.writeElements(from, from + joined.length);
983
+ const offsets = [];
984
+ let at = from;
985
+ for (const line of lines.slice(0, -1))
986
+ offsets.push(at += line.length);
987
+ const { first, last } = breakParagraphAt(this, offsets);
988
+ return new Range(first, from, lines[lines.length - 1].length, false, false, last);
989
+ }
990
+ /** @internal Refuses, before anything changes, a paragraph break at `at` where none can be made (E1; `Range`'s Replace across paragraphs). */
991
+ assertBreakableAt(at, prefer) {
992
+ this.assertBreakable(this.landingSegment(at, prefer));
993
+ }
994
+ /**
995
+ * The segment text put at `at` goes into, as `spliceText` chooses it: a replacement's, the one holding the first
996
+ * replaced character; else the one containing `at`, else the nearer neighbour.
997
+ */
998
+ landingSegment(at, prefer, replacing = false) {
999
+ const segs = this.segments();
1000
+ if (replacing) {
1001
+ const first = segs.find((s) => s.start <= at && at < s.end);
1002
+ if (first)
1003
+ return first;
1004
+ }
1005
+ const containing = segs.find((s) => s.editable && s.start < at && at < s.end);
1006
+ const ending = segs.find((s) => s.editable && s.end === at);
1007
+ const starting = segs.find((s) => s.editable && s.start === at);
1008
+ return containing !== null && containing !== void 0 ? containing : (prefer === 'after' ? starting !== null && starting !== void 0 ? starting : ending : ending !== null && ending !== void 0 ? ending : starting);
1009
+ }
1010
+ /** The run-level content control a segment's run is in, the innermost, as a view; undefined for none. */
1011
+ runControlOf(seg) {
1012
+ var _a;
1013
+ for (let o = (_a = seg === null || seg === void 0 ? void 0 : seg.run) === null || _a === void 0 ? void 0 : _a.PARENT; o && o !== this.p; o = o.PARENT) {
1014
+ if (o.TYPE_NAME === 'org_docx4j_wml.SdtRun')
1015
+ return controlViewOf(o, this.parentBody);
1016
+ }
1017
+ return undefined;
1018
+ }
1019
+ /** The block-level content control holding this paragraph directly, as a view; undefined for none. */
1020
+ get blockControl() {
1021
+ const content = this.p.PARENT;
1022
+ const sdt = (content === null || content === void 0 ? void 0 : content.TYPE_NAME) === 'org_docx4j_wml.SdtContentBlock' ? content.PARENT : undefined;
1023
+ return (sdt === null || sdt === void 0 ? void 0 : sdt.TYPE_NAME) === 'org_docx4j_wml.SdtBlock' ? controlViewOf(sdt, this.parentBody) : undefined;
1024
+ }
1025
+ /**
1026
+ * Refuses, before anything changes, a paragraph break where the text lands inside something a paragraph can't be
1027
+ * split in: a content control, an equation, a kept mc:AlternateContent (CR-002 section 60 E1, as `splitParagraph`
1028
+ * refuses one). A field's result is split: a complex field's runs across the paragraphs, a `w:fldSimple` made a
1029
+ * complex field first (section 62.2 D1).
1030
+ */
1031
+ assertBreakable(seg) {
1032
+ var _a;
1033
+ if (!seg)
1034
+ return;
1035
+ const refuse = (what) => {
1036
+ throw new NotSupportedError('Paragraph', 'insertText', `a line feed or carriage return is a paragraph break (CR-002 section 60, E1), `
1037
+ + `and this text goes inside ${what}, which can't be cut in two`);
1038
+ };
1039
+ if (seg.alternate || seg.math)
1040
+ refuse('an equation or an mc:AlternateContent');
1041
+ for (let o = seg.run.PARENT; o && o !== this.p; o = o.PARENT) {
1042
+ const tn = (_a = o.TYPE_NAME) !== null && _a !== void 0 ? _a : '';
1043
+ if (!BREAKABLE.has(tn))
1044
+ refuse(`a ${tn.replace('org_docx4j_wml.', '')}`);
1045
+ }
1046
+ }
1047
+ /**
1048
+ * The characters of [from, to) that are elements written as the elements they read as (CR-002 section 60 D1), each
1049
+ * in its run where it stood: a w:t holding one is cut around it. Characters outside the span are left as they are.
1050
+ */
1051
+ writeElements(from, to) {
1052
+ var _a;
1053
+ if (to <= from)
1054
+ return;
1055
+ for (const seg of this.segments().reverse()) {
1056
+ if (!seg.editable || seg.end <= from || seg.start >= to)
1057
+ continue;
1058
+ const value = seg.text;
1059
+ const a = Math.max(0, from - seg.start);
1060
+ const b = Math.min(value.length, to - seg.start);
1061
+ const inside = value.substring(a, b);
1062
+ if (!holdsElementCharacters(inside))
1063
+ continue;
1064
+ const items = [
1065
+ ...(a > 0 ? [textItem(value.substring(0, a))] : []),
1066
+ ...officeItems(inside),
1067
+ ...(b < value.length ? [textItem(value.substring(b))] : []),
1068
+ ];
1069
+ const parent = (_a = seg.item.value.PARENT) !== null && _a !== void 0 ? _a : seg.run;
1070
+ seg.owner.splice(seg.index, 1, ...items);
1071
+ linkParents(items, parent);
1072
+ }
1073
+ }
1074
+ /**
1075
+ * The first run's properties, for text with no run beside it to take its look from; none where that run is hidden,
1076
+ * so that text put into a paragraph whose every run is hidden can be seen (CR-002 section 60: the text was hidden
1077
+ * with it, so read as nothing, and its characters escaped the map).
575
1078
  */
576
- splice(start, end, text, prefer = 'before') {
577
- var _a, _b, _c, _d, _e, _f;
578
- var _g;
1079
+ visibleFirstRunRPr() {
1080
+ var _a;
1081
+ const first = (_a = runsOf(this.p)[0]) === null || _a === void 0 ? void 0 : _a.value;
1082
+ return first && !this.hiddenRuns()(first) ? first.rPr : undefined;
1083
+ }
1084
+ /** `splice` before section 60: the text put in as it is, as one stretch of a w:t's text. */
1085
+ spliceText(start, end, text, prefer = 'before', edges) {
1086
+ var _a, _b, _c, _d, _e;
1087
+ var _f;
579
1088
  const length = this.text.length;
580
1089
  start = Math.max(0, Math.min(start, length));
581
1090
  end = Math.max(start, Math.min(end, length));
582
1091
  const tracker = this.changeTracker;
1092
+ this.assertPlaceable(start, end, tracker === undefined ? false : text.length > 0 ? 'replace' : 'delete');
1093
+ // a run-level control showing its placeholder that the span holds whole goes with it, as Word takes one; tracked,
1094
+ // `deleteText` marks it deleted (CR-002 section 63, D3; check 63 cases 19, 20 and 26)
1095
+ if (!tracker && (end > start || edges))
1096
+ for (const control of this.placeholderControlsIn(start, end, edges))
1097
+ control.takeWhole();
583
1098
  if (tracker) {
584
1099
  // a replacement is the w:del first and the w:ins after it, as Word writes one
585
- const deletions = end > start ? this.deleteText(tracker, start, end) : [];
1100
+ const deletions = end > start || edges ? this.deleteText(tracker, start, end, edges) : [];
586
1101
  if (text.length > 0)
587
1102
  this.insertTracked(tracker, start, text, deletions[deletions.length - 1], prefer);
588
1103
  return new Range(this, start, start + text.length);
589
1104
  }
590
1105
  let segs = this.segments();
591
1106
  let inserted = false;
1107
+ const objects = this.segments({ objects: true }).filter((s) => s.object && objectInSpan(s.start, start, end, edges));
1108
+ // a comment's reference in the span goes with the text, and its comment with it (CR-014 section 12.6)
1109
+ const references = end > start || edges ? this.commentReferencesIn(start, end, edges) : [];
592
1110
  // A replacement that begins inside a revision is a deletion and then the new text typed there, so
593
1111
  // the new text is plain and splits the revision, as typed text is (test/README.md check 21), rather
594
1112
  // than joining the revision it replaced part of.
595
1113
  if (end > start && text.length > 0 && ((_a = segs.find((s) => s.start <= start && start < s.end)) === null || _a === void 0 ? void 0 : _a.revision)) {
596
- this.splice(start, end, '');
597
- return this.splice(start, start, text);
1114
+ this.spliceText(start, end, '', prefer, edges);
1115
+ return this.spliceText(start, start, text);
598
1116
  }
599
- if (end > start) {
1117
+ if (end > start || objects.length > 0 || references.length > 0) {
600
1118
  // Replace: the new text takes the place (and formatting) of the first replaced character, as Word does.
601
1119
  const first = segs.find((s) => s.start <= start && start < s.end);
602
1120
  const toRemove = [];
@@ -619,11 +1137,19 @@ export class Paragraph {
619
1137
  else
620
1138
  toRemove.push(seg);
621
1139
  }
622
- for (const seg of toRemove.reverse()) {
1140
+ for (const seg of [...toRemove.reverse(), ...objects]) {
623
1141
  const i = seg.owner.indexOf(seg.item);
624
1142
  if (i >= 0)
625
1143
  seg.owner.splice(i, 1);
626
1144
  }
1145
+ removeEmptiedAlternates([...toRemove, ...objects]);
1146
+ for (const { run, owner, id } of references) {
1147
+ const i = owner.indexOf(run);
1148
+ if (i >= 0)
1149
+ owner.splice(i, 1);
1150
+ this.queueCommentRemoval(id);
1151
+ }
1152
+ this.releaseRelationships(objects.map((seg) => seg.item));
627
1153
  if (inserted && text.length > 0) {
628
1154
  this.removeEmptyRuns();
629
1155
  return new Range(this, start, start + text.length);
@@ -663,7 +1189,7 @@ export class Paragraph {
663
1189
  const before = [...segs].reverse().find((s) => s.end <= start);
664
1190
  const after = segs.find((s) => s.start >= start);
665
1191
  const neighbour = prefer === 'after' ? after !== null && after !== void 0 ? after : before : before !== null && before !== void 0 ? before : after;
666
- const firstRun = (_e = runsOf(this.p)[0]) === null || _e === void 0 ? void 0 : _e.value.rPr;
1192
+ const firstRun = this.visibleFirstRunRPr();
667
1193
  const rPr = (neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) ? deepCopy(neighbour.run.rPr) : firstRun ? deepCopy(firstRun) : undefined;
668
1194
  const run = runOf([textItem(text)], rPr);
669
1195
  if (neighbour === before && before)
@@ -671,7 +1197,7 @@ export class Paragraph {
671
1197
  else if (after)
672
1198
  after.runOwner.splice(after.runIndex, 0, run);
673
1199
  else {
674
- const content = ((_f = (_g = this.p).content) !== null && _f !== void 0 ? _f : (_g.content = []));
1200
+ const content = ((_e = (_f = this.p).content) !== null && _e !== void 0 ? _e : (_f.content = []));
675
1201
  if (start === 0)
676
1202
  content.unshift(run);
677
1203
  else
@@ -689,8 +1215,10 @@ export class Paragraph {
689
1215
  insertItemsAt(offset, items) {
690
1216
  var _a, _b, _c, _d;
691
1217
  var _e;
1218
+ this.assertWritable();
692
1219
  if (items.length === 0)
693
1220
  return;
1221
+ this.assertPlaceable(offset, offset, false);
694
1222
  const tracker = this.changeTracker;
695
1223
  if (tracker) {
696
1224
  // With tracking on, what goes in is an insertion: its runs in w:ins, in a link, a control or a
@@ -720,18 +1248,46 @@ export class Paragraph {
720
1248
  /** Splits the run at a text offset so that [offset, ...) begins a run; returns nothing when the offset is already a boundary. */
721
1249
  splitAt(offset) {
722
1250
  var _a;
1251
+ this.assertWritable();
1252
+ this.assertPlaceable(offset, offset, false);
723
1253
  const seg = this.segments().find((s) => s.editable && s.start < offset && offset < s.end);
724
1254
  if (!seg)
725
1255
  return;
726
1256
  const at = offset - seg.start;
727
1257
  const t = seg.item.value;
728
1258
  const head = seg.text.substring(0, at);
729
- const tail = seg.text.substring(at);
1259
+ const tail = textItem(seg.text.substring(at));
730
1260
  setText(t, head);
731
- const rest = seg.owner.splice(seg.index + 1);
732
- const second = runOf([textItem(tail), ...rest], seg.run.rPr ? deepCopy(seg.run.rPr) : undefined);
1261
+ seg.owner.splice(seg.index + 1, 0, tail);
1262
+ linkParents(tail, (_a = t.PARENT) !== null && _a !== void 0 ? _a : seg.run);
1263
+ this.splitRunAtItem(seg, seg.owner, seg.index + 1);
1264
+ }
1265
+ /**
1266
+ * Splits `seg`'s run so that the items of `owner` from `index` on begin a run of their own, behind it.
1267
+ * `owner` is the run's content, or the selected branch of a run-level `mc:AlternateContent` in it
1268
+ * (CR-012 section 12.2): a run holds the element whole, so the branch splits with it, as the editor's
1269
+ * ED-009 section 3.4 has it. The first piece keeps the element, its other branches as they were; the
1270
+ * second gets an element of its own, the same branches in the same order with the same `Requires`, the
1271
+ * selected one holding the rest of its items and the others empty, so that a reader taking any branch
1272
+ * takes the same one in both. The run's items after the element go with the second piece.
1273
+ */
1274
+ splitRunAtItem(seg, owner, index) {
1275
+ var _a, _b;
1276
+ const alternate = ((_a = seg.alternate) === null || _a === void 0 ? void 0 : _a.level) === 'run' && owner === seg.alternate.branch ? seg.alternate : undefined;
1277
+ let moved;
1278
+ if (alternate) {
1279
+ const piece = alternatePiece(alternate, owner.splice(index));
1280
+ const at = alternate.owner.indexOf(alternate.element);
1281
+ moved = [piece, ...alternate.owner.splice(at + 1)];
1282
+ }
1283
+ else {
1284
+ moved = owner.splice(index);
1285
+ }
1286
+ if (moved.length === 0)
1287
+ return;
1288
+ const second = runOf(moved, seg.run.rPr ? deepCopy(seg.run.rPr) : undefined);
733
1289
  seg.runOwner.splice(seg.runIndex + 1, 0, second);
734
- linkParents(second, (_a = seg.run.PARENT) !== null && _a !== void 0 ? _a : this.p);
1290
+ linkParents(second, (_b = seg.run.PARENT) !== null && _b !== void 0 ? _b : this.p);
735
1291
  }
736
1292
  // --- lists (CR-002 phase H) ---
737
1293
  /**
@@ -768,14 +1324,17 @@ export class Paragraph {
768
1324
  * unmarshalled (CR-002 section 17).
769
1325
  */
770
1326
  startNewList(options) {
1327
+ this.assertWritable();
771
1328
  return startNewList(this, options);
772
1329
  }
773
1330
  /** Office JS `paragraph.attachToList(listId, level)`: this paragraph joins that list. */
774
1331
  attachToList(listId, level = 0) {
1332
+ this.assertWritable();
775
1333
  return attachToList(this, listId, level);
776
1334
  }
777
1335
  /** Office JS `paragraph.detachFromList()`: this paragraph leaves its list. */
778
1336
  detachFromList() {
1337
+ this.assertWritable();
779
1338
  detachFromList(this);
780
1339
  }
781
1340
  /**
@@ -783,6 +1342,7 @@ export class Paragraph {
783
1342
  * `w:abstractNum` with a `w:startOverride` (extension; `List.restart()` is the verb).
784
1343
  */
785
1344
  restartList() {
1345
+ this.assertWritable();
786
1346
  const list = this.list;
787
1347
  const level = this.listItem.level;
788
1348
  const restarted = list.restart();
@@ -796,6 +1356,7 @@ export class Paragraph {
796
1356
  }
797
1357
  /** Comments the whole paragraph (sugar over `getRange().insertComment`). */
798
1358
  async insertComment(content, options) {
1359
+ this.assertWritable();
799
1360
  return commentApi().insertComment(this.getRange(), content, options);
800
1361
  }
801
1362
  // --- content controls (CR-002 phase E) ---
@@ -806,18 +1367,32 @@ export class Paragraph {
806
1367
  */
807
1368
  insertContentControl(kind) {
808
1369
  var _a;
1370
+ this.assertWritable();
809
1371
  const at = this.index;
810
1372
  if (at < 0)
811
1373
  throw new Docx4JException('This paragraph is not in its container any more');
1374
+ // the paragraph's parent read before the builder makes the control its parent: read after, it was the
1375
+ // control's own content, and the control's PARENT pointed into itself (found in CR-012 phase 4)
1376
+ const parent = (_a = this.p.PARENT) !== null && _a !== void 0 ? _a : this.parentBody.container;
1377
+ // a bound block control holds one paragraph (CR-002 section 61): checked before sdtOf, which takes the paragraph
1378
+ assertControlHolds(this.parentBody, parent, this.container.filter((e) => e !== this.element), [BLOCK_CONTROL], undefined, ['Paragraph', 'insertContentControl']);
1379
+ const empty = this.segments({ objects: true }).length === 0;
812
1380
  const sdt = sdtOf([this.element], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(this.parentBody)), form: 'block' });
813
1381
  this.container.splice(at, 1, sdt);
814
- linkParents(sdt, (_a = this.p.PARENT) !== null && _a !== void 0 ? _a : this.parentBody.container);
1382
+ linkParents(sdt, parent);
815
1383
  declareSdtExtensions(this.parentBody, sdt);
816
1384
  // under tracking the control itself is the insertion, the paragraph what it was (check 30)
817
1385
  const tracker = this.changeTracker;
818
1386
  if (tracker)
819
1387
  markHolderInserted(tracker, sdt, this.container, () => { });
820
- return new ContentControl(sdt, this.container, this.parentBody);
1388
+ const control = new ContentControl(sdt, this.container, this.parentBody);
1389
+ // around an empty paragraph, a control showing Word's default placeholder, as Word's (CR-002 section 63, D8; check 64);
1390
+ // a picture control Word's form, the flag and the placeholder picture (CR-005 section 10, D2; docx4j's check 71 case 8)
1391
+ if (empty && showsPlaceholderWhenMade(kind))
1392
+ control.showPlaceholder();
1393
+ else if (empty && kind === 'Picture')
1394
+ showPicturePlaceholder(control);
1395
+ return control;
821
1396
  }
822
1397
  removeEmptyRuns() {
823
1398
  var _a;
@@ -829,6 +1404,16 @@ export class Paragraph {
829
1404
  if (!v.content || v.content.length === 0)
830
1405
  items.splice(i, 1);
831
1406
  }
1407
+ else if (isAlternateContent(el)) {
1408
+ // among the runs: a selected branch an edit has emptied takes the element, every branch with
1409
+ // it, as a deletion of all of a run-level element's text does (CR-012 section 12.2 item 4)
1410
+ const branch = selectedBranchOf(el.value);
1411
+ if (!branch || branch.length === 0)
1412
+ continue;
1413
+ prune(branch);
1414
+ if (branch.length === 0)
1415
+ items.splice(i, 1);
1416
+ }
832
1417
  else if (v.TYPE_NAME !== 'org_docx4j_wml.P') {
833
1418
  const nested = runItemsOf(v);
834
1419
  if (nested) {
@@ -847,15 +1432,29 @@ export class Paragraph {
847
1432
  * The text in [start, end) becomes a deletion: the runs holding it are isolated, their `w:t`
848
1433
  * turned into `w:delText`, and each run of consecutive ones moved into one `w:del`. Text this
849
1434
  * author had inserted is simply removed, as Word does, rather than nested in a `w:del`.
1435
+ * The pictures in the span (`splice`'s `edges`) are deleted with the text, each in a run of its own
1436
+ * where its run held more, as `InlinePicture.delete` gives it one (CR-002 section 48). An equation is deleted where
1437
+ * it stands, in Word's form, a `w:del` in each math run and each structure's `m:ctrlPr` (`markEquationDeleted`;
1438
+ * section 62.2 D2), the runs either side in `w:del`s of their own; one holding a revision of its own goes into the
1439
+ * `w:del` whole, as every equation did before.
850
1440
  * Returns the `w:del` elements it made, in document order.
851
1441
  */
852
- deleteText(tracker, start, end) {
853
- var _a, _b, _c, _d, _e;
854
- const targets = this.isolate(start, end);
1442
+ deleteText(tracker, start, end, edges) {
1443
+ var _a, _b, _c, _d, _e, _f, _g;
1444
+ // a run-level control showing its placeholder that the span holds whole is marked deleted whole, in Word's markers,
1445
+ // its placeholder gone unmarked (CR-002 section 63, D3; check 63's run 2, cases 45, 46 and 52)
1446
+ for (const control of this.placeholderControlsIn(start, end, edges))
1447
+ control.takeWhole();
1448
+ const targets = this.isolate(start, end, edges);
855
1449
  for (const seg of targets)
856
1450
  tracker.assertEditable(seg.revision);
857
1451
  const list = [];
1452
+ const equations = [];
858
1453
  for (const seg of targets) {
1454
+ if (seg.math && !tracker.ownInsertion(seg.revision) && equationRevisionIn(seg.item.value) === undefined) {
1455
+ equations.push(seg.item.value);
1456
+ continue;
1457
+ }
859
1458
  const run = seg.runOwner[seg.runIndex];
860
1459
  if (list.length > 0 && list[list.length - 1].run === run)
861
1460
  continue;
@@ -864,6 +1463,22 @@ export class Paragraph {
864
1463
  target.revision = seg.revision;
865
1464
  list.push(target);
866
1465
  }
1466
+ // a comment's reference in the span goes into the w:del with the text, where it stands among the runs, as Word
1467
+ // puts it (check 54 case 28; CR-014 section 12.6); accepting the deletion then takes the comment
1468
+ const references = this.commentReferencesIn(start, end, edges);
1469
+ if (references.length > 0) {
1470
+ const order = runOrderOf(this.p);
1471
+ const revisions = new Map(targets.filter((seg) => seg.revision).map((seg) => [seg.runOwner, seg.revision]));
1472
+ for (const { run, owner } of references) {
1473
+ const revision = (_a = revisions.get(owner)) !== null && _a !== void 0 ? _a : revisionHolding(run);
1474
+ const target = { run, owner, own: tracker.ownInsertion(revision) };
1475
+ if (revision)
1476
+ target.revision = revision;
1477
+ const key = (_b = order.get(run)) !== null && _b !== void 0 ? _b : Number.NaN;
1478
+ const at = list.findIndex((t) => { var _a; return ((_a = order.get(t.run)) !== null && _a !== void 0 ? _a : Number.NaN) > key; });
1479
+ list.splice(at < 0 ? list.length : at, 0, target);
1480
+ }
1481
+ }
867
1482
  // groups of consecutive runs in the same array, so that one w:del holds them all
868
1483
  const groups = [];
869
1484
  for (const t of list) {
@@ -875,6 +1490,8 @@ export class Paragraph {
875
1490
  else
876
1491
  groups.push([t]);
877
1492
  }
1493
+ for (const equation of equations)
1494
+ markEquationDeleted(tracker, equation);
878
1495
  const made = [];
879
1496
  for (let g = groups.length - 1; g >= 0; g--) {
880
1497
  const group = groups[g];
@@ -882,7 +1499,12 @@ export class Paragraph {
882
1499
  const at = owner.indexOf(group[0].run);
883
1500
  const runs = owner.splice(at, group.length);
884
1501
  if (group[0].own) {
885
- // taking back our own insertion: the runs go, and an emptied w:ins with them
1502
+ // taking back our own insertion: the runs go, and an emptied w:ins with them; a picture's
1503
+ // image waits for the save, as `InlinePicture.delete` leaves it (section 47); a comment whose
1504
+ // reference goes with them goes too
1505
+ this.releaseRelationships(runs);
1506
+ for (const id of commentReferenceIds(runs))
1507
+ this.queueCommentRemoval(id);
886
1508
  const revision = group[0].revision;
887
1509
  if (revision && revision.items.length === 0) {
888
1510
  const i = revision.owner.indexOf(revision.element);
@@ -895,15 +1517,60 @@ export class Paragraph {
895
1517
  toDeletedText(run.value);
896
1518
  const del = tracker.del(runs);
897
1519
  owner.splice(at, 0, del);
898
- linkParents(del, ((_c = (_b = (_a = group[0].revision) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : group[0].run.value.PARENT) !== null && _c !== void 0 ? _c : this.p));
1520
+ linkParents(del, ((_e = (_d = (_c = group[0].revision) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : group[0].run.value.PARENT) !== null && _e !== void 0 ? _e : this.p));
899
1521
  const revision = group[0].revision;
900
- const anchor = { owner, element: del, parent: ((_e = (_d = revision === null || revision === void 0 ? void 0 : revision.value) !== null && _d !== void 0 ? _d : del.value.PARENT) !== null && _e !== void 0 ? _e : this.p) };
1522
+ const anchor = { owner, element: del, parent: ((_g = (_f = revision === null || revision === void 0 ? void 0 : revision.value) !== null && _f !== void 0 ? _f : del.value.PARENT) !== null && _g !== void 0 ? _g : this.p) };
901
1523
  if (revision && revision.items === owner)
902
1524
  anchor.revision = revision;
903
1525
  made.unshift(anchor);
904
1526
  }
905
1527
  return made;
906
1528
  }
1529
+ /**
1530
+ * The run-level controls showing their placeholder that [start, end) holds whole: where the walk passes one strictly
1531
+ * inside the span, or at an edge `edges` says it holds. A placeholder is no text, so such a control has no width
1532
+ * (CR-002 section 63, D3).
1533
+ */
1534
+ placeholderControlsIn(start, end, edges) {
1535
+ const found = [];
1536
+ this.segments({ visit: (item, at) => {
1537
+ var _a;
1538
+ if (typeNameOf(item) !== 'org_docx4j_wml.SdtRun' || !objectInSpan(at, start, end, edges))
1539
+ return;
1540
+ const sdtPr = item.value.sdtPr;
1541
+ const flag = sdtProperty(sdtPr, 'showingPlcHdr');
1542
+ if (flag !== undefined && ((_a = flag.value) === null || _a === void 0 ? void 0 : _a.val) !== false)
1543
+ found.push(item.value);
1544
+ } });
1545
+ return found.map((sdt) => controlViewOf(sdt, this.parentBody));
1546
+ }
1547
+ /**
1548
+ * The runs of comments' references in [start, end): each a run holding nothing but a reference, where the walk
1549
+ * passes it, strictly inside the span or at an edge `edges` says it holds. A reference has no text, so a
1550
+ * deletion's segments never held it, and a comment whose reference a deletion took stayed. Word's deletion takes
1551
+ * it with the text (check 54 cases 27 and 28; CR-014 section 12.6).
1552
+ */
1553
+ commentReferencesIn(start, end, edges) {
1554
+ const out = [];
1555
+ this.segments({ visit: (item, at, _owner, run) => {
1556
+ var _a, _b;
1557
+ if (((_a = item.name) === null || _a === void 0 ? void 0 : _a.localPart) !== 'commentReference' || !run || !objectInSpan(at, start, end, edges))
1558
+ return;
1559
+ if (out.some((r) => r.run === run.element))
1560
+ return;
1561
+ if (!((_b = run.element.value.content) !== null && _b !== void 0 ? _b : []).every((c) => { var _a; return ((_a = c.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentReference'; }))
1562
+ return;
1563
+ out.push({ run: run.element, owner: run.owner, id: Number(item.value.id) });
1564
+ } });
1565
+ return out;
1566
+ }
1567
+ /** A comment queued to go when the package is saved, its markers, replies and entries with it, as a resolution queues one. */
1568
+ queueCommentRemoval(id) {
1569
+ var _a, _b;
1570
+ if (!Number.isInteger(id))
1571
+ return;
1572
+ (_b = (_a = this.parentBody.package_) === null || _a === void 0 ? void 0 : _a.pendingCommentRemovals) === null || _b === void 0 ? void 0 : _b.add(id);
1573
+ }
907
1574
  /**
908
1575
  * Splits the revision that holds the text at `at` into two - the second half a copy of its
909
1576
  * attributes under a new `w:id` - and puts `item` between them. Word splits a revision so when
@@ -993,7 +1660,7 @@ export class Paragraph {
993
1660
  * of a replacement, which the insertion must follow.
994
1661
  */
995
1662
  insertTracked(tracker, at, text, anchor, prefer = 'before') {
996
- var _a, _b, _c;
1663
+ var _a, _b;
997
1664
  if (anchor) {
998
1665
  const rPr = copyRPr((_a = runsOf(anchor.element.value, { view: 'original' })[0]) === null || _a === void 0 ? void 0 : _a.value.rPr);
999
1666
  const ins = tracker.ins([runOf([textItem(text)], rPr)]);
@@ -1029,7 +1696,7 @@ export class Paragraph {
1029
1696
  }
1030
1697
  const neighbour = prefer === 'after' ? after !== null && after !== void 0 ? after : before : before !== null && before !== void 0 ? before : after;
1031
1698
  tracker.assertEditable(neighbour === null || neighbour === void 0 ? void 0 : neighbour.revision);
1032
- const rPr = copyRPr((_b = neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) !== null && _b !== void 0 ? _b : (_c = runsOf(this.p)[0]) === null || _c === void 0 ? void 0 : _c.value.rPr);
1699
+ const rPr = copyRPr((_b = neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) !== null && _b !== void 0 ? _b : this.visibleFirstRunRPr());
1033
1700
  const ins = tracker.ins([runOf([textItem(text)], rPr)]);
1034
1701
  this.placeInsertion(at, [ins], before, after, () => tracker.nextId(), prefer);
1035
1702
  }
@@ -1069,32 +1736,42 @@ export class Paragraph {
1069
1736
  linkParents(items, this.p);
1070
1737
  }
1071
1738
  }
1739
+ /**
1740
+ * Splits runs so that every run holding text in [start, end) holds nothing else (internal: what a
1741
+ * `Range.font` write formats). A kept `mc:AlternateContent`'s branch splits with its run (section 12.2).
1742
+ */
1743
+ splitRunsAround(start, end) {
1744
+ this.isolate(start, end);
1745
+ }
1072
1746
  /**
1073
1747
  * Splits runs so that every run holding text in [start, end) holds nothing else, and returns
1074
1748
  * the segments inside the span. `splitAt` does the `w:t` boundaries; this also moves the items
1075
1749
  * of a run that straddles a boundary (a tab, a break, a drawing) out into runs of their own.
1750
+ * The pictures in the span (`splice`'s `edges`) are among the segments returned, and a run
1751
+ * holding one holds nothing outside the span either.
1076
1752
  */
1077
- isolate(start, end) {
1753
+ isolate(start, end, edges) {
1078
1754
  this.splitAt(start);
1079
1755
  this.splitAt(end);
1756
+ const inSpan = (s) => s.object ? objectInSpan(s.start, start, end, edges) : s.start >= start && s.end <= end && s.text.length > 0;
1080
1757
  for (let guard = 0; guard < 10000; guard++) {
1081
- const inside = this.segments().filter((s) => s.start >= start && s.end <= end && s.text.length > 0);
1758
+ const inside = this.segments({ objects: true }).filter(inSpan);
1082
1759
  let split = false;
1083
1760
  const seen = new Set();
1084
1761
  for (const seg of inside) {
1085
1762
  if (seen.has(seg.run))
1086
1763
  continue;
1087
1764
  seen.add(seg.run);
1765
+ if (seg.math)
1766
+ continue; // in no run: taken whole or refused
1088
1767
  const ofRun = inside.filter((s) => s.run === seg.run);
1089
1768
  const first = ofRun[0];
1090
1769
  const last = ofRun[ofRun.length - 1];
1091
- if (first.index > 0) {
1092
- this.splitRunBefore(first);
1770
+ if (this.splitRunBefore(first)) {
1093
1771
  split = true;
1094
1772
  break;
1095
1773
  }
1096
- if (last.index < last.owner.length - 1) {
1097
- this.splitRunAfter(last);
1774
+ if (this.splitRunAfter(last)) {
1098
1775
  split = true;
1099
1776
  break;
1100
1777
  }
@@ -1102,25 +1779,60 @@ export class Paragraph {
1102
1779
  if (!split)
1103
1780
  return inside;
1104
1781
  }
1105
- return this.segments().filter((s) => s.start >= start && s.end <= end && s.text.length > 0);
1782
+ return this.segments({ objects: true }).filter(inSpan);
1783
+ }
1784
+ /**
1785
+ * Refuses an edit that would cut an equation (CR-012 phase 4). Its text is one segment, Word's drawing of
1786
+ * its math runs (`mathTextOf`), so an edit may take it whole but not part of it, nor insert into it. Under change
1787
+ * tracking a deletion takes it whole in Word's form (`deleteText`, CR-002 section 62.2 D2, check 57), and a
1788
+ * replacement doesn't take it at all: where Word puts the new text beside a deleted equation isn't measured. Since
1789
+ * phase 4 every other offset is a place in the runs: `text` is the segments' text, where before it was the objects
1790
+ * package's `textOf` and section 12.4 refused an offset past a kept `mc:AlternateContent` among the runs.
1791
+ */
1792
+ assertPlaceable(start, end, tracked) {
1793
+ for (const seg of this.segments()) {
1794
+ if (!seg.math)
1795
+ continue;
1796
+ const cuts = (seg.start < start && start < seg.end) || (seg.start < end && end < seg.end);
1797
+ const takes = start < seg.end && seg.start < end;
1798
+ if (!cuts && !(tracked === 'replace' && takes))
1799
+ continue;
1800
+ throw new NotSupportedError('Paragraph', 'edit', `[${start}, ${end}) ${cuts ? 'cuts' : 'takes'} the equation at [${seg.start}, ${seg.end})`
1801
+ + (cuts ? ': an edit takes an equation whole or not at all (CR-012 phase 4)'
1802
+ : ' under change tracking, with text in its place: a tracked replacement of an equation is not measured (CR-002 section 62.2)'));
1803
+ }
1106
1804
  }
1107
- /** Moves the items before `seg` into a run of their own, in front of `seg`'s run. */
1805
+ /** Moves the items before `seg` into a run of their own, in front of the rest; false where there are none. */
1108
1806
  splitRunBefore(seg) {
1109
1807
  var _a;
1110
- const head = seg.owner.splice(0, seg.index);
1111
- const first = runOf(head, copyRPr(seg.run.rPr));
1112
- seg.runOwner.splice(seg.runIndex, 0, first);
1113
- linkParents(first, (_a = seg.run.PARENT) !== null && _a !== void 0 ? _a : this.p);
1808
+ if (seg.index > 0) {
1809
+ this.splitRunAtItem(seg, seg.owner, seg.index);
1810
+ return true;
1811
+ }
1812
+ const alternate = ((_a = seg.alternate) === null || _a === void 0 ? void 0 : _a.level) === 'run' && seg.owner === seg.alternate.branch ? seg.alternate : undefined;
1813
+ if (!alternate)
1814
+ return false;
1815
+ const at = alternate.owner.indexOf(alternate.element);
1816
+ if (at <= 0)
1817
+ return false;
1818
+ this.splitRunAtItem(seg, alternate.owner, at);
1819
+ return true;
1114
1820
  }
1115
- /** Moves the items after `seg` into a run of their own, behind `seg`'s run. */
1821
+ /** Moves the items after `seg` into a run of their own, behind it; false where there are none. */
1116
1822
  splitRunAfter(seg) {
1117
1823
  var _a;
1118
- const tail = seg.owner.splice(seg.index + 1);
1119
- if (tail.length === 0)
1120
- return;
1121
- const rest = runOf(tail, copyRPr(seg.run.rPr));
1122
- seg.runOwner.splice(seg.runIndex + 1, 0, rest);
1123
- linkParents(rest, (_a = seg.run.PARENT) !== null && _a !== void 0 ? _a : this.p);
1824
+ if (seg.index < seg.owner.length - 1) {
1825
+ this.splitRunAtItem(seg, seg.owner, seg.index + 1);
1826
+ return true;
1827
+ }
1828
+ const alternate = ((_a = seg.alternate) === null || _a === void 0 ? void 0 : _a.level) === 'run' && seg.owner === seg.alternate.branch ? seg.alternate : undefined;
1829
+ if (!alternate)
1830
+ return false;
1831
+ const at = alternate.owner.indexOf(alternate.element);
1832
+ if (at < 0 || at >= alternate.owner.length - 1)
1833
+ return false;
1834
+ this.splitRunAtItem(seg, alternate.owner, at + 1);
1835
+ return true;
1124
1836
  }
1125
1837
  }
1126
1838
  export { W_NS };
@@ -1172,3 +1884,127 @@ function setText(t, value) {
1172
1884
  else
1173
1885
  delete t.space;
1174
1886
  }
1887
+ /**
1888
+ * The element a run-level `mc:AlternateContent`'s later piece is written as (CR-012 section 12.2; the
1889
+ * editor's ED-009 section 3.4, P2): its branches in order with their `Requires`, the selected one holding
1890
+ * `items` and every other one empty.
1891
+ */
1892
+ function alternatePiece(alternate, items) {
1893
+ const ac = alternate.element.value;
1894
+ const emptied = (branch) => {
1895
+ if (branch === undefined)
1896
+ return undefined;
1897
+ const copy = {};
1898
+ for (const [key, value] of Object.entries(branch))
1899
+ if (key !== 'any' && key !== 'PARENT')
1900
+ copy[key] = value;
1901
+ copy.any = branch.any === alternate.branch ? items : [];
1902
+ return copy;
1903
+ };
1904
+ const value = {};
1905
+ for (const [key, v] of Object.entries(ac)) {
1906
+ if (key === 'PARENT')
1907
+ continue;
1908
+ if (key === 'choice')
1909
+ value.choice = v.map(emptied);
1910
+ else if (key === 'fallback')
1911
+ value.fallback = emptied(v);
1912
+ else
1913
+ value[key] = v;
1914
+ }
1915
+ return { name: alternate.element.name, value };
1916
+ }
1917
+ /**
1918
+ * After an untracked deletion: a run-level `mc:AlternateContent` whose selected branch the deletion
1919
+ * emptied goes, every branch with it (CR-012 section 12.2 item 4; ED-009 section 3.5). A branch that
1920
+ * was empty before is left as it is.
1921
+ */
1922
+ function removeEmptiedAlternates(removed) {
1923
+ const seen = new Set();
1924
+ for (const seg of removed) {
1925
+ const alternate = seg.alternate;
1926
+ if ((alternate === null || alternate === void 0 ? void 0 : alternate.level) !== 'run' || seen.has(alternate.element))
1927
+ continue;
1928
+ seen.add(alternate.element);
1929
+ if (alternate.branch.length > 0)
1930
+ continue;
1931
+ const i = alternate.owner.indexOf(alternate.element);
1932
+ if (i >= 0)
1933
+ alternate.owner.splice(i, 1);
1934
+ }
1935
+ }
1936
+ /** Block holders a cell's paragraph may stand in: a block content control, its content, custom XML. */
1937
+ const BLOCK_HOLDERS = new Set(['org_docx4j_wml.SdtBlock', 'org_docx4j_wml.SdtContentBlock', 'org_docx4j_wml.CTSdtContentBlock', 'org_docx4j_wml.CTCustomXmlBlock']);
1938
+ /** True for the last paragraph of a table cell, through the block holders between them. */
1939
+ function lastInCell(p) {
1940
+ var _a;
1941
+ let holder = p.PARENT;
1942
+ for (let guard = 0; holder && guard < 16 && BLOCK_HOLDERS.has((_a = holder.TYPE_NAME) !== null && _a !== void 0 ? _a : ''); guard++) {
1943
+ holder = holder.PARENT;
1944
+ }
1945
+ if ((holder === null || holder === void 0 ? void 0 : holder.TYPE_NAME) !== 'org_docx4j_wml.Tc')
1946
+ return false;
1947
+ let last;
1948
+ const visit = (items) => {
1949
+ for (const el of items !== null && items !== void 0 ? items : []) {
1950
+ const tn = typeNameOf(el);
1951
+ if (tn === 'org_docx4j_wml.P')
1952
+ last = el.value;
1953
+ else if (tn !== 'org_docx4j_wml.Tbl' && typeof el.value === 'object' && el.value !== null) {
1954
+ const v = el.value;
1955
+ visit(v.sdtContent ? v.sdtContent.content : v.content);
1956
+ }
1957
+ }
1958
+ };
1959
+ visit(holder.content);
1960
+ return last === p;
1961
+ }
1962
+ /** Every element among a paragraph's runs, in document order: where a deletion places a comment's reference among its runs. */
1963
+ function runOrderOf(p) {
1964
+ const order = new Map();
1965
+ const visit = (items) => {
1966
+ for (const el of items !== null && items !== void 0 ? items : []) {
1967
+ order.set(el, order.size);
1968
+ const v = el.value;
1969
+ if (typeof v === 'object' && v !== null && typeNameOf(el) !== 'org_docx4j_wml.R')
1970
+ visit(runItemsOf(v));
1971
+ }
1972
+ };
1973
+ visit(runItemsOf(p));
1974
+ return order;
1975
+ }
1976
+ /** The `w:ins` (or other revision) a run stands directly in, read from its `PARENT`, if any. */
1977
+ function revisionHolding(run) {
1978
+ const holder = run.value.PARENT;
1979
+ const items = holder === null || holder === void 0 ? void 0 : holder.customXmlOrSmartTagOrSdt;
1980
+ const outer = (holder === null || holder === void 0 ? void 0 : holder.PARENT) ? runItemsOf(holder.PARENT) : undefined;
1981
+ const element = outer === null || outer === void 0 ? void 0 : outer.find((e) => e.value === holder);
1982
+ const kind = element ? revisionKindOf(element) : undefined;
1983
+ if (!holder || !items || !outer || !element || !kind)
1984
+ return undefined;
1985
+ return { kind, element, value: holder, items, owner: outer };
1986
+ }
1987
+ /** The ids of the comment references among runs. */
1988
+ function commentReferenceIds(runs) {
1989
+ return runs.flatMap((run) => {
1990
+ var _a;
1991
+ return ((_a = run.value.content) !== null && _a !== void 0 ? _a : [])
1992
+ .filter((c) => { var _a; return ((_a = c.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'commentReference'; }).map((c) => Number(c.value.id));
1993
+ });
1994
+ }
1995
+ /** The `w:sdt` value types, block, run, row and cell. */
1996
+ const SDT_VALUE_TYPES = new Set(['org_docx4j_wml.SdtBlock', 'org_docx4j_wml.SdtRun', 'org_docx4j_wml.CTSdtRow', 'org_docx4j_wml.CTSdtCell']);
1997
+ /** Whether a paragraph holds a run-level control bound by `w:dataBinding`, at any depth among its runs. */
1998
+ function holdsBoundRunControl(holder) {
1999
+ var _a;
2000
+ for (const item of (_a = runItemsOf(holder)) !== null && _a !== void 0 ? _a : []) {
2001
+ const value = item.value;
2002
+ if (!value || typeof value !== 'object')
2003
+ continue;
2004
+ if (value.TYPE_NAME === 'org_docx4j_wml.SdtRun' && sdtProperty(value.sdtPr, 'dataBinding') !== undefined)
2005
+ return true;
2006
+ if (value.TYPE_NAME !== 'org_docx4j_wml.R' && holdsBoundRunControl(value))
2007
+ return true;
2008
+ }
2009
+ return false;
2010
+ }