@docx4j/core-ts 0.3.0 → 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 (42) hide show
  1. package/dist/model/content/Body.mjs +40 -19
  2. package/dist/model/content/Comment.d.mts +6 -0
  3. package/dist/model/content/Comment.mjs +76 -22
  4. package/dist/model/content/ContentControl.d.mts +11 -1
  5. package/dist/model/content/ContentControl.mjs +69 -10
  6. package/dist/model/content/Paragraph.d.mts +37 -2
  7. package/dist/model/content/Paragraph.mjs +140 -38
  8. package/dist/model/content/Range.mjs +49 -5
  9. package/dist/model/content/Table.mjs +6 -8
  10. package/dist/model/content/TrackedChange.d.mts +32 -4
  11. package/dist/model/content/TrackedChange.mjs +271 -34
  12. package/dist/model/content/index.d.mts +1 -1
  13. package/dist/model/content/index.mjs +1 -1
  14. package/dist/model/content/stylesFacade.mjs +9 -3
  15. package/dist/model/content/tracking.d.mts +65 -5
  16. package/dist/model/content/tracking.mjs +170 -43
  17. package/dist/model/customxml/XmlMapping.d.mts +20 -4
  18. package/dist/model/customxml/XmlMapping.mjs +44 -6
  19. package/dist/model/customxml/bindings.d.mts +9 -2
  20. package/dist/model/customxml/bindings.mjs +18 -2
  21. package/dist/model/customxml/index.d.mts +1 -1
  22. package/dist/model/customxml/index.mjs +1 -1
  23. package/dist/model/customxml/insert.d.mts +10 -0
  24. package/dist/model/customxml/insert.mjs +13 -0
  25. package/dist/model/fonts/RunFontSelector.d.mts +8 -1
  26. package/dist/model/fonts/RunFontSelector.mjs +6 -5
  27. package/dist/model/properties/PropertyResolver.d.mts +110 -10
  28. package/dist/model/properties/PropertyResolver.mjs +300 -31
  29. package/dist/model/properties/index.d.mts +3 -0
  30. package/dist/model/properties/index.mjs +2 -0
  31. package/dist/model/properties/table.d.mts +155 -0
  32. package/dist/model/properties/table.mjs +315 -0
  33. package/dist/model/properties/tableStyleConditions.d.mts +101 -0
  34. package/dist/model/properties/tableStyleConditions.mjs +296 -0
  35. package/dist/packages/WordprocessingMLPackage.d.mts +6 -0
  36. package/dist/packages/WordprocessingMLPackage.mjs +11 -0
  37. package/dist/parts/wml/commentStyles.d.mts +3 -0
  38. package/dist/parts/wml/commentStyles.mjs +25 -0
  39. package/dist/parts/wml/comments.mjs +4 -22
  40. package/dist/parts/wml/placeholderStyle.d.mts +3 -0
  41. package/dist/parts/wml/placeholderStyle.mjs +13 -0
  42. package/package.json +2 -2
@@ -1,8 +1,9 @@
1
1
  import { deepCopy, deepCopyAsSync } from '@docx4j/generated-objects-ts';
2
- import { createCTTrackChange, createCTRPrChange, createCTRPrChangeRPr, createCTPPrChange, createDelText, createPPr, createParaRPr, createTrPr, } from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
2
+ import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
3
+ import { createCTTrackChange, createCTRPrChange, createCTRPrChangeRPr, createCTPPrChange, createCTMarkup, createDelText, createPPr, createParaRPr, createTrPr, } from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
3
4
  import { rPrToElements, rPrFromElements } from '@docx4j/generated-objects-ts/builders/wml';
4
5
  import { Docx4JException } from '../../opc/exceptions.mjs';
5
- import { W_NS, linkParents, walk, typeNameOf, revisionKindOf } from './tree.mjs';
6
+ import { W_NS, linkParents, walk, typeNameOf, revisionKindOf, childrenOf, rowsOf, cellsOf } from './tree.mjs';
6
7
  /**
7
8
  * `w:id` on these is the one annotation id space (ECMA-376 17.13.5.4): revisions, bookmarks,
8
9
  * comment range marks and permissions share it. `w:comment/@w:id` (docx4j `Comments.Comment`,
@@ -104,24 +105,33 @@ export function canonical(value) {
104
105
  });
105
106
  }
106
107
  /**
107
- * Makes sure the part's root lists `w16du` in `mc:Ignorable`, appending it when absent, so that
108
- * `w16du:dateUtc` may be written in it; the facade then declares the prefix. This is docx4j's
109
- * pattern - the writer of the content adds its prefix (`Paginate.declareW14Ignorable`) - and Jason's
110
- * decision (CR-002 section 29); objects CR-008 would do it for every writer. False when there is no
111
- * unmarshalled root to put it on. Every part a `Body` is over (document, header, footer, notes,
112
- * 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).
113
119
  */
114
- function declareDateUtc(part) {
120
+ export function declareIgnorable(part, prefix) {
115
121
  var _a;
116
122
  const contents = part === null || part === void 0 ? void 0 : part.contents;
117
123
  if (!contents || typeof contents !== 'object')
118
124
  return false;
119
125
  const root = (contents.value && typeof contents.value === 'object' ? contents.value : contents);
120
- const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((prefix) => prefix.length > 0);
121
- if (!prefixes.includes('w16du'))
122
- 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(' ');
123
129
  return true;
124
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
+ }
125
135
  function element(localPart, value) {
126
136
  return { name: { namespaceURI: W_NS, localPart }, value };
127
137
  }
@@ -294,49 +304,166 @@ export class ChangeTracker {
294
304
  }
295
305
  }
296
306
  /**
297
- * A whole paragraph inserted: the mark is marked inserted and every run of it goes into a
298
- * `w:ins`. Word, splitting a paragraph, marks the *first* paragraph's mark instead and leaves
299
- * the new one the original mark; marking the new paragraph's own mark is the same document once
307
+ * A whole paragraph inserted: the mark is marked inserted (unless it already is) and every run of it
308
+ * goes into a `w:ins` - the runs in its hyperlinks, inline content controls, fields and smart tags as
309
+ * well (`wrapNewRuns`). Word, splitting a paragraph, marks the *first* paragraph's mark instead and
310
+ * leaves the new one the original mark; marking the new paragraph's own mark is the same document once
300
311
  * accepted or rejected and keeps the edit to the element that was added (CR-002 section 13).
301
312
  */
302
313
  export function trackInsertedParagraph(tracker, p) {
303
- tracker.markParagraphInserted(p);
314
+ var _a, _b;
315
+ if (!((_b = (_a = p.pPr) === null || _a === void 0 ? void 0 : _a.rPr) === null || _b === void 0 ? void 0 : _b.ins))
316
+ tracker.markParagraphInserted(p);
304
317
  wrapNewRuns(tracker, p);
305
318
  }
306
- /** Every top-level run of the paragraph that is not already in a revision, wrapped in one `w:ins` per group. */
319
+ /**
320
+ * Every run of the paragraph not already in a revision, wrapped in one `w:ins` per group of touching
321
+ * runs - at the top level and inside the run-level holders, where Word's form decides (`test/README.md`
322
+ * check 30, CR-002 section 37 item 7): in a hyperlink or a simple field the `w:ins` goes inside, since
323
+ * neither can be in one (`CT_RunTrackChange` holds `EG_ContentRunContent` only); a smart tag goes
324
+ * *into* the `w:ins` with the runs beside it, its own runs plain; an inline content control or custom
325
+ * XML element keeps the `w:ins` inside and is itself marked inserted (`markHolderInserted`). Before,
326
+ * only the top level was wrapped, so that rejecting an inserted paragraph left a link's text behind
327
+ * (the editor's finding on 0.3.0, CR-002 section 37 item 4).
328
+ */
307
329
  export function wrapNewRuns(tracker, p) {
308
- const content = p.content;
309
- if (!content)
330
+ wrapInsertedRuns(tracker, p.content, p);
331
+ }
332
+ /** True for what goes into a `w:ins` beside the runs: a run, or a smart tag (Word's form, check 30). */
333
+ function wrapsAsRun(item) {
334
+ const tn = typeNameOf(item);
335
+ return (tn === 'org_docx4j_wml.R' || tn === 'org_docx4j_wml.CTSmartTagRun') && revisionKindOf(item) === undefined;
336
+ }
337
+ /**
338
+ * `wrapNewRuns` over run-level items that are about to go into a paragraph (`Paragraph.insertItemsAt`):
339
+ * the runs among them, and those in their holders, wrapped in `w:ins`; a content control or custom
340
+ * XML element among them marked inserted. `parent` is what the items' array belongs to.
341
+ */
342
+ export function wrapInsertedRuns(tracker, items, parent) {
343
+ var _a, _b;
344
+ if (!items)
310
345
  return;
311
- for (let i = 0; i < content.length; i++) {
312
- if (typeNameOf(content[i]) !== 'org_docx4j_wml.R' || revisionKindOf(content[i]) !== undefined)
346
+ for (let i = 0; i < items.length; i++) {
347
+ const item = items[i];
348
+ if (revisionKindOf(item) !== undefined)
349
+ continue; // already a revision
350
+ if (wrapsAsRun(item)) {
351
+ let j = i;
352
+ while (j < items.length && wrapsAsRun(items[j]))
353
+ j++;
354
+ const runs = items.splice(i, j - i);
355
+ const ins = tracker.ins(runs);
356
+ items.splice(i, 0, ins);
357
+ linkParents(ins, parent);
358
+ continue;
359
+ }
360
+ const value = item.value;
361
+ if (typeof value !== 'object' || value === null)
362
+ continue;
363
+ const holder = (_a = value.sdtContent) !== null && _a !== void 0 ? _a : value;
364
+ if (HOLDER_TYPES.has((_b = typeNameOf(item)) !== null && _b !== void 0 ? _b : '')) {
365
+ const before = items.length;
366
+ markHolderInserted(tracker, item, items, () => wrapInsertedRuns(tracker, childrenOf(value), holder));
367
+ i += items.length - before;
313
368
  continue;
314
- let j = i;
315
- while (j < content.length && typeNameOf(content[j]) === 'org_docx4j_wml.R' && revisionKindOf(content[j]) === undefined)
316
- j++;
317
- const runs = content.splice(i, j - i);
318
- const ins = tracker.ins(runs);
319
- content.splice(i, 0, ins);
320
- linkParents(ins, p);
369
+ }
370
+ wrapInsertedRuns(tracker, childrenOf(value), holder);
321
371
  }
322
372
  }
323
- /** A whole table inserted: every row is marked inserted and every paragraph in it is an insertion. */
373
+ /** A whole table inserted: every row is marked inserted and everything in its cells is an insertion (`trackInsertedBlocks`). */
324
374
  export function trackInsertedTable(tracker, tbl) {
325
- var _a, _b, _c;
326
- for (const row of ((_a = tbl.content) !== null && _a !== void 0 ? _a : [])) {
327
- if (typeNameOf(row) !== 'org_docx4j_wml.Tr')
328
- continue;
329
- const tr = row.value;
330
- tracker.markRowInserted(tr);
331
- for (const cell of ((_b = tr.content) !== null && _b !== void 0 ? _b : [])) {
332
- if (typeNameOf(cell) !== 'org_docx4j_wml.Tc')
333
- continue;
334
- for (const block of ((_c = cell.value.content) !== null && _c !== void 0 ? _c : [])) {
335
- if (typeNameOf(block) === 'org_docx4j_wml.P')
336
- trackInsertedParagraph(tracker, block.value);
337
- }
338
- }
375
+ var _a;
376
+ for (const row of rowsOf(tbl)) {
377
+ const tr = row.element.value;
378
+ if (!((_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.ins))
379
+ tracker.markRowInserted(tr);
380
+ for (const cell of cellsOf(tr))
381
+ trackInsertedBlocks(tracker, childrenOf(cell.element.value));
382
+ }
383
+ }
384
+ /**
385
+ * Block content inserted, whatever it holds: a paragraph (`trackInsertedParagraph`), a table
386
+ * (`trackInsertedTable`: rows inside row-level content controls, nested tables in its cells), and a
387
+ * block-level content control's or custom XML element's content, recursively, the element itself
388
+ * marked inserted (`markHolderInserted`; `owner` is the array holding `block`, which takes the
389
+ * markers). What `Body.insertElement` and `Table.addRows` track new content by (CR-002 section 37).
390
+ */
391
+ export function trackInsertedBlock(tracker, block, owner) {
392
+ const tn = typeNameOf(block);
393
+ if (tn === 'org_docx4j_wml.P')
394
+ trackInsertedParagraph(tracker, block.value);
395
+ else if (tn === 'org_docx4j_wml.Tbl')
396
+ trackInsertedTable(tracker, block.value);
397
+ else if (typeof block.value !== 'object' || block.value === null)
398
+ return;
399
+ else if (HOLDER_TYPES.has(tn !== null && tn !== void 0 ? tn : ''))
400
+ markHolderInserted(tracker, block, owner, () => trackInsertedBlocks(tracker, childrenOf(block.value)));
401
+ else
402
+ trackInsertedBlocks(tracker, childrenOf(block.value));
403
+ }
404
+ /** `trackInsertedBlock` over every item of a list, which is their owner. */
405
+ export function trackInsertedBlocks(tracker, items) {
406
+ if (!items)
407
+ return;
408
+ for (let i = 0; i < items.length; i++) {
409
+ const before = items.length;
410
+ trackInsertedBlock(tracker, items[i], items);
411
+ i += items.length - before; // past the markers just put in
412
+ }
413
+ }
414
+ /**
415
+ * The elements Word marks inserted as a whole with `w:customXmlInsRangeStart` / `End`: a content
416
+ * control or a custom XML element (check 30 measured the control at run and block level; a smart tag
417
+ * goes into the `w:ins` instead, and a hyperlink or field has no form of its own). Row- and cell-level
418
+ * ones are listed for the reader: `trackInsertedTable` walks through them to the rows and cells, and
419
+ * does not mark them.
420
+ */
421
+ export const HOLDER_TYPES = new Set([
422
+ 'org_docx4j_wml.SdtRun', 'org_docx4j_wml.SdtBlock', 'org_docx4j_wml.CTSdtRow', 'org_docx4j_wml.CTSdtCell',
423
+ 'org_docx4j_wml.CTCustomXmlRun', 'org_docx4j_wml.CTCustomXmlBlock', 'org_docx4j_wml.CTCustomXmlRow', 'org_docx4j_wml.CTCustomXmlCell',
424
+ ]);
425
+ /** The list a holder's content is in (`w:sdtContent`'s for a control, its own for custom XML), created when absent. */
426
+ export function holderItemsOf(value) {
427
+ var _a, _b;
428
+ var _c;
429
+ const v = value;
430
+ if (v.sdtContent)
431
+ return ((_a = (_c = v.sdtContent).content) !== null && _a !== void 0 ? _a : (_c.content = []));
432
+ if ('element' in v)
433
+ return ((_b = v.content) !== null && _b !== void 0 ? _b : (v.content = [])); // w:customXml carries w:element
434
+ return undefined;
435
+ }
436
+ /**
437
+ * A content control or custom XML element inserted under tracking, as Word writes it (check 30): a
438
+ * `w:customXmlInsRangeStart` before the element with its `w:customXmlInsRangeEnd` first inside its
439
+ * content, and a second start last inside with its end after the element. Rejecting takes the element
440
+ * away and leaves its content where it stood (`TrackedChange`'s `holder` target); without the markers
441
+ * an element holding only an insertion reads as one that was there before and was typed into, which
442
+ * rejecting keeps (check 30, existing-control-typed). `inside` tracks the content between the two
443
+ * pairs, so that the ids run in document order as Word's do.
444
+ */
445
+ export function markHolderInserted(tracker, holder, owner, inside) {
446
+ var _a;
447
+ const at = owner.indexOf(holder);
448
+ const items = holderItemsOf(holder.value);
449
+ if (at < 0 || !items) {
450
+ inside();
451
+ return;
339
452
  }
453
+ const value = holder.value;
454
+ const first = tracker.markup();
455
+ const outerStart = el.customXmlInsRangeStart(createCTTrackChange(first));
456
+ const innerEnd = el.customXmlInsRangeEnd(createCTMarkup({ id: first.id }));
457
+ owner.splice(at, 0, outerStart);
458
+ items.unshift(innerEnd);
459
+ inside();
460
+ const second = tracker.markup();
461
+ const innerStart = el.customXmlInsRangeStart(createCTTrackChange(second));
462
+ const outerEnd = el.customXmlInsRangeEnd(createCTMarkup({ id: second.id }));
463
+ items.push(innerStart);
464
+ owner.splice(owner.indexOf(holder) + 1, 0, outerEnd);
465
+ linkParents([outerStart, outerEnd], value.PARENT);
466
+ linkParents([innerEnd, innerStart], (_a = value.sdtContent) !== null && _a !== void 0 ? _a : value);
340
467
  }
341
468
  /** The paragraph mark's run properties (`w:pPr/w:rPr`), created when absent. */
342
469
  export function paraRPrOf(p) {
@@ -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';
@@ -16,5 +16,15 @@ export declare function controlIdScope(body: {
16
16
  part?: XmlPart<unknown> | undefined;
17
17
  container: object;
18
18
  }): object;
19
+ /**
20
+ * Lists `w15` in the `mc:Ignorable` of the body's part when the new control's `w:sdtPr` holds a
21
+ * `w15` element (a repeating section or its item): Word 2010 knows no `w15` and opens a part with
22
+ * one only when the prefix is ignorable (CR-002 section 40, `test/README.md` check 35).
23
+ */
24
+ export declare function declareSdtExtensions(body: {
25
+ part?: XmlPart<unknown> | undefined;
26
+ }, sdt: {
27
+ value?: unknown;
28
+ }): void;
19
29
  /** True for a run-level item (what a Range wraps). */
20
30
  export declare function isRunLevel(element: Element): boolean;
@@ -1,4 +1,6 @@
1
1
  import { typeNameOf } from '../content/tree.mjs';
2
+ import { declareIgnorable } from '../content/tracking.mjs';
3
+ const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
2
4
  /**
3
5
  * The kind the builders' `sdt` takes: Office JS's `Unknown` has no kind element, as `RichText`
4
6
  * has none (Word reports an untyped control as rich text).
@@ -15,6 +17,17 @@ export function controlIdScope(body) {
15
17
  const part = body.part;
16
18
  return (part === null || part === void 0 ? void 0 : part.isUnmarshalled) ? part.contents : body.container;
17
19
  }
20
+ /**
21
+ * Lists `w15` in the `mc:Ignorable` of the body's part when the new control's `w:sdtPr` holds a
22
+ * `w15` element (a repeating section or its item): Word 2010 knows no `w15` and opens a part with
23
+ * one only when the prefix is ignorable (CR-002 section 40, `test/README.md` check 35).
24
+ */
25
+ export function declareSdtExtensions(body, sdt) {
26
+ var _a, _b;
27
+ const items = (_b = (_a = sdt.value) === null || _a === void 0 ? void 0 : _a.sdtPr) === null || _b === void 0 ? void 0 : _b.rPrOrAliasOrLock;
28
+ if (items === null || items === void 0 ? void 0 : items.some((item) => { var _a; return ((_a = item.name) === null || _a === void 0 ? void 0 : _a.namespaceURI) === W15_NS; }))
29
+ declareIgnorable(body.part, 'w15');
30
+ }
18
31
  /** True for a run-level item (what a Range wraps). */
19
32
  export function isRunLevel(element) {
20
33
  const tn = typeNameOf(element);
@@ -1,5 +1,6 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
2
  import type { PropertyResolver } from '../properties/PropertyResolver.mjs';
3
+ import type { CellContext } from '../properties/table.mjs';
3
4
  import { type ThemeFontLang, type ThemeSource } from './ThemeFonts.mjs';
4
5
  import { type DefaultTheme } from './defaultTheme.mjs';
5
6
  /** Word's built-in default where the document defaults name no font at all (docx4j CR-016
@@ -41,6 +42,11 @@ export interface RunFontSelectorSource {
41
42
  export interface SpansOptions {
42
43
  /** True where the caller has resolved the run's effective properties already. */
43
44
  rPrIsEffective?: boolean;
45
+ /**
46
+ * The paragraph's table context (`PropertyResolver.cellContextOf(p)`, or a tracker's), so a run
47
+ * in a table cell is resolved with its table style's formatting (CR-007; docx4j CR-030 phase 5).
48
+ */
49
+ cellContext?: CellContext | undefined;
44
50
  }
45
51
  /** `w:cs` and `w:rtl` are `ST_OnOff`: present without `w:val` means true, `w:val="0"` false. */
46
52
  export declare function isOn(value: wml.BooleanDefaultTrue | undefined): boolean;
@@ -149,8 +155,9 @@ export declare class RunFontSelector {
149
155
  * at least.
150
156
  *
151
157
  * @param rPrIsEffective true where the caller has resolved the run's properties already
158
+ * @param cellContext the paragraph's table context, where it is in a table cell (CR-007)
152
159
  */
153
- documentFontFor(pPr: wml.PPr | undefined, rPr: wml.RPr | undefined, codePoint: number, rPrIsEffective?: boolean): string;
160
+ documentFontFor(pPr: wml.PPr | undefined, rPr: wml.RPr | undefined, codePoint: number, rPrIsEffective?: boolean, cellContext?: CellContext | undefined): string;
154
161
  /**
155
162
  * The run's text folded into maximal stretches of one document font.
156
163
  *
@@ -173,10 +173,10 @@ export class RunFontSelector {
173
173
  return this.defaultFont;
174
174
  }
175
175
  /** The run's effective properties, never undefined. */
176
- effectiveRPr(pPr, rPr, rPrIsEffective) {
176
+ effectiveRPr(pPr, rPr, rPrIsEffective, cellContext) {
177
177
  var _a;
178
178
  if (!rPrIsEffective && this.source.resolver !== undefined) {
179
- return (_a = this.source.resolver.getEffectiveRPr(rPr, pPr)) !== null && _a !== void 0 ? _a : {};
179
+ return (_a = this.source.resolver.getEffectiveRPr(rPr, pPr, cellContext)) !== null && _a !== void 0 ? _a : {};
180
180
  }
181
181
  return rPr !== null && rPr !== void 0 ? rPr : {};
182
182
  }
@@ -259,10 +259,11 @@ export class RunFontSelector {
259
259
  * at least.
260
260
  *
261
261
  * @param rPrIsEffective true where the caller has resolved the run's properties already
262
+ * @param cellContext the paragraph's table context, where it is in a table cell (CR-007)
262
263
  */
263
- documentFontFor(pPr, rPr, codePoint, rPrIsEffective = false) {
264
+ documentFontFor(pPr, rPr, codePoint, rPrIsEffective = false, cellContext) {
264
265
  var _a;
265
- const effective = this.effectiveRPr(pPr, rPr, rPrIsEffective);
266
+ const effective = this.effectiveRPr(pPr, rPr, rPrIsEffective, cellContext);
266
267
  return (_a = this.fontOf(this.slotsOf(pPr, effective), codePoint)) !== null && _a !== void 0 ? _a : this.defaultFont;
267
268
  }
268
269
  /**
@@ -280,7 +281,7 @@ export class RunFontSelector {
280
281
  const spans = [];
281
282
  if (text === undefined || text === null || text.length === 0)
282
283
  return spans;
283
- const effective = this.effectiveRPr(pPr, rPr, options.rPrIsEffective === true);
284
+ const effective = this.effectiveRPr(pPr, rPr, options.rPrIsEffective === true, options.cellContext);
284
285
  const bold = isOn(effective.b);
285
286
  const italic = isOn(effective.i);
286
287
  const cs = isOn(effective.cs);