@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,20 +1,31 @@
1
1
  import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
2
2
  import * as w15el from '@docx4j/generated-objects-ts/el/org_docx4j_w15';
3
3
  import { Docx4JException } from '../../opc/exceptions.mjs';
4
+ import { NotSupportedError } from '../../office-js/errors.mjs';
5
+ import { GeneralException, InvalidArgument } from './generalException.mjs';
4
6
  import { marshalString, deepCopy } from '@docx4j/generated-objects-ts';
5
7
  // CR-002 phase E: the mapping and the typed kinds live in src/model/customxml/; they import
6
8
  // nothing from here at run time (types only), so this direction is the only one.
7
9
  import { XmlMapping } from '../customxml/XmlMapping.mjs';
8
10
  import { CheckboxContentControl, DatePickerContentControl, ListContentControl, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKBOX_FONT, } from '../customxml/kinds.mjs';
9
- import { runsForValue, updateFromControl, PLACEHOLDER_TEXT, PLACEHOLDER_STYLE } from '../customxml/bindings.mjs';
10
- import { declareIgnorable } from './tracking.mjs';
11
- import { sdtProperty, sdtKindOf, t as textItem } from '@docx4j/generated-objects-ts/builders/wml';
12
- import { typeNameOf, childrenOf, textOf, runItemsOf, segmentsOf, linkParents, runOf, SDT_TYPES } from './tree.mjs';
11
+ import { updateFromControl, holdsFlatOpc, placeholderRun, PLACEHOLDER_STYLE } from '../customxml/bindings.mjs';
12
+ import { dropExcluded, forWriter, officeItems } from './officeText.mjs';
13
+ import { declareIgnorable, markHolderDeleted } from './tracking.mjs';
14
+ import { placeholderEntryOf, namePlaceholderEntry, newEntryName, entryContentOf, defaultEntryFor, DEFAULT_ENTRIES } from './placeholders.mjs';
15
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
16
+ import { commentReferencesIn } from './TrackedChange.mjs';
17
+ import { wrappingCellOf, paragraphEndOf, endsWithParagraph, endWithParagraph } from './tableRules.mjs';
18
+ import { sdtProperty, sdtKindOf } from '@docx4j/generated-objects-ts/builders/wml';
19
+ import { typeNameOf, childrenOf, runItemsOf, runsOf, linkParents, runOf, SDT_TYPES, ALTERNATE_CONTENT, BLOCK_LEVEL_TYPES, branchListsOf, inBranch, assertOutsideBranch } from './tree.mjs';
13
20
  import { Range } from './Range.mjs';
14
21
  import { Table } from './Table.mjs';
15
22
  /** The namespaces of the Word 2010 and 2012 content-control properties. */
16
23
  export const W14_NS = 'http://schemas.microsoft.com/office/word/2010/wordml';
17
24
  export const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
25
+ /** The markers of a control inserted or deleted under tracking (`markHolderInserted`, `markHolderDeleted`). */
26
+ const TRACKED_HOLDER_MARKER = /^customXml(Ins|Del)Range(Start|End)$/;
27
+ /** What a block, row or cell control's content ends with, for its range's end: a paragraph, a table or a row. */
28
+ const BLOCK_ITEMS = new Set(['org_docx4j_wml.P', 'org_docx4j_wml.Tbl', 'org_docx4j_wml.Tr', 'org_docx4j_wml.Tc']);
18
29
  /** A subset of Office JS `Word.ContentControl` over a `w:sdt`. A view, as Paragraph is. */
19
30
  export class ContentControl {
20
31
  constructor(
@@ -54,7 +65,10 @@ export class ContentControl {
54
65
  }
55
66
  /** The type Word reports (w:sdtPr's kind element); RichText when none is given, as Word does. */
56
67
  get type() {
57
- return sdtKindOf(this.sdt.sdtPr);
68
+ const kind = sdtKindOf(this.sdt.sdtPr);
69
+ // Word reads a block control bound by w:dataBinding, untyped or w:richText, as single-line plain text, and saves it
70
+ // so (checks 35 and 56; CR-002 section 61 H4); the file is left as it is
71
+ return kind === 'RichText' && isBoundBlockControl(this) ? 'PlainText' : kind;
58
72
  }
59
73
  /** w:tag. */
60
74
  get tag() {
@@ -77,10 +91,20 @@ export class ContentControl {
77
91
  const val = (_c = item === null || item === void 0 ? void 0 : item.value) === null || _c === void 0 ? void 0 : _c.val;
78
92
  return typeof val === 'number' ? val : 0;
79
93
  }
80
- /** The control's text, a line per paragraph (docx4j TextUtils). */
94
+ /**
95
+ * The control's text as Office JS gives it (CR-012 phase 4): a run-level control's span of
96
+ * `Paragraph.text`; a block-level one's content as `Body.text` reads it, its paragraphs a mark `\r` apart
97
+ * (check 51); a row control's cells a `\t` apart and a cell control's paragraphs `\r` apart (check 53).
98
+ */
81
99
  get text() {
82
- var _a;
83
- return textOf((_a = this.sdt.sdtContent) !== null && _a !== void 0 ? _a : {});
100
+ if (this.form === 'Run') {
101
+ const span = this.runSpan();
102
+ return span ? span.paragraph.text.substring(span.start, span.end) : '';
103
+ }
104
+ // a row control's cells a \t apart (check 53: ROW15A\tROW15B), a cell control's paragraphs a \r apart; a
105
+ // control's content ending with a row (a row control, or a table last) leaves the row's end out (check 53)
106
+ const text = this.body().text;
107
+ return text.endsWith('\r\n') ? text.slice(0, -2) : text;
84
108
  }
85
109
  /** Every paragraph in the control; for a run-level control, the paragraph it is in. */
86
110
  get paragraphs() {
@@ -106,6 +130,14 @@ export class ContentControl {
106
130
  collectControls(this.content, this.parentBody, out);
107
131
  return out;
108
132
  }
133
+ /**
134
+ * Office JS `getContentControls` (WordApi 1.5): the content controls `contentControls` lists, those of `options.types`
135
+ * where given. Word's `contentControls` lists rich-text controls only; this package's lists every kind, a recorded
136
+ * departure (CR-002 section 60 G1, check 55b), so this is how add-in code that asks for plain-text controls finds them.
137
+ */
138
+ getContentControls(options) {
139
+ return controlsOfTypes(this.contentControls, options);
140
+ }
109
141
  /**
110
142
  * Text at the start, the end, or replacing the control's text. On a **bound** control the value
111
143
  * is written through to the custom XML node as well when the mapping resolves (CR-002 phase E):
@@ -114,10 +146,296 @@ export class ContentControl {
114
146
  * write is left to `pkg.customXmlParts.updateFromContentControls()`.
115
147
  */
116
148
  insertText(text, location) {
149
+ this.assertWritable();
150
+ // a control showing its placeholder takes the text in the placeholder's place, wherever it is put (check 58 cases 20
151
+ // to 22, CR-012 section 18; CR-002 section 63)
152
+ if (this.showsPlaceholderContent)
153
+ return this.writePlaceholder(text);
117
154
  const range = this.insertTextInto(text, location);
118
155
  this.syncBoundNode();
119
156
  return range;
120
157
  }
158
+ // --- CR-002 section 63: writing into a control showing its placeholder ---
159
+ /**
160
+ * The glossary entry the control's `w:placeholder` names, which makes it Word's form; undefined where it names none,
161
+ * the bare form, which has no entry to show once emptied, nor for `placeholderText` to read (CR-002 section 63; an
162
+ * extension). Writes are the same in both forms (check 63's run 2).
163
+ */
164
+ get placeholderEntry() {
165
+ return placeholderEntryOf(this.sdt.sdtPr);
166
+ }
167
+ /** @internal True for a block or run-level control showing its placeholder, which a text write replaces (section 63). */
168
+ get showsPlaceholderContent() {
169
+ return this.isShowingPlaceholder && (this.form === 'Block' || this.form === 'Run');
170
+ }
171
+ /** The package's glossary entries, where the control is in a package. */
172
+ get glossary() {
173
+ var _a;
174
+ return (_a = this.parentBody.package_) === null || _a === void 0 ? void 0 : _a.glossaryEntries;
175
+ }
176
+ /**
177
+ * @internal Text written into the content of this control while it shows its placeholder, by any route: its own
178
+ * `insertText`, its 'Content' range's, a block control's paragraph's (CR-002 section 63, D1, D2 (b), D5, D11). The
179
+ * placeholder is no content, in either form (checks 64 and 67, check 63's run 2): its runs go, untracked, the text takes
180
+ * their place in one paragraph, and the flag is cleared; under tracking only the insertion is recorded. The text takes
181
+ * the control's own run properties (`w:sdtPr/w:rPr`) and nothing of the placeholder's (check 68, cases 1 to 20).
182
+ */
183
+ writePlaceholder(text, options = {}) {
184
+ this.assertWritable();
185
+ const form = this.form;
186
+ // refused before anything changes: a line break into a run-level control (CR-002 section 60 F4)
187
+ if (form === 'Run')
188
+ textForRunControl(this, dropExcluded(text));
189
+ this.takePlaceholderRuns(form);
190
+ this.isShowingPlaceholder = false;
191
+ let range;
192
+ if (form === 'Run') {
193
+ const paragraph = this.parentParagraph();
194
+ if (!paragraph)
195
+ throw new Docx4JException('This content control is not in a paragraph');
196
+ range = this.insertIntoEmpty(paragraph, text);
197
+ }
198
+ else {
199
+ range = this.firstParagraph().splice(0, 0, text, 'before', undefined, options.lineBreaks);
200
+ }
201
+ // every run now in the control is the write's: each takes a copy of the control's run properties (D11)
202
+ const rPr = this.runProperties;
203
+ if (rPr) {
204
+ const holder = form === 'Run' ? this.sdt.sdtContent : undefined;
205
+ const runs = holder ? runsOf(holder) : this.body().paragraphs.flatMap((p) => runsOf(p.p));
206
+ for (const run of runs) {
207
+ run.value.rPr = deepCopy(rPr);
208
+ linkParents(run.value.rPr, run.value);
209
+ }
210
+ }
211
+ this.syncBoundNode();
212
+ return range;
213
+ }
214
+ /** The control's first paragraph, made where its content has none. */
215
+ firstParagraph() {
216
+ var _a;
217
+ var _b;
218
+ const body = this.body();
219
+ const first = body.paragraphs[0];
220
+ if (first)
221
+ return first;
222
+ const p = el.p({ content: [] });
223
+ const content = ((_a = (_b = this.sdt.sdtContent).content) !== null && _a !== void 0 ? _a : (_b.content = []));
224
+ content.push(p);
225
+ linkParents(p, this.sdt.sdtContent);
226
+ return body.paragraphs[0];
227
+ }
228
+ /**
229
+ * Takes the placeholder's runs out of the content: a run-level control's, or a block control's paragraphs', the first
230
+ * paragraph kept, emptied, and any other going. What held them.
231
+ */
232
+ takePlaceholderRuns(form) {
233
+ var _a, _b, _c, _d, _e, _f, _g;
234
+ var _h, _j, _k;
235
+ const runs = [];
236
+ const take = (items) => {
237
+ for (let i = 0; i < items.length; i++) {
238
+ if (typeNameOf(items[i]) !== 'org_docx4j_wml.R')
239
+ continue;
240
+ runs.push(items.splice(i, 1)[0]);
241
+ i--;
242
+ }
243
+ };
244
+ if (form === 'Run') {
245
+ const owner = ((_a = (_h = this.sdt).sdtContent) !== null && _a !== void 0 ? _a : (_h.sdtContent = {}));
246
+ const holder = ((_b = owner.content) !== null && _b !== void 0 ? _b : (owner.content = []));
247
+ take(holder);
248
+ return { runs, holder, owner };
249
+ }
250
+ const content = (_d = (_c = this.sdt.sdtContent) === null || _c === void 0 ? void 0 : _c.content) !== null && _d !== void 0 ? _d : [];
251
+ const paragraphs = content.filter((e) => typeNameOf(e) === 'org_docx4j_wml.P');
252
+ for (const p of paragraphs)
253
+ take(((_e = (_j = p.value).content) !== null && _e !== void 0 ? _e : (_j.content = [])));
254
+ for (const p of paragraphs.slice(1))
255
+ content.splice(content.indexOf(p), 1);
256
+ const first = (_f = paragraphs[0]) !== null && _f !== void 0 ? _f : this.firstParagraph().element;
257
+ return { runs, holder: ((_g = (_k = first.value).content) !== null && _g !== void 0 ? _g : (_k.content = [])), owner: first.value };
258
+ }
259
+ /**
260
+ * Shows Word's filler for a control emptied in the bare form, where no glossary entry gives it a placeholder: the flag
261
+ * on over a run of five spaces, in the control's own run properties (CR-002 section 63, D10; check 63's run 2, cases 6,
262
+ * 17, 32 and 43, and check 52's case 13). Nothing is marked under tracking. The spaces read as no text while the flag is
263
+ * on (CR-012 section 18).
264
+ */
265
+ showFiller() {
266
+ const { holder, owner } = this.takePlaceholderRuns(this.form);
267
+ const rPr = this.runProperties;
268
+ const run = el.r({ ...(rPr ? { rPr: deepCopy(rPr) } : {}), content: [el.t({ TYPE_NAME: 'org_docx4j_wml.Text', value: ' ', space: 'preserve' })] });
269
+ pushBeforeMarker(holder, run);
270
+ linkParents(run, owner);
271
+ this.isShowingPlaceholder = true;
272
+ }
273
+ /**
274
+ * @internal A write through the control's 'Content' or 'Whole' range that the control takes (D3, D4): text into a
275
+ * control showing its placeholder, or into an empty run-level control, goes inside it; a 'Whole' range's Replace over
276
+ * a run-level control showing its placeholder takes the control, the text in its place. Undefined where the range's
277
+ * own write applies.
278
+ */
279
+ writeThrough(part, text, location) {
280
+ var _a;
281
+ if (part === 'Content') {
282
+ if (this.showsPlaceholderContent)
283
+ return this.writePlaceholder(text);
284
+ if (this.form !== 'Run' || !((_a = this.runSpan()) === null || _a === void 0 ? void 0 : _a.empty))
285
+ return undefined;
286
+ this.assertWritable();
287
+ const range = this.insertTextInto(text, 'Replace');
288
+ this.syncBoundNode();
289
+ return range;
290
+ }
291
+ if (location !== 'Replace' || this.form !== 'Run' || !this.isShowingPlaceholder)
292
+ return undefined;
293
+ return this.takeWhole(text);
294
+ }
295
+ /**
296
+ * @internal A delete through the control's 'Content' or 'Whole' range that the control takes; false where the range's
297
+ * own delete applies. A 'Content' delete of a control showing its placeholder keeps the control and the flag: in the
298
+ * bare form over Word's filler, five spaces (D10; check 63's run 2, cases 6, 17, 32 and 43, nothing marked under
299
+ * tracking); in Word's form nothing changes, the placeholder being no content. A 'Whole' delete takes a run-level
300
+ * control showing its placeholder (D3).
301
+ */
302
+ deleteThrough(part) {
303
+ if (part === 'Whole') {
304
+ if (this.form !== 'Run' || !this.isShowingPlaceholder)
305
+ return false;
306
+ this.takeWhole('');
307
+ return true;
308
+ }
309
+ if (!this.showsPlaceholderContent)
310
+ return false;
311
+ this.assertWritable();
312
+ if (this.placeholderEntry === undefined)
313
+ this.showFiller();
314
+ return true;
315
+ }
316
+ /**
317
+ * @internal After a 'Content' delete, untracked, that left the control holding no text: in Word's form it shows its
318
+ * entry again (check 66 case 3); in the bare form Word's filler (D10; check 52's case 13), as for a control of a kind
319
+ * Word gives no default entry, naming an entry the glossary lacks.
320
+ */
321
+ showEmptied() {
322
+ var _a;
323
+ if (this.isShowingPlaceholder || this.text !== '' || (this.form !== 'Block' && this.form !== 'Run'))
324
+ return;
325
+ const name = this.placeholderEntry;
326
+ if (name !== undefined && (this.defaultPlaceholderEntry !== undefined || ((_a = this.glossary) === null || _a === void 0 ? void 0 : _a.has(name)) === true))
327
+ this.showPlaceholder();
328
+ else
329
+ this.showFiller();
330
+ }
331
+ /**
332
+ * @internal Word's default placeholder entry for this control's kind (CR-002 section 63.22; docx4j CR-063's D8): the
333
+ * text kinds', the date picker's, the lists', a building block gallery's, a repeating section item's. Undefined for a
334
+ * kind Word gives none.
335
+ */
336
+ get defaultPlaceholderEntry() {
337
+ return defaultEntryFor(this.sdt.sdtPr);
338
+ }
339
+ /**
340
+ * @internal Whether an empty control shows a placeholder in Word's form (D8; docx4j CR-063's D3 and D8): a kind Word
341
+ * gives a default entry, or one naming an entry the glossary holds.
342
+ */
343
+ get showsWordsFormPlaceholder() {
344
+ var _a;
345
+ if (this.defaultPlaceholderEntry !== undefined)
346
+ return true;
347
+ const name = this.placeholderEntry;
348
+ return name !== undefined && name !== '' && ((_a = this.glossary) === null || _a === void 0 ? void 0 : _a.has(name)) === true;
349
+ }
350
+ /**
351
+ * @internal Takes the control whole (D3): a Replace or delete over a control showing its placeholder, as Word takes one
352
+ * (check 63 cases 12, 18 to 20, 26, 38, 44 to 46 and 52). Untracked it goes, a block control leaving its paragraph,
353
+ * emptied, in its place; tracked it is marked deleted in Word's form (`markHolderDeleted`: `w:customXmlDelRange`
354
+ * markers around it and inside its content), its placeholder gone unmarked and the flag cleared (check 63's run 2,
355
+ * cases 38, 44 to 46 and 52). `text` goes where a run-level control stood, before it under tracking, as Word puts a
356
+ * Replace's insertion first.
357
+ */
358
+ takeWhole(text = '') {
359
+ var _a, _b;
360
+ this.assertWritable();
361
+ const form = this.form;
362
+ const tracker = this.parentBody.changeTracker;
363
+ if (form === 'Run') {
364
+ const span = this.runSpan();
365
+ if (!span)
366
+ throw new Docx4JException('This content control is not in a paragraph');
367
+ const { paragraph, start } = span;
368
+ if (!tracker) {
369
+ const at = this.container.indexOf(this.element);
370
+ if (at >= 0)
371
+ this.container.splice(at, 1);
372
+ return text === '' ? new Range(paragraph, start, start) : paragraph.splice(start, start, text);
373
+ }
374
+ const inserted = text === '' ? new Range(paragraph, start, start) : paragraph.splice(start, start, text);
375
+ this.markWholeDeleted(tracker, form);
376
+ return inserted;
377
+ }
378
+ const first = this.firstParagraph();
379
+ if (!tracker) {
380
+ const at = this.container.indexOf(this.element);
381
+ this.takePlaceholderRuns(form);
382
+ const paragraph = first.element;
383
+ const owner = (_a = this.element.value.PARENT) !== null && _a !== void 0 ? _a : ownerOf(this.container, this.parentBody);
384
+ this.container.splice(at, 1, paragraph);
385
+ linkParents(paragraph, owner);
386
+ return new Range((_b = this.parentBody.paragraphFor(paragraph.value)) !== null && _b !== void 0 ? _b : first, 0, 0);
387
+ }
388
+ this.markWholeDeleted(tracker, form);
389
+ return new Range(first, 0, 0);
390
+ }
391
+ /** The control marked deleted with Word's markers, its placeholder gone, unmarked, and the flag cleared (`takeWhole`). */
392
+ markWholeDeleted(tracker, form) {
393
+ markHolderDeleted(tracker, this.element, this.container, () => { this.takePlaceholderRuns(form); });
394
+ this.isShowingPlaceholder = false;
395
+ }
396
+ /**
397
+ * Shows the control's placeholder (CR-002 section 63, D8; section 63.18): a copy of the content of the entry its
398
+ * `w:placeholder` names, as the glossary holds it, the control's own run properties not over it, as Word shows it
399
+ * (check 68, section 63.21), with `w:showingPlcHdr`. A control naming no entry, or one the glossary lacks, is made to
400
+ * name Word's default entry for its kind (section 63.22: a date picker's is "Click or tap to enter a date.", a list's
401
+ * "Choose an item."), which is written into the glossary on save where it lacks it (D9), as docx4j's CR-063 has it:
402
+ * Word's save drops a reference no entry holds (check 65). A kind Word gives no default entry (a picture, a check box)
403
+ * keeps the name it has, and naming none shows docx4j's run, with no reference; any control keeps its name until the
404
+ * glossary is read (`getBody`, `Word.run`), showing its kind's default text. An extension.
405
+ */
406
+ showPlaceholder() {
407
+ var _a, _b, _c, _d;
408
+ var _e, _f;
409
+ this.assertWritable();
410
+ const sdtPr = ((_a = (_e = this.sdt).sdtPr) !== null && _a !== void 0 ? _a : (_e.sdtPr = {}));
411
+ const glossary = this.glossary;
412
+ const fallback = this.defaultPlaceholderEntry;
413
+ let name = (_b = this.placeholderEntry) !== null && _b !== void 0 ? _b : '';
414
+ const missing = name === '' || ((glossary === null || glossary === void 0 ? void 0 : glossary.loaded) === true && !glossary.has(name));
415
+ if (missing && fallback) {
416
+ name = fallback.name;
417
+ namePlaceholderEntry(sdtPr, name);
418
+ }
419
+ if (fallback && name === fallback.name)
420
+ glossary === null || glossary === void 0 ? void 0 : glossary.require(name, fallback.text);
421
+ const body = name === '' ? null : glossary === null || glossary === void 0 ? void 0 : glossary.bodyOf(name);
422
+ // no entry to copy: the kind's default text as its entry holds it, else docx4j's run (CR-063's D8 (a))
423
+ const paragraphs = body ? entryContentOf(body) : [[fallback ? placeholderRun(undefined, fallback.text) : placeholderRun(this.runProperties)]];
424
+ this.setBoundContent((_c = paragraphs[0]) !== null && _c !== void 0 ? _c : [], true);
425
+ // a block control shows an entry's further paragraphs too, where it may hold more than one (CR-002 section 61)
426
+ if (this.form !== 'Block' || paragraphs.length < 2 || isBoundBlockControl(this))
427
+ return;
428
+ const content = (_d = (_f = this.sdt.sdtContent).content) !== null && _d !== void 0 ? _d : (_f.content = []);
429
+ const first = content.find((e) => typeNameOf(e) === 'org_docx4j_wml.P');
430
+ let at = first ? content.indexOf(first) + 1 : content.length;
431
+ // the further paragraphs take a copy of the control's own paragraph's properties, as docx4j's binding gives them
432
+ const pPr = first === null || first === void 0 ? void 0 : first.value.pPr;
433
+ for (const runs of paragraphs.slice(1)) {
434
+ const p = el.p({ ...(pPr ? { pPr: deepCopy(pPr) } : {}), content: runs });
435
+ content.splice(at++, 0, p);
436
+ linkParents(p, this.sdt.sdtContent);
437
+ }
438
+ }
121
439
  insertTextInto(text, location) {
122
440
  const form = this.form;
123
441
  if ((form === 'Row' || form === 'Cell') && location === 'Replace') {
@@ -125,6 +443,7 @@ export class ContentControl {
125
443
  }
126
444
  if (form !== 'Run')
127
445
  return this.body().insertText(text, location);
446
+ text = textForRunControl(this, dropExcluded(text)); // CR-002 section 60: F4, F3
128
447
  const span = this.runSpan();
129
448
  if (!span)
130
449
  throw new Docx4JException('This content control is not in a paragraph');
@@ -139,7 +458,31 @@ export class ContentControl {
139
458
  }
140
459
  /** A paragraph at the start or end of a block control; before or after the paragraph of a run control. */
141
460
  insertParagraph(text, location) {
461
+ this.assertWritable();
142
462
  const form = this.form;
463
+ if ((location === 'Start' || location === 'End') && form === 'Block' && this.isShowingPlaceholder) {
464
+ // CR-002 section 63, D1: the placeholder goes, its paragraph left empty, the flag cleared, and the new paragraph
465
+ // follows, in either form (check 64 cases 6 and 27, check 63's run 2 cases 3 and 29)
466
+ this.takePlaceholderRuns(form);
467
+ this.isShowingPlaceholder = false;
468
+ }
469
+ if ((location === 'Start' || location === 'End') && isBoundBlockControl(this)) {
470
+ // Word holds a bound block control to one paragraph (CR-002 section 61): into a single-line one the text joins
471
+ // its paragraph, as Word's insertParagraph writes it; into a multi-line one Word's own second paragraph makes a
472
+ // file Word can't open, so it is refused (check 56, H2)
473
+ if (this.isMultiLine) {
474
+ throw new NotSupportedError('ContentControl', 'insertParagraph', 'this control is bound by w:dataBinding, and Word holds a bound '
475
+ + "block control to one paragraph: its own second paragraph in a multi-line one makes a file Word can't open (CR-002 section 61, check 56); "
476
+ + 'insertText with a line feed writes a line break');
477
+ }
478
+ const paragraphs = this.paragraphs;
479
+ const p = location === 'Start' ? paragraphs[0] : paragraphs[paragraphs.length - 1];
480
+ if (p) {
481
+ p.insertText(text, location);
482
+ this.syncBoundNode();
483
+ return p;
484
+ }
485
+ }
143
486
  if ((form === 'Row' || form === 'Cell') && (location === 'Start' || location === 'End')) {
144
487
  throw new Docx4JException(`A ${form.toLowerCase()}-level content control holds ${form === 'Row' ? 'rows' : 'cells'}, not paragraphs; insert into a cell's body`);
145
488
  }
@@ -159,10 +502,19 @@ export class ContentControl {
159
502
  return range ? range.search(text, options) : [];
160
503
  }
161
504
  /**
162
- * The control's range. For a run-level control, exactly the text it holds; for the other forms,
163
- * the first paragraph (departure: a Range here is within one paragraph, CR-002 section 7).
505
+ * The control's range. For a run-level control, exactly the text it holds; for the other forms, its
506
+ * paragraphs: one `Range` from the first one's start to the last one's end (CR-014 phase 1), its 'Whole'
507
+ * ending with the last one's mark, as Office JS's does (check 51: `CCP1\rCCP2\rCCP3\r`, its 'Content'
508
+ * without the last `\r`).
164
509
  */
165
510
  getRange(location = 'Whole') {
511
+ const range = this.rangeOf(location);
512
+ // its own 'Content' and 'Whole' ranges know the control, for a write into one with no width (CR-002 section 63, D4)
513
+ if ((location === 'Content' || location === 'Whole') && (this.form === 'Run' || this.form === 'Block'))
514
+ range.ofControl = { control: this, part: location };
515
+ return range;
516
+ }
517
+ rangeOf(location) {
166
518
  if (this.form === 'Run') {
167
519
  const span = this.runSpan();
168
520
  if (!span)
@@ -174,22 +526,51 @@ export class ContentControl {
174
526
  return new Range(paragraph, end, end);
175
527
  return new Range(paragraph, start, end);
176
528
  }
177
- const first = this.paragraphs[0];
529
+ const paragraphs = this.paragraphs;
530
+ const first = paragraphs[0];
178
531
  if (!first)
179
532
  throw new Docx4JException('This content control holds no paragraph');
180
- return first.getRange(location);
533
+ const last = paragraphs[paragraphs.length - 1];
534
+ if (location === 'End')
535
+ return last.getRange('End');
536
+ if (location === 'Start')
537
+ return first.getRange('Start');
538
+ // a row-level control, or one whose content ends with a table: 'Content' ends with the last cell's text, as
539
+ // the control's text does, and 'Whole' with \t\t, as Word 365 gives them (check 53)
540
+ const lastItem = [...this.content].reverse().find((item) => { var _a; return BLOCK_ITEMS.has((_a = typeNameOf(item)) !== null && _a !== void 0 ? _a : ''); });
541
+ if (this.form === 'Row' || (lastItem !== undefined && typeNameOf(lastItem) === 'org_docx4j_wml.Tbl')) {
542
+ return location === 'Whole' ? first.rangeTo(last, true, '\t\t') : first.rangeTo(last, false);
543
+ }
544
+ if (paragraphs.length === 1)
545
+ return first.getRange(location);
546
+ return new Range(first, 0, last.text.length, true, location === 'Whole', last);
181
547
  }
182
- /** Removes the control; `keepContent` puts what it held in its place, as Office JS. */
548
+ /**
549
+ * Removes the control; `keepContent` puts what it held in its place, as Office JS. Where the control held the
550
+ * paragraph a table cell ends with, that paragraph stays in its place, emptied, its properties kept, outside
551
+ * any control, as Word keeps a cell's last paragraph (check 52, case 95; CR-002 section 55.6).
552
+ */
183
553
  delete(keepContent = false) {
554
+ this.assertWritable();
184
555
  const at = this.container.indexOf(this.element);
185
556
  if (at < 0)
186
557
  return;
187
558
  const content = keepContent ? [...this.content] : [];
559
+ const owner = ownerOf(this.container, this.parentBody);
560
+ const cell = this.form === 'Block' ? wrappingCellOf(owner) : undefined;
561
+ const end = cell ? paragraphEndOf(cell.content) : undefined;
188
562
  this.container.splice(at, 1, ...content);
189
- if (keepContent) {
190
- const owner = ownerOf(this.container, this.parentBody);
563
+ if (keepContent)
191
564
  for (const item of content)
192
565
  linkParents(item, owner);
566
+ if (cell && !endsWithParagraph(cell.content)) {
567
+ if (end) {
568
+ end.element.value.content = [];
569
+ this.container.splice(at, 0, end.element);
570
+ linkParents(end.element, owner);
571
+ }
572
+ else
573
+ endWithParagraph(cell);
193
574
  }
194
575
  }
195
576
  /** The XML of the control (extension). */
@@ -213,6 +594,7 @@ export class ContentControl {
213
594
  setProperty(localPart, value, make) {
214
595
  var _a, _b;
215
596
  var _c;
597
+ this.assertWritable();
216
598
  const sdt = this.sdt;
217
599
  (_a = sdt.sdtPr) !== null && _a !== void 0 ? _a : (sdt.sdtPr = {});
218
600
  const items = ((_b = (_c = sdt.sdtPr).rPrOrAliasOrLock) !== null && _b !== void 0 ? _b : (_c.rPrOrAliasOrLock = []));
@@ -254,7 +636,11 @@ export class ContentControl {
254
636
  };
255
637
  let start;
256
638
  let end = 0;
257
- const segs = segmentsOf(paragraph.p);
639
+ // where the walk passes the control: a control holding no text stands there, after the text before it
640
+ // in the paragraph (before, it was reported at 0, and text put into it went to the paragraph's start)
641
+ let at = 0;
642
+ const segs = paragraph.segments({ visit: (el, offset) => { if (el === this.element)
643
+ at = offset; } });
258
644
  for (const seg of segs) {
259
645
  if (!inside(seg.run))
260
646
  continue;
@@ -262,34 +648,8 @@ export class ContentControl {
262
648
  start = seg.start;
263
649
  end = seg.end;
264
650
  }
265
- if (start === undefined) {
266
- // a control holding no text: its position is the end of the last text before it in the
267
- // paragraph (before, it was reported at 0, and text put into it went to the paragraph's start)
268
- let pos = 0;
269
- let found = false;
270
- const visit = (items) => {
271
- for (const el of items !== null && items !== void 0 ? items : []) {
272
- if (found)
273
- return;
274
- if (el === this.element) {
275
- found = true;
276
- return;
277
- }
278
- const v = el.value;
279
- if (typeof v !== 'object' || v === null)
280
- continue;
281
- if (typeNameOf(el) === 'org_docx4j_wml.R') {
282
- const last = segs.filter((seg) => seg.run === v).pop();
283
- if (last)
284
- pos = last.end;
285
- }
286
- else
287
- visit(runItemsOf(v));
288
- }
289
- };
290
- visit(paragraph.p.content);
291
- return { paragraph, start: pos, end: pos, empty: true };
292
- }
651
+ if (start === undefined)
652
+ return { paragraph, start: at, end: at, empty: true };
293
653
  return { paragraph, start, end };
294
654
  }
295
655
  /**
@@ -300,9 +660,10 @@ export class ContentControl {
300
660
  insertIntoEmpty(paragraph, text) {
301
661
  var _a, _b;
302
662
  var _c;
663
+ text = textForRunControl(this, dropExcluded(text));
303
664
  const sdt = this.sdt;
304
665
  const items = ((_b = (_c = ((_a = sdt.sdtContent) !== null && _a !== void 0 ? _a : (sdt.sdtContent = {}))).content) !== null && _b !== void 0 ? _b : (_c.content = []));
305
- const run = runOf([textItem(text)]);
666
+ const run = runOf(officeItems(text));
306
667
  const tracker = paragraph.changeTracker;
307
668
  const item = tracker ? tracker.ins([run]) : run;
308
669
  const at = items.findIndex((el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === 'customXmlInsRangeStart'; });
@@ -318,26 +679,52 @@ export class ContentControl {
318
679
  return new XmlMapping(this, pkg === null || pkg === void 0 ? void 0 : pkg.customXmlParts);
319
680
  }
320
681
  /**
321
- * The placeholder Word shows for an empty control: the control's own text while it is showing
322
- * the placeholder (`w:showingPlcHdr`), else '' — Word keeps the text itself in a glossary
323
- * document part, which this phase does not create (CR-002 section 12). Setting writes the text
324
- * as the control's content and sets `w:showingPlcHdr`, which is refused when the control holds
325
- * content of its own, so that a value is never lost.
682
+ * The placeholder Word shows for an empty control: the text of the glossary entry the control's `w:placeholder` names,
683
+ * as Office JS reads it (CR-002 section 63, D8; check 65), `null` where the control names none (the bare form) or the
684
+ * glossary lacks the entry. The glossary is read when the body is (`getBody`, `Word.run`). Setting it names Word's
685
+ * default entry for Word's default text, else a new entry holding the text, named as Word names one (32 hex digits),
686
+ * which is written into the glossary on save, the entry named before left there; a control showing its placeholder,
687
+ * or holding nothing, shows the new text. A control's content is left alone. Until section 63 the text was written as
688
+ * the control's content in the bare form, and a control holding content refused it.
326
689
  */
327
690
  get placeholderText() {
328
- return this.isShowingPlaceholder ? this.text : '';
691
+ var _a, _b;
692
+ const name = this.placeholderEntry;
693
+ if (name === undefined)
694
+ return null;
695
+ return (_b = (_a = this.glossary) === null || _a === void 0 ? void 0 : _a.textOf(name)) !== null && _b !== void 0 ? _b : null;
329
696
  }
330
697
  set placeholderText(text) {
331
- var _a;
332
- if (!this.isShowingPlaceholder && this.text !== '') {
333
- throw new Docx4JException('This content control holds content; clear it before setting its placeholder text');
334
- }
335
- this.setBoundContent(runsForValue('', this.runProperties, false), true);
336
- if (text !== '' && text !== PLACEHOLDER_TEXT) {
337
- const run = this.firstRunOfContent();
338
- const t = (_a = run === null || run === void 0 ? void 0 : run.content) === null || _a === void 0 ? void 0 : _a[0];
339
- if (t === null || t === void 0 ? void 0 : t.value)
340
- t.value.value = text;
698
+ var _a, _b, _c;
699
+ var _d;
700
+ this.assertWritable();
701
+ // the kind's default text, or '', names its default entry (section 63.22); any other text a new entry of its own
702
+ const fallback = (_a = this.defaultPlaceholderEntry) !== null && _a !== void 0 ? _a : DEFAULT_ENTRIES.text;
703
+ const own = text !== '' && text !== fallback.text;
704
+ const name = own ? newEntryName() : fallback.name;
705
+ const shown = own ? text : fallback.text;
706
+ namePlaceholderEntry(((_b = (_d = this.sdt).sdtPr) !== null && _b !== void 0 ? _b : (_d.sdtPr = {})), name);
707
+ (_c = this.glossary) === null || _c === void 0 ? void 0 : _c.require(name, shown);
708
+ // the entry's run, as the glossary will hold it: the control's own run properties aren't over it (check 68)
709
+ if (this.isShowingPlaceholder || this.text === '')
710
+ this.setBoundContent([placeholderRun(undefined, shown)], true);
711
+ }
712
+ /**
713
+ * Blocks taken out of the control's content: the relationships only they used (a picture's image) and the comments
714
+ * whose references they held go when the package is saved, as a deletion's do.
715
+ */
716
+ removeBlocks(content, blocks) {
717
+ const pkg = this.parentBody.package_;
718
+ const ids = new Set();
719
+ relationshipIdsIn(blocks, ids);
720
+ if (ids.size > 0)
721
+ queueRelationshipRemovals(pkg === null || pkg === void 0 ? void 0 : pkg.pendingRelationshipRemovals, this.parentBody.part, ids);
722
+ if (pkg === null || pkg === void 0 ? void 0 : pkg.pendingCommentRemovals)
723
+ commentReferencesIn(blocks, pkg.pendingCommentRemovals);
724
+ for (const block of blocks) {
725
+ const at = content.indexOf(block);
726
+ if (at >= 0)
727
+ content.splice(at, 1);
341
728
  }
342
729
  }
343
730
  /** Office JS `appearance`: how Word draws the control (`w15:appearance`). */
@@ -402,19 +789,19 @@ export class ContentControl {
402
789
  get datePickerContentControl() {
403
790
  var _a;
404
791
  const value = (_a = sdtProperty(this.sdt.sdtPr, 'date')) === null || _a === void 0 ? void 0 : _a.value;
405
- return value ? new DatePickerContentControl(value) : undefined;
792
+ return value ? new DatePickerContentControl(value, () => this.assertWritable()) : undefined;
406
793
  }
407
794
  /** The list view, for a `w:dropDownList` control. */
408
795
  get dropDownListContentControl() {
409
796
  var _a;
410
797
  const value = (_a = sdtProperty(this.sdt.sdtPr, 'dropDownList')) === null || _a === void 0 ? void 0 : _a.value;
411
- return value ? new ListContentControl(value) : undefined;
798
+ return value ? new ListContentControl(value, () => this.assertWritable()) : undefined;
412
799
  }
413
800
  /** The list view, for a `w:comboBox` control. */
414
801
  get comboBoxContentControl() {
415
802
  var _a;
416
803
  const value = (_a = sdtProperty(this.sdt.sdtPr, 'comboBox')) === null || _a === void 0 ? void 0 : _a.value;
417
- return value ? new ListContentControl(value) : undefined;
804
+ return value ? new ListContentControl(value, () => this.assertWritable()) : undefined;
418
805
  }
419
806
  /** The picture view, for a `w:picture` control. */
420
807
  get pictureContentControl() {
@@ -438,6 +825,7 @@ export class ContentControl {
438
825
  putProperty(element) {
439
826
  var _a, _b;
440
827
  var _c;
828
+ this.assertWritable();
441
829
  const sdt = this.sdt;
442
830
  (_a = sdt.sdtPr) !== null && _a !== void 0 ? _a : (sdt.sdtPr = {});
443
831
  const items = ((_b = (_c = sdt.sdtPr).rPrOrAliasOrLock) !== null && _b !== void 0 ? _b : (_c.rPrOrAliasOrLock = []));
@@ -452,6 +840,7 @@ export class ContentControl {
452
840
  /** Removes a `w:sdtPr` child (extension). */
453
841
  removeProperty(localPart, namespaceURI) {
454
842
  var _a;
843
+ this.assertWritable();
455
844
  const items = (_a = this.sdt.sdtPr) === null || _a === void 0 ? void 0 : _a.rPrOrAliasOrLock;
456
845
  if (!items)
457
846
  return;
@@ -469,6 +858,10 @@ export class ContentControl {
469
858
  var _a, _b;
470
859
  return ((_b = (_a = sdtProperty(this.sdt.sdtPr, 'text')) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.multiLine) === true;
471
860
  }
861
+ /** Office JS's name for `isShowingPlaceholder` (`showingPlaceholderText`, WordApiDesktop 1.3, read-only there). */
862
+ get showingPlaceholderText() {
863
+ return this.isShowingPlaceholder;
864
+ }
472
865
  /** Whether the control is showing its placeholder rather than a value (`w:showingPlcHdr`). */
473
866
  get isShowingPlaceholder() {
474
867
  var _a;
@@ -480,7 +873,7 @@ export class ContentControl {
480
873
  this.removeProperty('showingPlcHdr');
481
874
  return;
482
875
  }
483
- this.putProperty(el.showingPlcHdr({ val: true }));
876
+ this.putProperty(el.showingPlcHdr({})); // <w:showingPlcHdr/>, as Word writes it
484
877
  }
485
878
  /**
486
879
  * Replaces what the control shows with these runs (docx4j BindingHandler's applyBoundContent):
@@ -490,16 +883,22 @@ export class ContentControl {
490
883
  setBoundContent(runs, showingPlaceholder = false) {
491
884
  var _a, _b, _c, _d;
492
885
  var _e;
886
+ this.assertWritable();
493
887
  const sdt = this.sdt;
494
888
  (_a = sdt.sdtContent) !== null && _a !== void 0 ? _a : (sdt.sdtContent = {});
495
889
  const content = ((_b = (_e = sdt.sdtContent).content) !== null && _b !== void 0 ? _b : (_e.content = []));
496
890
  const first = content.find((e) => typeNameOf(e) !== undefined);
497
891
  if (this.form === 'Run' || (first && typeNameOf(first) === 'org_docx4j_wml.R')) {
892
+ // a tracked control's own markers stay where they are, its inner end first and its inner start last (check 30)
893
+ const ends = content.filter((e) => { var _a, _b; return TRACKED_HOLDER_MARKER.test((_b = (_a = e.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : '') && e.name.localPart.endsWith('End'); });
894
+ const starts = content.filter((e) => { var _a, _b; return TRACKED_HOLDER_MARKER.test((_b = (_a = e.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : '') && e.name.localPart.endsWith('Start'); });
498
895
  content.length = 0;
499
- content.push(...runs);
896
+ content.push(...ends, ...runs, ...starts);
500
897
  linkParents(content, sdt.sdtContent);
501
898
  }
502
- else {
899
+ else if (!isBoundBlockControl(this)) {
900
+ // a cell or row control, or a block control section 61's rule doesn't cover (a w15:dataBinding, Word's rich-text
901
+ // binding, or none): the value goes into its first paragraph, the rest as it is
503
902
  const paragraph = this.paragraphs[0];
504
903
  if (paragraph) {
505
904
  paragraph.p.content = runs;
@@ -511,6 +910,26 @@ export class ContentControl {
511
910
  linkParents(p, sdt.sdtContent);
512
911
  }
513
912
  }
913
+ else {
914
+ // A bound block control holds one paragraph (CR-002 section 61): the value goes into its first paragraph and any
915
+ // other block it holds goes, as docx4j's bind.xslt writes one paragraph (section 61.6, docx4j-python's review).
916
+ // Before, the first paragraph took the value and the rest stayed, so a control holding two kept them, and Word
917
+ // still couldn't open the file.
918
+ const blocks = content.filter((e) => { var _a; return BLOCK_LEVEL_TYPES.has((_a = typeNameOf(e)) !== null && _a !== void 0 ? _a : ''); });
919
+ const paragraph = blocks.find((e) => typeNameOf(e) === 'org_docx4j_wml.P');
920
+ if (paragraph) {
921
+ paragraph.value.content = runs;
922
+ linkParents(paragraph.value.content, paragraph.value);
923
+ }
924
+ else {
925
+ const p = paragraphElementOf(runs);
926
+ content.push(p);
927
+ linkParents(p, sdt.sdtContent);
928
+ }
929
+ const others = blocks.filter((e) => e !== paragraph);
930
+ if (others.length > 0)
931
+ this.removeBlocks(content, others);
932
+ }
514
933
  this.isShowingPlaceholder = showingPlaceholder;
515
934
  // The placeholder run names the PlaceholderText style, which a document made here lacks: the
516
935
  // package splices the definition in when it is saved, as it does a new hyperlink's style. Word
@@ -527,6 +946,7 @@ export class ContentControl {
527
946
  }
528
947
  /** A copy of this control right after it, as Word's repeating-section + button does (extension). */
529
948
  insertCopyAfter() {
949
+ this.assertWritable();
530
950
  const at = this.container.indexOf(this.element);
531
951
  if (at < 0)
532
952
  throw new Docx4JException('This content control is not in its container any more');
@@ -536,8 +956,8 @@ export class ContentControl {
536
956
  return new ContentControl(copy, this.container, this.parentBody);
537
957
  }
538
958
  /**
539
- * Writes the control's text into the node it is bound to, when there is one and the XPath engine
540
- * is warm. Silent when it is not: reading a custom XML part is `pkg.customXmlParts.load()`'s job,
959
+ * @internal Writes the control's text into the node it is bound to, when there is one and the XPath engine
960
+ * is warm (CR-002 section 63, D7: every text write into a bound control). Silent when it is not: reading a custom XML part is `pkg.customXmlParts.load()`'s job,
541
961
  * and `updateFromContentControls()` is the explicit form of this write.
542
962
  */
543
963
  syncBoundNode() {
@@ -549,14 +969,6 @@ export class ContentControl {
549
969
  // the part is not parsed, or the engine is not ready: leave it to updateFromContentControls()
550
970
  }
551
971
  }
552
- firstRunOfContent() {
553
- var _a, _b, _c, _d;
554
- const runs = this.form === 'Run' ? (_b = runItemsOf((_a = this.sdt.sdtContent) !== null && _a !== void 0 ? _a : {})) !== null && _b !== void 0 ? _b : [] : (_d = (_c = this.paragraphs[0]) === null || _c === void 0 ? void 0 : _c.runs) !== null && _d !== void 0 ? _d : [];
555
- for (const item of runs)
556
- if (typeNameOf(item) === 'org_docx4j_wml.R')
557
- return item.value;
558
- return undefined;
559
- }
560
972
  lockValue() {
561
973
  var _a, _b;
562
974
  return (_b = (_a = sdtProperty(this.sdt.sdtPr, 'lock')) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.val;
@@ -572,6 +984,17 @@ export class ContentControl {
572
984
  }
573
985
  this.putProperty(el.lock({ val }));
574
986
  }
987
+ /**
988
+ * True for a control in the selected branch of a kept `mc:AlternateContent`, at any level, which the
989
+ * collectors list as Word reads it (CR-013 phase 2). Such a view is read-only (D4).
990
+ */
991
+ get inBranch() {
992
+ return inBranch(this.element.value);
993
+ }
994
+ /** Refuses a write to a control in a branch (CR-013 D4). Internal: the control's mutators, and its kinds', call it first. */
995
+ assertWritable() {
996
+ assertOutsideBranch(this.element.value, 'ContentControl');
997
+ }
575
998
  /** The table a row-level or cell-level control belongs to. */
576
999
  ancestorTable() {
577
1000
  var _a;
@@ -588,8 +1011,13 @@ export class ContentControl {
588
1011
  return undefined;
589
1012
  }
590
1013
  }
591
- /** Every content control under a list of block-level items, in document order, nested ones included. */
592
- export function collectControls(items, body, out) {
1014
+ /**
1015
+ * Every content control under a list of block-level items, in document order, nested ones included. In a
1016
+ * kept `mc:AlternateContent` a reader's (`'read'`, the default) takes the selected branch's controls (CR-013
1017
+ * phase 2), the binding passes (`'every'`) every branch's, each other branch's where the element stands
1018
+ * (phase 3).
1019
+ */
1020
+ export function collectControls(items, body, out, mode = 'read') {
593
1021
  var _a;
594
1022
  for (const element of items) {
595
1023
  const tn = typeNameOf(element);
@@ -598,24 +1026,29 @@ export function collectControls(items, body, out) {
598
1026
  if (SDT_TYPES.has(tn)) {
599
1027
  const control = new ContentControl(element, items, body);
600
1028
  out.push(control);
601
- collectControls(control.content, body, out);
1029
+ collectControls(control.content, body, out, mode);
602
1030
  continue;
603
1031
  }
604
1032
  if (tn === 'org_docx4j_wml.P') {
605
- collectRunControls(element.value, body, out);
1033
+ collectRunControls(element.value, body, out, mode);
606
1034
  continue;
607
1035
  }
608
1036
  if (tn === 'org_docx4j_wml.Tbl') {
609
- collectInRows((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out);
1037
+ collectInRows((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out, mode);
1038
+ continue;
1039
+ }
1040
+ if (tn === ALTERNATE_CONTENT) {
1041
+ for (const branch of branchListsOf(element.value, mode))
1042
+ collectControls(branch, body, out, mode);
610
1043
  continue;
611
1044
  }
612
1045
  const children = childrenOf(element.value);
613
1046
  if (children)
614
- collectControls(children, body, out);
1047
+ collectControls(children, body, out, mode);
615
1048
  }
616
1049
  }
617
1050
  /** Row-level controls and the controls inside the rows of a table. */
618
- function collectInRows(items, body, out) {
1051
+ function collectInRows(items, body, out, mode) {
619
1052
  var _a;
620
1053
  for (const element of items) {
621
1054
  const tn = typeNameOf(element);
@@ -624,15 +1057,18 @@ function collectInRows(items, body, out) {
624
1057
  if (SDT_TYPES.has(tn)) {
625
1058
  const control = new ContentControl(element, items, body);
626
1059
  out.push(control);
627
- collectInRows(control.content, body, out);
1060
+ collectInRows(control.content, body, out, mode);
628
1061
  continue;
629
1062
  }
630
1063
  if (tn === 'org_docx4j_wml.Tr')
631
- collectInCells((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out);
1064
+ collectInCells((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out, mode);
1065
+ else if (tn === ALTERNATE_CONTENT)
1066
+ for (const branch of branchListsOf(element.value, mode))
1067
+ collectInRows(branch, body, out, mode);
632
1068
  }
633
1069
  }
634
1070
  /** Cell-level controls and the controls inside the cells of a row. */
635
- function collectInCells(items, body, out) {
1071
+ function collectInCells(items, body, out, mode) {
636
1072
  var _a;
637
1073
  for (const element of items) {
638
1074
  const tn = typeNameOf(element);
@@ -641,18 +1077,29 @@ function collectInCells(items, body, out) {
641
1077
  if (SDT_TYPES.has(tn)) {
642
1078
  const control = new ContentControl(element, items, body);
643
1079
  out.push(control);
644
- collectInCells(control.content, body, out);
1080
+ collectInCells(control.content, body, out, mode);
645
1081
  continue;
646
1082
  }
647
1083
  if (tn === 'org_docx4j_wml.Tc')
648
- collectControls((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out);
1084
+ collectControls((_a = childrenOf(element.value)) !== null && _a !== void 0 ? _a : [], body, out, mode);
1085
+ else if (tn === ALTERNATE_CONTENT)
1086
+ for (const branch of branchListsOf(element.value, mode))
1087
+ collectInCells(branch, body, out, mode);
649
1088
  }
650
1089
  }
651
1090
  /** The run-level controls in a paragraph (or any run holder), in order, nested ones included. */
652
- export function collectRunControls(holder, body, out) {
653
- const items = runItemsOf(holder);
654
- if (!items)
1091
+ export function collectRunControls(holder, body, out, mode = 'read') {
1092
+ // a kept mc:AlternateContent among runs: its selected branch's (CR-013 phase 2), or every branch's (phase 3)
1093
+ if (holder.TYPE_NAME === ALTERNATE_CONTENT) {
1094
+ for (const branch of branchListsOf(holder, mode))
1095
+ collectRunItems(branch, body, out, mode);
655
1096
  return;
1097
+ }
1098
+ const items = runItemsOf(holder);
1099
+ if (items)
1100
+ collectRunItems(items, body, out, mode);
1101
+ }
1102
+ function collectRunItems(items, body, out, mode) {
656
1103
  for (const element of items) {
657
1104
  const tn = typeNameOf(element);
658
1105
  if (tn === undefined)
@@ -660,15 +1107,84 @@ export function collectRunControls(holder, body, out) {
660
1107
  if (SDT_TYPES.has(tn)) {
661
1108
  const control = new ContentControl(element, items, body);
662
1109
  out.push(control);
663
- collectRunControls(element.value, body, out);
1110
+ collectRunControls(element.value, body, out, mode);
664
1111
  continue;
665
1112
  }
666
1113
  if (tn !== 'org_docx4j_wml.R')
667
- collectRunControls(element.value, body, out);
1114
+ collectRunControls(element.value, body, out, mode);
668
1115
  }
669
1116
  }
670
1117
  function paragraphElement(text) {
671
- return el.p({ content: text === '' ? [] : [el.r({ content: [el.t({ value: text })] })] });
1118
+ const items = officeItems(forWriter(dropExcluded(text), 'one-paragraph'));
1119
+ return el.p({ content: items.length === 0 ? [] : [el.r({ content: items })] });
1120
+ }
1121
+ /**
1122
+ * A view of the content control whose `w:sdt` value this is, found in its owner's list (a paragraph's runs, a body's
1123
+ * blocks, a holder's items).
1124
+ */
1125
+ export function controlViewOf(sdt, body) {
1126
+ const owner = sdt.PARENT;
1127
+ for (const value of Object.values(owner !== null && owner !== void 0 ? owner : {})) {
1128
+ if (!Array.isArray(value))
1129
+ continue;
1130
+ const element = value.find((e) => (e === null || e === void 0 ? void 0 : e.value) === sdt);
1131
+ if (element)
1132
+ return new ContentControl(element, value, body);
1133
+ }
1134
+ throw new Docx4JException('This content control is not in the document any more');
1135
+ }
1136
+ /**
1137
+ * A block-level control bound by `w:dataBinding`, untyped, `w:text` or `w:richText`, whose node doesn't hold flat OPC
1138
+ * (CR-002 section 61 H1): Word holds its content to one paragraph, and can't open a file where it holds more (check 56,
1139
+ * docx4j-python's file 3). A node not resolved (the custom XML parts not loaded) counts as not flat OPC. `w15:dataBinding`
1140
+ * is Word's rich-text binding, and isn't this.
1141
+ */
1142
+ export function isBoundBlockControl(control) {
1143
+ if (control.form !== 'Block')
1144
+ return false;
1145
+ const pr = control.sdt.sdtPr;
1146
+ if (sdtProperty(pr, 'dataBinding') === undefined)
1147
+ return false;
1148
+ const kind = sdtKindOf(pr);
1149
+ if (kind !== 'RichText' && kind !== 'PlainText')
1150
+ return false;
1151
+ let node;
1152
+ try {
1153
+ node = control.xmlMapping.customXmlNode;
1154
+ }
1155
+ catch {
1156
+ node = undefined; // the parts aren't parsed: the rule applies
1157
+ }
1158
+ return !(node && holdsFlatOpc(node.text));
1159
+ }
1160
+ /**
1161
+ * How a control holds lines, as Word reads it: `multi` or `single` for a plain-text control (`w:text` and its
1162
+ * `w:multiLine`), `single` for a bound block control Word reads as plain text (section 61 H4); undefined for any other.
1163
+ */
1164
+ export function plainTextLinesOf(control) {
1165
+ if (sdtProperty(control.sdt.sdtPr, 'text') !== undefined)
1166
+ return control.isMultiLine ? 'multi' : 'single';
1167
+ return isBoundBlockControl(control) ? 'single' : undefined;
1168
+ }
1169
+ /**
1170
+ * Text for a run-level control: a line feed or carriage return there is refused before anything changes (CR-002
1171
+ * section 60 F4: Word, given one, moved a bound plain-text control to block level), and a plain-text control's breaks
1172
+ * go as its kind takes them (F3: dropped in a single-line one).
1173
+ */
1174
+ export function textForRunControl(control, text) {
1175
+ if (/[\r\n]/.test(text)) {
1176
+ throw new NotSupportedError('ContentControl', 'insertText', 'a line feed or carriage return in a run-level content control would make a '
1177
+ + 'paragraph break inside it, which Word answers by moving the control to block level (CR-002 section 60, F4); write a line break (U+000B) instead');
1178
+ }
1179
+ const lines = plainTextLinesOf(control);
1180
+ return lines === 'single' ? forWriter(text, 'single-line') : text;
1181
+ }
1182
+ /** An item put last in a control's content, before the inner start marker a control tracked as inserted ends with (check 30). */
1183
+ function pushBeforeMarker(items, item) {
1184
+ var _a, _b;
1185
+ const last = items[items.length - 1];
1186
+ const marker = last !== undefined && TRACKED_HOLDER_MARKER.test((_b = (_a = last.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : '') && last.name.localPart.endsWith('Start');
1187
+ items.splice(marker ? items.length - 1 : items.length, 0, item);
672
1188
  }
673
1189
  /** A `w:p` holding these run-level items (CR-002 phase E: the bound content of an empty control). */
674
1190
  function paragraphElementOf(runs) {
@@ -695,3 +1211,56 @@ function ownerOf(container, body) {
695
1211
  };
696
1212
  return (_a = seek(body.container)) !== null && _a !== void 0 ? _a : body.container;
697
1213
  }
1214
+ /**
1215
+ * Refuses, before anything changes, putting blocks into a block-level control bound by `w:dataBinding` (untyped,
1216
+ * `w:text` or `w:richText`, its node not flat OPC) that would leave it holding anything but one paragraph: Word can't
1217
+ * open such a file, and its repair loses the extra blocks (CR-002 section 61, check 56). Word's own error where
1218
+ * measured (`insertTable` `InvalidArgument`, `insertOoxml` `GeneralException`), else `NotSupportedError` (H3).
1219
+ */
1220
+ export function assertControlHolds(body, holder, container, elements, refusal, member = ['Body', 'insertElement']) {
1221
+ var _a;
1222
+ const owner = holder;
1223
+ if ((owner === null || owner === void 0 ? void 0 : owner.TYPE_NAME) !== 'org_docx4j_wml.SdtContentBlock' || ((_a = owner.PARENT) === null || _a === void 0 ? void 0 : _a.TYPE_NAME) !== 'org_docx4j_wml.SdtBlock')
1224
+ return;
1225
+ const control = controlViewOf(owner.PARENT, body);
1226
+ if (!isBoundBlockControl(control))
1227
+ return;
1228
+ const blocks = [...container, ...elements].filter((e) => { var _a; return BLOCK_LEVEL_TYPES.has((_a = typeNameOf(e)) !== null && _a !== void 0 ? _a : ''); });
1229
+ if (blocks.length === 1 && typeNameOf(blocks[0]) === 'org_docx4j_wml.P')
1230
+ return;
1231
+ const message = `this content control is bound by w:dataBinding, and Word holds a bound block control to one paragraph: it can't open `
1232
+ + `a file where one holds more, and its repair loses them (CR-002 section 61, check 56)`;
1233
+ if (refusal === 'InvalidArgument')
1234
+ throw new InvalidArgument(`Table: ${message}`);
1235
+ if (refusal === 'GeneralException')
1236
+ throw new GeneralException(`insertOoxml: ${message}`);
1237
+ throw new NotSupportedError(member[0], member[1], message);
1238
+ }
1239
+ /** The controls of `options.types`, or all of them. */
1240
+ export function controlsOfTypes(controls, options) {
1241
+ const types = options === null || options === void 0 ? void 0 : options.types;
1242
+ return types && types.length > 0 ? controls.filter((c) => types.includes(c.type)) : controls;
1243
+ }
1244
+ /**
1245
+ * Whether a control of this kind made on an empty range shows its default placeholder, as Word's does (CR-002 section
1246
+ * 63, D8; check 64; Jason's `placeholders.docx`, section 63.22): a text control, a date picker, a drop-down list or a
1247
+ * combo box. The building block and repeating section kinds are made as before.
1248
+ */
1249
+ export function showsPlaceholderWhenMade(kind) {
1250
+ return kind === undefined || kind === 'RichText' || kind === 'PlainText' || kind === 'DatePicker' || kind === 'DropDownList' || kind === 'ComboBox';
1251
+ }
1252
+ /** A stand-in for a block-level control about to be made, for `assertControlHolds` before `sdtOf` takes its content. */
1253
+ export const BLOCK_CONTROL = Object.freeze({
1254
+ name: { namespaceURI: 'http://schemas.openxmlformats.org/wordprocessingml/2006/main', localPart: 'sdt' },
1255
+ value: { TYPE_NAME: 'org_docx4j_wml.SdtBlock' },
1256
+ });
1257
+ /**
1258
+ * Whether a block-level control holds one paragraph and nothing else at block level, or nothing yet: what a bound
1259
+ * block control may hold (CR-002 section 61).
1260
+ */
1261
+ export function holdsOneParagraph(control) {
1262
+ var _a, _b;
1263
+ const content = (_b = (_a = control.sdt.sdtContent) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : [];
1264
+ const blocks = content.filter((e) => { var _a; return BLOCK_LEVEL_TYPES.has((_a = typeNameOf(e)) !== null && _a !== void 0 ? _a : ''); });
1265
+ return blocks.length === 0 || (blocks.length === 1 && typeNameOf(blocks[0]) === 'org_docx4j_wml.P');
1266
+ }