@docx4j/core-ts 0.3.1 → 0.3.2

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 (30) hide show
  1. package/dist/model/content/Body.mjs +2 -1
  2. package/dist/model/content/ContentControl.d.mts +5 -1
  3. package/dist/model/content/ContentControl.mjs +18 -5
  4. package/dist/model/content/Paragraph.d.mts +11 -11
  5. package/dist/model/content/Paragraph.mjs +30 -43
  6. package/dist/model/content/Range.mjs +3 -2
  7. package/dist/model/content/stylesFacade.mjs +7 -4
  8. package/dist/model/content/tracking.d.mts +14 -0
  9. package/dist/model/content/tracking.mjs +19 -10
  10. package/dist/model/customxml/XmlMapping.d.mts +20 -4
  11. package/dist/model/customxml/XmlMapping.mjs +44 -6
  12. package/dist/model/customxml/bindings.d.mts +9 -2
  13. package/dist/model/customxml/bindings.mjs +18 -2
  14. package/dist/model/customxml/index.d.mts +1 -1
  15. package/dist/model/customxml/index.mjs +1 -1
  16. package/dist/model/customxml/insert.d.mts +10 -0
  17. package/dist/model/customxml/insert.mjs +13 -0
  18. package/dist/model/fonts/RunFontSelector.d.mts +8 -1
  19. package/dist/model/fonts/RunFontSelector.mjs +6 -5
  20. package/dist/model/properties/PropertyResolver.d.mts +110 -10
  21. package/dist/model/properties/PropertyResolver.mjs +300 -31
  22. package/dist/model/properties/index.d.mts +3 -0
  23. package/dist/model/properties/index.mjs +2 -0
  24. package/dist/model/properties/table.d.mts +155 -0
  25. package/dist/model/properties/table.mjs +315 -0
  26. package/dist/model/properties/tableStyleConditions.d.mts +101 -0
  27. package/dist/model/properties/tableStyleConditions.mjs +296 -0
  28. package/dist/parts/wml/placeholderStyle.d.mts +3 -0
  29. package/dist/parts/wml/placeholderStyle.mjs +13 -0
  30. package/package.json +2 -2
@@ -6,7 +6,7 @@ import { Paragraph } from './Paragraph.mjs';
6
6
  import { Table } from './Table.mjs';
7
7
  import { ContentControl, collectControls } from './ContentControl.mjs';
8
8
  import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
9
- import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
9
+ import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
10
10
  import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
11
11
  import { contentOf } from './ooxml.mjs';
12
12
  import { expandReplacement } from './search.mjs';
@@ -748,6 +748,7 @@ export class Body {
748
748
  this.content.length = 0;
749
749
  this.content.push(sdt);
750
750
  linkParents(sdt, this.container);
751
+ declareSdtExtensions(this, sdt);
751
752
  // under tracking the control itself is the insertion, its content what it was (check 30)
752
753
  const tracker = this.changeTracker;
753
754
  if (tracker)
@@ -140,7 +140,11 @@ export declare class ContentControl {
140
140
  get repeatingSectionContentControl(): RepeatingSectionContentControl | undefined;
141
141
  /** The group view, for a `w:group` control (Office JS: an object with no members of its own). */
142
142
  get groupContentControl(): GroupContentControl | undefined;
143
- /** Adds or replaces a `w:sdtPr` child (extension). */
143
+ /**
144
+ * Adds or replaces a `w:sdtPr` child (extension). A `w15` element (`w15:appearance`, `w15:color`,
145
+ * `w15:dataBinding`, the repeating section) puts `w15` in the part's `mc:Ignorable`, as Word's own
146
+ * files have it: Word 2010 opens a part holding one only then (CR-002 section 40).
147
+ */
144
148
  putProperty(element: Element): void;
145
149
  /** Removes a `w:sdtPr` child (extension). */
146
150
  removeProperty(localPart: string, namespaceURI?: string): void;
@@ -6,7 +6,8 @@ import { marshalString, deepCopy } from '@docx4j/generated-objects-ts';
6
6
  // nothing from here at run time (types only), so this direction is the only one.
7
7
  import { XmlMapping } from '../customxml/XmlMapping.mjs';
8
8
  import { CheckboxContentControl, DatePickerContentControl, ListContentControl, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKBOX_FONT, } from '../customxml/kinds.mjs';
9
- import { runsForValue, updateFromControl, PLACEHOLDER_TEXT } from '../customxml/bindings.mjs';
9
+ import { runsForValue, updateFromControl, PLACEHOLDER_TEXT, PLACEHOLDER_STYLE } from '../customxml/bindings.mjs';
10
+ import { declareIgnorable } from './tracking.mjs';
10
11
  import { sdtProperty, sdtKindOf, t as textItem } from '@docx4j/generated-objects-ts/builders/wml';
11
12
  import { typeNameOf, childrenOf, textOf, runItemsOf, segmentsOf, linkParents, runOf, SDT_TYPES } from './tree.mjs';
12
13
  import { Range } from './Range.mjs';
@@ -429,7 +430,11 @@ export class ContentControl {
429
430
  get groupContentControl() {
430
431
  return sdtProperty(this.sdt.sdtPr, 'group') ? new GroupContentControl() : undefined;
431
432
  }
432
- /** Adds or replaces a `w:sdtPr` child (extension). */
433
+ /**
434
+ * Adds or replaces a `w:sdtPr` child (extension). A `w15` element (`w15:appearance`, `w15:color`,
435
+ * `w15:dataBinding`, the repeating section) puts `w15` in the part's `mc:Ignorable`, as Word's own
436
+ * files have it: Word 2010 opens a part holding one only then (CR-002 section 40).
437
+ */
433
438
  putProperty(element) {
434
439
  var _a, _b;
435
440
  var _c;
@@ -441,6 +446,8 @@ export class ContentControl {
441
446
  items[at] = element;
442
447
  else
443
448
  items.push(element);
449
+ if (element.name.namespaceURI === W15_NS)
450
+ declareIgnorable(this.parentBody.part, 'w15');
444
451
  }
445
452
  /** Removes a `w:sdtPr` child (extension). */
446
453
  removeProperty(localPart, namespaceURI) {
@@ -481,11 +488,11 @@ export class ContentControl {
481
488
  * the runs of a run-level one.
482
489
  */
483
490
  setBoundContent(runs, showingPlaceholder = false) {
484
- var _a, _b;
485
- var _c;
491
+ var _a, _b, _c, _d;
492
+ var _e;
486
493
  const sdt = this.sdt;
487
494
  (_a = sdt.sdtContent) !== null && _a !== void 0 ? _a : (sdt.sdtContent = {});
488
- const content = ((_b = (_c = sdt.sdtContent).content) !== null && _b !== void 0 ? _b : (_c.content = []));
495
+ const content = ((_b = (_e = sdt.sdtContent).content) !== null && _b !== void 0 ? _b : (_e.content = []));
489
496
  const first = content.find((e) => typeNameOf(e) !== undefined);
490
497
  if (this.form === 'Run' || (first && typeNameOf(first) === 'org_docx4j_wml.R')) {
491
498
  content.length = 0;
@@ -505,6 +512,12 @@ export class ContentControl {
505
512
  }
506
513
  }
507
514
  this.isShowingPlaceholder = showingPlaceholder;
515
+ // The placeholder run names the PlaceholderText style, which a document made here lacks: the
516
+ // package splices the definition in when it is saved, as it does a new hyperlink's style. Word
517
+ // 2010 drops a w:rStyle naming a style the part does not define (CR-002 section 40).
518
+ if (showingPlaceholder) {
519
+ (_d = (_c = this.parentBody.package_) === null || _c === void 0 ? void 0 : _c.requireStyles) === null || _d === void 0 ? void 0 : _d.call(_c, [PLACEHOLDER_STYLE]);
520
+ }
508
521
  }
509
522
  /** Shows a checkbox's glyph, as Word writes it: one run in the checkbox font (docx4j checkboxRun). */
510
523
  setCheckboxGlyph(symbol) {
@@ -1,4 +1,5 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import type { CellContext } from '../properties/table.mjs';
2
3
  import { type Element, W_NS, type TextSegment, type TextViewOptions } from './tree.mjs';
3
4
  import { Font, type FontTracking } from './Font.mjs';
4
5
  import { Range } from './Range.mjs';
@@ -95,6 +96,16 @@ export declare class Paragraph {
95
96
  get effectivePPr(): wml.PPr;
96
97
  /** The paragraph mark's effective run properties (docx4j `getEffectiveParagraphMarkRPr`). */
97
98
  get effectiveParagraphMarkRPr(): wml.RPr;
99
+ /**
100
+ * Where this paragraph sits in a table, as far as its formatting goes (docx4j
101
+ * `PropertyResolver.cellContextOf`; CR-007): the table style its table resolves to and the
102
+ * conditional formats it is under. Undefined outside a table, and in a text box, note or comment
103
+ * reached from a cell. Every effective read of the paragraph and of its runs passes it, so a run
104
+ * in a header row the table style makes bold reads bold. Each read walks the table's rows; code
105
+ * resolving every paragraph of a large table should walk with a `TableContextTracker` instead.
106
+ * Needs the package's resolver, as the effective reads do.
107
+ */
108
+ get cellContext(): CellContext | undefined;
98
109
  /** The `w:pPr` a formatting read works from: the resolved one, or this paragraph's own. */
99
110
  private readPPr;
100
111
  /**
@@ -195,17 +206,6 @@ export declare class Paragraph {
195
206
  */
196
207
  delete(): void;
197
208
  private trackedDelete;
198
- /**
199
- * A paragraph mark deleted under tracking gives the next paragraph this paragraph's properties,
200
- * as a `w:pPrChange` on it recording its own: Word, at Delete at the end of a Heading 2 with a
201
- * Normal paragraph after it, wrote the next paragraph `w:pStyle Heading2` with
202
- * `<w:pPrChange><w:pPr/></w:pPrChange>`, and No Markup reads the joined line as a heading
203
- * throughout (the editor's `measure-word15.docx`, 2026-09-30; CR-002 section 38). The Delete
204
- * counterpart of check 29's Enter. Nothing where the two paragraphs' properties are the same
205
- * (check 22's deleted marks). Accepting the mark then joins into a paragraph with these
206
- * properties, and rejecting puts the next paragraph's back.
207
- */
208
- private giveMarkPropertiesToNext;
209
209
  /**
210
210
  * The paragraph's content and its mark marked deleted, the paragraph element itself kept:
211
211
  * what the cells of a deleted table row need (a `w:tc` must keep a block-level child, and Word
@@ -15,11 +15,11 @@ import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
15
15
  import { cellOf } from './Table.mjs';
16
16
  import { ContentControl, collectRunControls } from './ContentControl.mjs';
17
17
  import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
18
- import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
18
+ import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
19
19
  import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
20
20
  import { contentOf } from './ooxml.mjs';
21
21
  import { commentApi } from './comments.mjs';
22
- import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted } from './tracking.mjs';
22
+ import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties } from './tracking.mjs';
23
23
  import { trackedChangesOfParagraph } from './TrackedChange.mjs';
24
24
  import { isListItem, listOf, listOrNullObjectOf, listItemOf, listItemOrNullObjectOf, startNewList, attachToList, detachFromList, } from './List.mjs';
25
25
  const TWIPS_PER_POINT = 20;
@@ -125,11 +125,23 @@ export class Paragraph {
125
125
  * changing it. Needs the package's resolver; see `Body.propertyResolver`.
126
126
  */
127
127
  get effectivePPr() {
128
- return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr);
128
+ return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr, this.cellContext);
129
129
  }
130
130
  /** The paragraph mark's effective run properties (docx4j `getEffectiveParagraphMarkRPr`). */
131
131
  get effectiveParagraphMarkRPr() {
132
- return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr);
132
+ return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr, this.cellContext);
133
+ }
134
+ /**
135
+ * Where this paragraph sits in a table, as far as its formatting goes (docx4j
136
+ * `PropertyResolver.cellContextOf`; CR-007): the table style its table resolves to and the
137
+ * conditional formats it is under. Undefined outside a table, and in a text box, note or comment
138
+ * reached from a cell. Every effective read of the paragraph and of its runs passes it, so a run
139
+ * in a header row the table style makes bold reads bold. Each read walks the table's rows; code
140
+ * resolving every paragraph of a large table should walk with a `TableContextTracker` instead.
141
+ * Needs the package's resolver, as the effective reads do.
142
+ */
143
+ get cellContext() {
144
+ return this.parentBody.propertyResolver.cellContextOf(this.p);
133
145
  }
134
146
  /** The `w:pPr` a formatting read works from: the resolved one, or this paragraph's own. */
135
147
  readPPr(options) {
@@ -157,6 +169,7 @@ export class Paragraph {
157
169
  else
158
170
  pPr.jc = { val };
159
171
  this.settleFormatting();
172
+ pruneParagraphProperties(this.p); // no empty w:pPr left (the editor's finding on 0.3.1)
160
173
  }
161
174
  /**
162
175
  * The alignment the paragraph resolves to with no `w:jc` of its own: its style's, else the
@@ -172,7 +185,8 @@ export class Paragraph {
172
185
  if (!resolver)
173
186
  return undefined;
174
187
  const { jc: _jc, pPrChange: _change, PARENT: _parent, ...rest } = this.p.pPr;
175
- return alignmentOf(resolver.getEffectivePPr(rest), true);
188
+ // in a table cell what is inherited includes the table style's (CR-007)
189
+ return alignmentOf(resolver.getEffectivePPr(rest, resolver.cellContextOf(this.p)), true);
176
190
  }
177
191
  /** Indents and spacing in points, as Office JS; the effective values (CR-001 Phase B step 2). */
178
192
  get leftIndent() { var _a, _b; return ((_b = (_a = this.effectivePPr.ind) === null || _a === void 0 ? void 0 : _a.left) !== null && _b !== void 0 ? _b : 0) / TWIPS_PER_POINT; }
@@ -222,7 +236,7 @@ export class Paragraph {
222
236
  const holders = () => runsOf(this.p).map((r) => r.value);
223
237
  if ((options === null || options === void 0 ? void 0 : options.direct) === true)
224
238
  return new Font(holders, () => this.fontTracking());
225
- return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
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); });
226
240
  }
227
241
  /** w14:paraId, the stable address Word gives paragraphs. */
228
242
  get paraId() {
@@ -426,42 +440,13 @@ export class Paragraph {
426
440
  return;
427
441
  }
428
442
  tracker.markParagraphDeleted(this.p);
429
- this.giveMarkPropertiesToNext(tracker);
430
- }
431
- /**
432
- * A paragraph mark deleted under tracking gives the next paragraph this paragraph's properties,
433
- * as a `w:pPrChange` on it recording its own: Word, at Delete at the end of a Heading 2 with a
434
- * Normal paragraph after it, wrote the next paragraph `w:pStyle Heading2` with
435
- * `<w:pPrChange><w:pPr/></w:pPrChange>`, and No Markup reads the joined line as a heading
436
- * throughout (the editor's `measure-word15.docx`, 2026-09-30; CR-002 section 38). The Delete
437
- * counterpart of check 29's Enter. Nothing where the two paragraphs' properties are the same
438
- * (check 22's deleted marks). Accepting the mark then joins into a paragraph with these
439
- * properties, and rejecting puts the next paragraph's back.
440
- */
441
- giveMarkPropertiesToNext(tracker) {
442
- const i = this.index;
443
- const next = i >= 0 ? this.container[i + 1] : undefined;
444
- if (!next || typeNameOf(next) !== 'org_docx4j_wml.P')
445
- return;
446
- const base = (pPr) => {
447
- const out = {};
448
- for (const [key, value] of Object.entries(pPr !== null && pPr !== void 0 ? pPr : {})) {
449
- if (key !== 'TYPE_NAME' && key !== 'PARENT' && key !== 'rPr' && key !== 'sectPr' && key !== 'pPrChange' && value !== undefined)
450
- out[key] = value;
451
- }
452
- return out;
453
- };
454
- const source = base(this.p.pPr);
455
- const nextP = next.value;
456
- if (canonical(source) === canonical(base(nextP.pPr)))
457
- return;
458
- const target = new Paragraph(next, this.container, this.parentBody).pPr();
459
- tracker.recordPPrChange(target);
460
- for (const key of Object.keys(base(target)))
461
- delete target[key];
462
- for (const [key, value] of Object.entries(source))
463
- target[key] = deepCopy(value);
464
- linkParents(target, nextP);
443
+ // Word, deleting a paragraph mark ALONE under tracking, gives the next paragraph this one's
444
+ // properties as a w:pPrChange (CR-002 section 38 item 2, the editor's measure-word15.docx), so
445
+ // that the joined line reads in them. This method deletes the whole paragraph, text and mark - the
446
+ // engine's only mark deletion, a Range being within one paragraph - and what Word writes for that
447
+ // is not measured (test/README.md check 33). 0.3.1 carried the properties here too, which turned
448
+ // an agent's new heading, put in after a paragraph it then deleted, into body text once accepted
449
+ // (the editor's finding on 0.3.1); until the check, nothing is carried.
465
450
  }
466
451
  /**
467
452
  * The paragraph's content and its mark marked deleted, the paragraph element itself kept:
@@ -533,8 +518,9 @@ export class Paragraph {
533
518
  if (!same) {
534
519
  const rest = (props) => canonical(Object.fromEntries(Object.entries(props).filter(([key]) => !FORMATTING_MEMBERS.has(key))));
535
520
  const resolver = (_b = this.parentBody.package_) === null || _b === void 0 ? void 0 : _b.propertyResolverOrUndefined;
521
+ const cell = resolver === null || resolver === void 0 ? void 0 : resolver.cellContextOf(this.p);
536
522
  same = resolver !== undefined && rest(current) === rest(before)
537
- && canonical(formattingOf(resolver.getEffectivePPr(current), true)) === canonical(formattingOf(resolver.getEffectivePPr(before), true));
523
+ && canonical(formattingOf(resolver.getEffectivePPr(current, cell), true)) === canonical(formattingOf(resolver.getEffectivePPr(before, cell), true));
538
524
  }
539
525
  if (same)
540
526
  restorePPr(this.p, recorded);
@@ -826,6 +812,7 @@ export class Paragraph {
826
812
  const sdt = sdtOf([this.element], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(this.parentBody)), form: 'block' });
827
813
  this.container.splice(at, 1, sdt);
828
814
  linkParents(sdt, (_a = this.p.PARENT) !== null && _a !== void 0 ? _a : this.parentBody.container);
815
+ declareSdtExtensions(this.parentBody, sdt);
829
816
  // under tracking the control itself is the insertion, the paragraph what it was (check 30)
830
817
  const tracker = this.changeTracker;
831
818
  if (tracker)
@@ -6,7 +6,7 @@ import { ContentControl } from './ContentControl.mjs';
6
6
  import { sdt as sdtOf, nextSdtId, applyRunOptions, t as textElement } from '@docx4j/generated-objects-ts/builders/wml';
7
7
  import { hyperlink as hyperlinkOf } from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
8
8
  import { Namespaces } from '../../parts/Namespaces.mjs';
9
- import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
9
+ import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
10
10
  import { contentOf } from './ooxml.mjs';
11
11
  import { markHolderInserted } from './tracking.mjs';
12
12
  import { searchPattern, findAll, matchesOf, expandReplacement } from './search.mjs';
@@ -74,7 +74,7 @@ export class Range {
74
74
  };
75
75
  if ((options === null || options === void 0 ? void 0 : options.direct) === true)
76
76
  return new Font(holders, () => this.paragraph.fontTracking());
77
- return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
77
+ return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr, this.paragraph.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
78
78
  }
79
79
  /** The runs the span covers (a run partly inside counts). */
80
80
  get runs() {
@@ -215,6 +215,7 @@ export class Range {
215
215
  // The control is built empty first: `sdt` refuses a kind that cannot be run-level (a repeating
216
216
  // section), and that refusal must come before any run is split.
217
217
  const sdt = sdtOf([], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(body)), form: 'run' });
218
+ declareSdtExtensions(body, sdt);
218
219
  if (this.start === this.end) {
219
220
  paragraph.insertItemsAt(this.start, [sdt]);
220
221
  return new ContentControl(sdt, containerOf(sdt, paragraph), body);
@@ -4,12 +4,14 @@ import { parseXml } from '../../xml/dom.mjs';
4
4
  import { DEFAULT_STYLES_XML } from '../../parts/wml/defaultStyles.mjs';
5
5
  import { SPLICEABLE_STYLES_XML } from '../../parts/wml/spliceableStyles.mjs';
6
6
  import { commentStyles } from '../../parts/wml/commentStyles.mjs';
7
+ import { placeholderStyle } from '../../parts/wml/placeholderStyle.mjs';
7
8
  import { Docx4JException } from '../../opc/exceptions.mjs';
8
9
  /**
9
10
  * Where a definition is looked up, unmarshalled once and never handed out (a copy is spliced in):
10
11
  * the defaults `createPackage()` writes, and then the nine styles docx4j's own `styles.xml` carries
11
12
  * **commented out** - which is why a created document has no `FootnoteText` although the file
12
- * appears to contain one, and why this facade exists (CR-002 section 22.2).
13
+ * appears to contain one, and why this facade exists (CR-002 section 22.2) - with Word's two comment
14
+ * styles and its `PlaceholderText` after them.
13
15
  */
14
16
  let sources;
15
17
  function styleSources() {
@@ -17,8 +19,9 @@ function styleSources() {
17
19
  const read = async (xml) => { var _a; return (_a = (await unmarshalNode(parseXml(xml))).value.style) !== null && _a !== void 0 ? _a : []; };
18
20
  const defaults = await read(DEFAULT_STYLES_XML);
19
21
  // and Word's two comment styles, which the comment parts add when they are made, for a caller
20
- // that makes the comment itself (CR-002 section 37)
21
- return { defaults, all: [...defaults, ...(await read(SPLICEABLE_STYLES_XML)), ...commentStyles()] };
22
+ // that makes the comment itself (CR-002 section 37), and Word's PlaceholderText, which a
23
+ // placeholder run names (CR-002 section 40)
24
+ return { defaults, all: [...defaults, ...(await read(SPLICEABLE_STYLES_XML)), ...commentStyles(), placeholderStyle()] };
22
25
  })()));
23
26
  }
24
27
  /**
@@ -102,7 +105,7 @@ function closureOf(wanted, present, source) {
102
105
  seen.add(id);
103
106
  const template = source.find((s) => s.styleId === id);
104
107
  if (template === undefined) {
105
- throw new Docx4JException(`No definition to splice for the style ${id}: neither docx4j's default styles, the nine it comments out nor the two comment styles carry one. Define it yourself on the styles part.`);
108
+ throw new Docx4JException(`No definition to splice for the style ${id}: neither docx4j's default styles, the nine it comments out, the two comment styles nor PlaceholderText carry one. Define it yourself on the styles part.`);
106
109
  }
107
110
  // what it is based on and linked to must be there too, and before it reads better in the part
108
111
  for (const dependency of [(_a = template.basedOn) === null || _a === void 0 ? void 0 : _a.val, (_b = template.link) === null || _b === void 0 ? void 0 : _b.val]) {
@@ -92,6 +92,20 @@ export declare function canonical(value: unknown): string;
92
92
  export interface TrackedPart {
93
93
  readonly contents?: unknown;
94
94
  }
95
+ /**
96
+ * Makes sure the part's root lists a prefix in `mc:Ignorable`, appending it when absent, so that
97
+ * content in that prefix's namespace may be written in it; the facade then declares the prefix.
98
+ * This is docx4j's pattern - the writer of the content adds its prefix
99
+ * (`Paginate.declareW14Ignorable`) - and Jason's decision (CR-002 section 29). Since objects 0.3.1
100
+ * (its CR-008) the marshaller does it for every writer as well; these calls stay as the writer's
101
+ * own statement. False when there is no unmarshalled root to put it on. Every part a
102
+ * `Body` is over (document, header, footer, notes, comments) has a root that binds `mc:Ignorable`.
103
+ *
104
+ * The prefix must be the facade's conventional one for its namespace (`w15`, `w16du`): an
105
+ * application that does not know the namespace (Word 2010 and `w15`) opens the part only when the
106
+ * prefix is ignorable, as Word's own files declare it (CR-002 section 40).
107
+ */
108
+ export declare function declareIgnorable(part: TrackedPart | undefined, prefix: string): boolean;
95
109
  /**
96
110
  * The per-package writer of revision markup. Created by the package while
97
111
  * `changeTrackingMode` is not `Off`; every content-API mutation asks its `Body` for one and,
@@ -105,24 +105,33 @@ export function canonical(value) {
105
105
  });
106
106
  }
107
107
  /**
108
- * Makes sure the part's root lists `w16du` in `mc:Ignorable`, appending it when absent, so that
109
- * `w16du:dateUtc` may be written in it; the facade then declares the prefix. This is docx4j's
110
- * pattern - the writer of the content adds its prefix (`Paginate.declareW14Ignorable`) - and Jason's
111
- * decision (CR-002 section 29); objects CR-008 would do it for every writer. False when there is no
112
- * unmarshalled root to put it on. Every part a `Body` is over (document, header, footer, notes,
113
- * comments) has a root that binds `mc:Ignorable`.
108
+ * Makes sure the part's root lists a prefix in `mc:Ignorable`, appending it when absent, so that
109
+ * content in that prefix's namespace may be written in it; the facade then declares the prefix.
110
+ * This is docx4j's pattern - the writer of the content adds its prefix
111
+ * (`Paginate.declareW14Ignorable`) - and Jason's decision (CR-002 section 29). Since objects 0.3.1
112
+ * (its CR-008) the marshaller does it for every writer as well; these calls stay as the writer's
113
+ * own statement. False when there is no unmarshalled root to put it on. Every part a
114
+ * `Body` is over (document, header, footer, notes, comments) has a root that binds `mc:Ignorable`.
115
+ *
116
+ * The prefix must be the facade's conventional one for its namespace (`w15`, `w16du`): an
117
+ * application that does not know the namespace (Word 2010 and `w15`) opens the part only when the
118
+ * prefix is ignorable, as Word's own files declare it (CR-002 section 40).
114
119
  */
115
- function declareDateUtc(part) {
120
+ export function declareIgnorable(part, prefix) {
116
121
  var _a;
117
122
  const contents = part === null || part === void 0 ? void 0 : part.contents;
118
123
  if (!contents || typeof contents !== 'object')
119
124
  return false;
120
125
  const root = (contents.value && typeof contents.value === 'object' ? contents.value : contents);
121
- const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((prefix) => prefix.length > 0);
122
- if (!prefixes.includes('w16du'))
123
- root.ignorable = [...prefixes, 'w16du'].join(' ');
126
+ const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((p) => p.length > 0);
127
+ if (!prefixes.includes(prefix))
128
+ root.ignorable = [...prefixes, prefix].join(' ');
124
129
  return true;
125
130
  }
131
+ /** `w16du` in the part's `mc:Ignorable`, so that `w16du:dateUtc` may be written in it. */
132
+ function declareDateUtc(part) {
133
+ return declareIgnorable(part, 'w16du');
134
+ }
126
135
  function element(localPart, value) {
127
136
  return { name: { namespaceURI: W_NS, localPart }, value };
128
137
  }
@@ -8,7 +8,9 @@ export interface CustomXmlPartLookup {
8
8
  }
9
9
  /**
10
10
  * A subset of Office JS `Word.XmlMapping`: the content control's data binding. `isMapped` is
11
- * whether the control has a `w:dataBinding`; `customXmlNode` evaluates the XPath now, so it needs
11
+ * whether the control has a `w:dataBinding` (or, on a rich text control or a repeating section,
12
+ * the `w15:dataBinding` Word writes there, which is what `setMapping` writes on one: CR-002
13
+ * section 40); `customXmlNode` evaluates the XPath now, so it needs
12
14
  * the XPath engine to be warm (`await pkg.customXmlParts.load()` once, CR-002 section 3.6).
13
15
  */
14
16
  export declare class XmlMapping {
@@ -33,13 +35,27 @@ export declare class XmlMapping {
33
35
  * Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
34
36
  * given, else the one the control is already bound to, else the first custom XML part (the
35
37
  * built-in property stores last) in which the XPath selects a node. Returns false and changes
36
- * nothing when nothing is selected, which is what Word reports.
38
+ * nothing when nothing is selected, which is what Word reports. **The node's value then goes into
39
+ * the control**, whatever text it held: Office JS's `setMapping` on a plain text control holding
40
+ * "quick brown", on an empty one and on one already holding the value, and Word's XML Mapping
41
+ * Pane mapping an existing control, each showed the node's "Ann" and left the part unchanged
42
+ * (`test/README.md` check 34, 2026-10-03; CR-002 section 39). Before, only the binding was
43
+ * written and the values stayed apart until `applyBindings`.
37
44
  */
38
45
  setMapping(xpath: string, prefixMappings?: string, part?: CustomXmlPart): boolean;
39
- /** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them. */
46
+ /** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them; the node's value goes into the control (check 34). */
40
47
  setMappingByNode(node: CustomXmlNode): boolean;
41
- /** Removes the binding (`w:dataBinding`); the control keeps the content it shows. */
48
+ /** Removes the binding (`w:dataBinding` and `w15:dataBinding`); the control keeps the content it shows. */
42
49
  delete(): void;
50
+ /**
51
+ * Whether the control's binding is `w15:dataBinding`, as Word 2013 and later write it: on a rich
52
+ * text control (one with `w:richText`, or with no kind element at all) and on a repeating
53
+ * section. `w:dataBinding` on a rich text control makes it a plain text control to Word 15 and
54
+ * Word 2010 alike - shown from its node on open, given `<w:text/>` on saving, an edit written
55
+ * back as text (`test/README.md` check 35, 1f; CR-002 section 40).
56
+ */
57
+ get usesW15(): boolean;
58
+ /** Writes the binding in the element the control's kind takes, replacing one in the other namespace where it stood. */
43
59
  private write;
44
60
  /** The parts to try, the customer's data before Word's property stores. */
45
61
  private candidates;
@@ -1,11 +1,16 @@
1
1
  import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
2
+ import * as w15el from '@docx4j/generated-objects-ts/el/org_docx4j_w15';
2
3
  import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
3
4
  import { canonicalXPathOf } from './xpath.mjs';
5
+ import { applyBindingTo } from './bindings.mjs';
4
6
  /** The Word 2012 (w15) namespace; not imported from `ContentControl`, which imports this module. */
5
7
  const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
8
+ const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
6
9
  /**
7
10
  * A subset of Office JS `Word.XmlMapping`: the content control's data binding. `isMapped` is
8
- * whether the control has a `w:dataBinding`; `customXmlNode` evaluates the XPath now, so it needs
11
+ * whether the control has a `w:dataBinding` (or, on a rich text control or a repeating section,
12
+ * the `w15:dataBinding` Word writes there, which is what `setMapping` writes on one: CR-002
13
+ * section 40); `customXmlNode` evaluates the XPath now, so it needs
9
14
  * the XPath engine to be warm (`await pkg.customXmlParts.load()` once, CR-002 section 3.6).
10
15
  */
11
16
  export class XmlMapping {
@@ -58,7 +63,12 @@ export class XmlMapping {
58
63
  * Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
59
64
  * given, else the one the control is already bound to, else the first custom XML part (the
60
65
  * built-in property stores last) in which the XPath selects a node. Returns false and changes
61
- * nothing when nothing is selected, which is what Word reports.
66
+ * nothing when nothing is selected, which is what Word reports. **The node's value then goes into
67
+ * the control**, whatever text it held: Office JS's `setMapping` on a plain text control holding
68
+ * "quick brown", on an empty one and on one already holding the value, and Word's XML Mapping
69
+ * Pane mapping an existing control, each showed the node's "Ann" and left the part unchanged
70
+ * (`test/README.md` check 34, 2026-10-03; CR-002 section 39). Before, only the binding was
71
+ * written and the values stayed apart until `applyBindings`.
62
72
  */
63
73
  setMapping(xpath, prefixMappings, part) {
64
74
  const candidates = part ? [part] : this.candidates();
@@ -67,25 +77,53 @@ export class XmlMapping {
67
77
  if (!node)
68
78
  continue;
69
79
  this.write(xpath, prefixMappings !== null && prefixMappings !== void 0 ? prefixMappings : '', candidate.id);
80
+ applyBindingTo(this.contentControl);
70
81
  return true;
71
82
  }
72
83
  return false;
73
84
  }
74
- /** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them. */
85
+ /** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them; the node's value goes into the control (check 34). */
75
86
  setMappingByNode(node) {
76
87
  const { xpath, prefixMappings } = canonicalXPathOf(node.node);
77
88
  this.write(xpath, prefixMappings, node.ownerPart.id);
89
+ applyBindingTo(this.contentControl);
78
90
  return true;
79
91
  }
80
- /** Removes the binding (`w:dataBinding`); the control keeps the content it shows. */
92
+ /** Removes the binding (`w:dataBinding` and `w15:dataBinding`); the control keeps the content it shows. */
81
93
  delete() {
82
- this.contentControl.removeProperty('dataBinding');
94
+ this.contentControl.removeProperty('dataBinding', W_NS);
95
+ this.contentControl.removeProperty('dataBinding', W15_NS);
83
96
  }
97
+ /**
98
+ * Whether the control's binding is `w15:dataBinding`, as Word 2013 and later write it: on a rich
99
+ * text control (one with `w:richText`, or with no kind element at all) and on a repeating
100
+ * section. `w:dataBinding` on a rich text control makes it a plain text control to Word 15 and
101
+ * Word 2010 alike - shown from its node on open, given `<w:text/>` on saving, an edit written
102
+ * back as text (`test/README.md` check 35, 1f; CR-002 section 40).
103
+ */
104
+ get usesW15() {
105
+ const type = this.contentControl.type;
106
+ return type === 'RichText' || type === 'RepeatingSection';
107
+ }
108
+ /** Writes the binding in the element the control's kind takes, replacing one in the other namespace where it stood. */
84
109
  write(xpath, prefixMappings, storeItemID) {
110
+ var _a, _b;
85
111
  const binding = { TYPE_NAME: 'org_docx4j_wml.CTDataBinding', xpath, storeItemID };
86
112
  if (prefixMappings !== '')
87
113
  binding.prefixMappings = prefixMappings;
88
- this.contentControl.putProperty(el.dataBinding(binding));
114
+ const w15 = this.usesW15;
115
+ const element = (w15 ? w15el.dataBinding(binding) : el.dataBinding(binding));
116
+ const items = (_a = this.contentControl.sdt.sdtPr) === null || _a === void 0 ? void 0 : _a.rPrOrAliasOrLock;
117
+ const other = w15 ? W_NS : W15_NS;
118
+ const at = (_b = items === null || items === void 0 ? void 0 : items.findIndex((i) => i.name.localPart === 'dataBinding' && i.name.namespaceURI === other)) !== null && _b !== void 0 ? _b : -1;
119
+ if (items && at >= 0) {
120
+ if (items.some((i) => i.name.localPart === 'dataBinding' && i.name.namespaceURI === element.name.namespaceURI))
121
+ items.splice(at, 1);
122
+ else
123
+ items[at] = element;
124
+ }
125
+ // putProperty also lists w15 in the part's mc:Ignorable when the element is a w15 one
126
+ this.contentControl.putProperty(element);
89
127
  }
90
128
  /** The parts to try, the customer's data before Word's property stores. */
91
129
  candidates() {
@@ -13,6 +13,13 @@ export interface BindingResult {
13
13
  /** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
14
14
  skipped: number;
15
15
  }
16
+ /**
17
+ * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
18
+ * text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
19
+ * check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
20
+ * control bound to the same node - so neither direction moves it as text (CR-002 section 40).
21
+ */
22
+ export declare function holdsFlatOpc(value: string): boolean;
16
23
  /** docx4j ValueInserterPlainTextImpl: the runs for a value, with the w:sdtPr run properties. */
17
24
  export declare function runsForValue(value: string, rPr: wml.RPr | undefined, multiLine: boolean): Element[];
18
25
  /**
@@ -24,8 +31,8 @@ export declare function runsForValue(value: string, rPr: wml.RPr | undefined, mu
24
31
  export declare function formatDate(pattern: string, date: Date, locale?: string): string;
25
32
  /**
26
33
  * Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
27
- * false when nothing was written: no binding, no part, an XPath that selects nothing, or a kind
28
- * this phase does not handle (pictures, explicit rich text).
34
+ * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
35
+ * this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
29
36
  */
30
37
  export declare function applyBindingTo(control: ContentControl): boolean;
31
38
  /** `applyBindings` over a list of controls. */
@@ -4,6 +4,16 @@ import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
4
4
  /** The style Word gives placeholder text, and the text docx4j's placeholder.xml carries. */
5
5
  export const PLACEHOLDER_STYLE = 'PlaceholderText';
6
6
  export const PLACEHOLDER_TEXT = 'Click here to enter text.';
7
+ /**
8
+ * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
9
+ * text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
10
+ * check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
11
+ * control bound to the same node - so neither direction moves it as text (CR-002 section 40).
12
+ */
13
+ export function holdsFlatOpc(value) {
14
+ return /^\s*(<\?[^>]*\?>\s*)*<([\w.-]+:)?package[\s>]/.test(value)
15
+ && value.includes('http://schemas.microsoft.com/office/2006/xmlPackage');
16
+ }
7
17
  function isTrue(value) {
8
18
  const v = value.trim().toLowerCase();
9
19
  return v === 'true' || v === '1' || v === 'on' || v === 'yes';
@@ -103,8 +113,8 @@ function dateOf(value) {
103
113
  }
104
114
  /**
105
115
  * Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
106
- * false when nothing was written: no binding, no part, an XPath that selects nothing, or a kind
107
- * this phase does not handle (pictures, explicit rich text).
116
+ * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
117
+ * this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
108
118
  */
109
119
  export function applyBindingTo(control) {
110
120
  var _a;
@@ -123,6 +133,9 @@ export function applyBindingTo(control) {
123
133
  return false;
124
134
  if (isContainer(control))
125
135
  return false;
136
+ // A Flat OPC package is a rich text control's content, not text: no control is filled with it.
137
+ if (holdsFlatOpc(value))
138
+ return false;
126
139
  if (control.type === 'CheckBox') {
127
140
  const checkbox = control.checkboxContentControl;
128
141
  if (!checkbox)
@@ -190,6 +203,9 @@ export function updateFromControl(control) {
190
203
  // A control showing its placeholder holds no value.
191
204
  if (control.isShowingPlaceholder)
192
205
  return false;
206
+ // The node holds a rich text control's content as Flat OPC: text is not written over it.
207
+ if (holdsFlatOpc(node.text))
208
+ return false;
193
209
  let value = control.text;
194
210
  if (control.type === 'CheckBox') {
195
211
  const checkbox = control.checkboxContentControl;
@@ -3,5 +3,5 @@ export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection, type Cu
3
3
  export { CustomXmlPartCollection, type CustomXmlHost } from './CustomXmlPartCollection.mjs';
4
4
  export { XmlMapping, type CustomXmlPartLookup } from './XmlMapping.mjs';
5
5
  export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
6
- export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
6
+ export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
7
7
  export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
@@ -5,5 +5,5 @@ export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection } from '
5
5
  export { CustomXmlPartCollection } from './CustomXmlPartCollection.mjs';
6
6
  export { XmlMapping } from './XmlMapping.mjs';
7
7
  export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
8
- export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
8
+ export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
9
9
  export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';