@docx4j/core-ts 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +32 -4
  3. package/dist/html/convert.mjs +2 -1
  4. package/dist/html/elements.mjs +2 -1
  5. package/dist/model/color/ColorMath.d.mts +29 -0
  6. package/dist/model/color/ColorMath.mjs +146 -0
  7. package/dist/model/color/ColorTables.d.mts +6 -0
  8. package/dist/model/color/ColorTables.mjs +83 -0
  9. package/dist/model/color/DrawingMLColors.d.mts +52 -0
  10. package/dist/model/color/DrawingMLColors.mjs +374 -0
  11. package/dist/model/color/ResolvedColor.d.mts +36 -0
  12. package/dist/model/color/ResolvedColor.mjs +65 -0
  13. package/dist/model/color/SpreadsheetColors.d.mts +36 -0
  14. package/dist/model/color/SpreadsheetColors.mjs +84 -0
  15. package/dist/model/color/StyleFill.d.mts +28 -0
  16. package/dist/model/color/StyleFill.mjs +21 -0
  17. package/dist/model/color/ThemeColors.d.mts +86 -0
  18. package/dist/model/color/ThemeColors.mjs +304 -0
  19. package/dist/model/color/WordColors.d.mts +71 -0
  20. package/dist/model/color/WordColors.mjs +121 -0
  21. package/dist/model/color/index.d.mts +6 -0
  22. package/dist/model/color/index.mjs +30 -0
  23. package/dist/model/content/Body.d.mts +145 -23
  24. package/dist/model/content/Body.mjs +509 -141
  25. package/dist/model/content/Comment.d.mts +21 -3
  26. package/dist/model/content/Comment.mjs +195 -19
  27. package/dist/model/content/ContentControl.d.mts +197 -18
  28. package/dist/model/content/ContentControl.mjs +664 -95
  29. package/dist/model/content/Font.d.mts +67 -17
  30. package/dist/model/content/Font.mjs +114 -35
  31. package/dist/model/content/InlinePicture.d.mts +11 -0
  32. package/dist/model/content/InlinePicture.mjs +21 -1
  33. package/dist/model/content/List.mjs +3 -0
  34. package/dist/model/content/Paragraph.d.mts +189 -13
  35. package/dist/model/content/Paragraph.mjs +870 -74
  36. package/dist/model/content/Range.d.mts +227 -12
  37. package/dist/model/content/Range.mjs +785 -59
  38. package/dist/model/content/ResolvedView.d.mts +104 -0
  39. package/dist/model/content/ResolvedView.mjs +596 -0
  40. package/dist/model/content/Table.d.mts +29 -3
  41. package/dist/model/content/Table.mjs +109 -33
  42. package/dist/model/content/TrackedChange.d.mts +172 -13
  43. package/dist/model/content/TrackedChange.mjs +728 -167
  44. package/dist/model/content/comments.d.mts +20 -6
  45. package/dist/model/content/comments.mjs +67 -57
  46. package/dist/model/content/generalException.d.mts +18 -0
  47. package/dist/model/content/generalException.mjs +24 -0
  48. package/dist/model/content/index.d.mts +8 -3
  49. package/dist/model/content/index.mjs +7 -2
  50. package/dist/model/content/mathText.d.mts +21 -0
  51. package/dist/model/content/mathText.mjs +267 -0
  52. package/dist/model/content/officeText.d.mts +27 -0
  53. package/dist/model/content/officeText.mjs +74 -0
  54. package/dist/model/content/outline.d.mts +5 -0
  55. package/dist/model/content/outline.mjs +13 -0
  56. package/dist/model/content/placeholders.d.mts +110 -0
  57. package/dist/model/content/placeholders.mjs +253 -0
  58. package/dist/model/content/search.d.mts +72 -0
  59. package/dist/model/content/search.mjs +204 -2
  60. package/dist/model/content/spanEdits.d.mts +159 -0
  61. package/dist/model/content/spanEdits.mjs +728 -0
  62. package/dist/model/content/story.d.mts +44 -0
  63. package/dist/model/content/story.mjs +92 -0
  64. package/dist/model/content/tableColumns.d.mts +1 -1
  65. package/dist/model/content/tableColumns.mjs +108 -80
  66. package/dist/model/content/tableRevisions.d.mts +11 -3
  67. package/dist/model/content/tableRevisions.mjs +72 -42
  68. package/dist/model/content/tableRules.d.mts +30 -0
  69. package/dist/model/content/tableRules.mjs +100 -0
  70. package/dist/model/content/textBoxes.d.mts +18 -0
  71. package/dist/model/content/textBoxes.mjs +105 -0
  72. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  73. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  74. package/dist/model/content/tracking.d.mts +28 -0
  75. package/dist/model/content/tracking.mjs +149 -10
  76. package/dist/model/content/tree.d.mts +171 -22
  77. package/dist/model/content/tree.mjs +329 -53
  78. package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
  79. package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
  80. package/dist/model/customxml/XmlMapping.d.mts +1 -1
  81. package/dist/model/customxml/XmlMapping.mjs +11 -0
  82. package/dist/model/customxml/bindings.d.mts +21 -2
  83. package/dist/model/customxml/bindings.mjs +76 -32
  84. package/dist/model/customxml/kinds.d.mts +8 -3
  85. package/dist/model/customxml/kinds.mjs +26 -5
  86. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  87. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  88. package/dist/model/customxml/pictures.d.mts +4 -3
  89. package/dist/model/customxml/pictures.mjs +18 -6
  90. package/dist/model/customxml/xhtml.mjs +8 -2
  91. package/dist/model/index.d.mts +2 -0
  92. package/dist/model/index.mjs +4 -0
  93. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  94. package/dist/model/listnumbering/Emulator.mjs +63 -9
  95. package/dist/model/properties/PropertyResolver.d.mts +60 -1
  96. package/dist/model/properties/PropertyResolver.mjs +115 -2
  97. package/dist/model/properties/catalogue.d.mts +19 -5
  98. package/dist/model/properties/catalogue.mjs +41 -15
  99. package/dist/model/properties/log.d.mts +1 -1
  100. package/dist/model/properties/log.mjs +5 -5
  101. package/dist/model/properties/table.mjs +4 -2
  102. package/dist/model/text/Extractor.d.mts +289 -0
  103. package/dist/model/text/Extractor.mjs +2162 -0
  104. package/dist/model/text/LinkTargets.d.mts +45 -0
  105. package/dist/model/text/LinkTargets.mjs +170 -0
  106. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  107. package/dist/model/text/MarkdownExporter.mjs +255 -0
  108. package/dist/model/text/PlainText.d.mts +29 -0
  109. package/dist/model/text/PlainText.mjs +539 -0
  110. package/dist/model/text/TextContext.d.mts +161 -0
  111. package/dist/model/text/TextContext.mjs +425 -0
  112. package/dist/model/text/TextOptions.d.mts +150 -0
  113. package/dist/model/text/TextOptions.mjs +278 -0
  114. package/dist/model/text/TextResult.d.mts +504 -0
  115. package/dist/model/text/TextResult.mjs +526 -0
  116. package/dist/model/text/Texts.d.mts +99 -0
  117. package/dist/model/text/Texts.mjs +187 -0
  118. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  119. package/dist/model/text/WmlToMarkdown.mjs +911 -0
  120. package/dist/model/text/commonmark.d.mts +121 -0
  121. package/dist/model/text/commonmark.mjs +655 -0
  122. package/dist/model/text/index.d.mts +7 -0
  123. package/dist/model/text/index.mjs +8 -0
  124. package/dist/model/text/normalize.d.mts +8 -0
  125. package/dist/model/text/normalize.mjs +13 -0
  126. package/dist/model/text/outline.d.mts +4 -0
  127. package/dist/model/text/outline.mjs +71 -0
  128. package/dist/office-js/fragment.d.mts +7 -5
  129. package/dist/office-js/fragment.mjs +63 -14
  130. package/dist/office-js/proxy.d.mts +8 -0
  131. package/dist/office-js/proxy.mjs +29 -0
  132. package/dist/office-js/run.mjs +2 -0
  133. package/dist/office-js/supported.generated.mjs +16 -0
  134. package/dist/office-js/toApiScript.mjs +21 -5
  135. package/dist/opc/Save.d.mts +17 -0
  136. package/dist/opc/Save.mjs +12 -2
  137. package/dist/opc/index.d.mts +3 -3
  138. package/dist/opc/index.mjs +1 -1
  139. package/dist/opc/mce/preprocessor.d.mts +28 -3
  140. package/dist/opc/mce/preprocessor.mjs +4 -4
  141. package/dist/opc/mce/understood.d.mts +30 -6
  142. package/dist/opc/mce/understood.mjs +37 -7
  143. package/dist/packages/OpcPackage.d.mts +13 -0
  144. package/dist/packages/OpcPackage.mjs +1 -0
  145. package/dist/packages/WordprocessingMLPackage.d.mts +45 -8
  146. package/dist/packages/WordprocessingMLPackage.mjs +155 -20
  147. package/dist/parts/DefaultXmlPart.mjs +2 -2
  148. package/dist/parts/Part.d.mts +10 -2
  149. package/dist/parts/XmlPart.d.mts +12 -30
  150. package/dist/parts/XmlPart.mjs +44 -93
  151. package/dist/parts/wml/comments.mjs +30 -0
  152. package/dist/parts/wml/index.mjs +12 -0
  153. package/dist/xml/dom.d.mts +7 -0
  154. package/dist/xml/dom.mjs +18 -0
  155. package/package.json +2 -2
@@ -0,0 +1,71 @@
1
+ // CR-012 D3: Body.outline() and pkg.outline() read the text model. The paragraph text is the model's (hidden
2
+ // text left out, a deleted mark joined, the view's), each text box's paragraphs follow their anchor at
3
+ // `textbox:` addresses, flagged, and the options are the model's; the shape is the outline's own.
4
+ import { setOutlineReader } from '../content/outline.mjs';
5
+ import { Extractor } from './Extractor.mjs';
6
+ import { PackageTrees, TextContext } from './TextContext.mjs';
7
+ import { contextFor, optionsOf, isPackage } from './Texts.mjs';
8
+ /** The story a body's blocks are in, from its address prefix. */
9
+ function storyOf(prefix) {
10
+ const kind = prefix.split(':')[0];
11
+ switch (kind) {
12
+ case 'header': return 'HEADER';
13
+ case 'footer': return 'FOOTER';
14
+ case 'footnote': return 'FOOTNOTE';
15
+ case 'endnote': return 'ENDNOTE';
16
+ case 'comment': return 'COMMENT';
17
+ default: return 'MAIN';
18
+ }
19
+ }
20
+ /** The outline of blocks: each paragraph with its address, paraId, style and text, each table with its size; a cell's blocks after its table. */
21
+ export function outlineOfBlocks(blocks) {
22
+ const paragraphs = [];
23
+ const tables = [];
24
+ const visit = (list) => {
25
+ var _a, _b, _c;
26
+ for (const block of list) {
27
+ if (block.kind === 'PARAGRAPH') {
28
+ const entry = { address: block.address, style: (_a = block.style) !== null && _a !== void 0 ? _a : 'Normal', text: block.text };
29
+ if (block.paraId)
30
+ entry.paraId = block.paraId;
31
+ if (block.textbox)
32
+ entry.textbox = true;
33
+ if (block.joined.length > 0)
34
+ entry.joined = [...block.joined];
35
+ paragraphs.push(entry);
36
+ }
37
+ else if (block.kind === 'TABLE') {
38
+ tables.push({ address: block.address, rows: block.rows.length, cols: (_c = (_b = block.rows[0]) === null || _b === void 0 ? void 0 : _b.cells.length) !== null && _c !== void 0 ? _c : 0 });
39
+ for (const row of block.rows)
40
+ for (const cell of row.cells)
41
+ visit(cell.blocks);
42
+ }
43
+ }
44
+ };
45
+ visit(blocks);
46
+ return { paragraphs, tables };
47
+ }
48
+ setOutlineReader(async (body, options, context, blocks) => {
49
+ const o = optionsOf(options);
50
+ const pkg = body.package_;
51
+ const kind = storyOf(body.prefix);
52
+ if (!isPackage(pkg)) {
53
+ // content with no package: read as it stands, as Texts.of(content) does
54
+ const none = PackageTrees.none();
55
+ const x = new Extractor(none, o, 'planless', new TextContext(none));
56
+ if (blocks === undefined)
57
+ return outlineOfBlocks(x.extractContent(body.container, kind, body.prefix, undefined).blocks);
58
+ const starts = Extractor.fieldsAtBlocks(none, o, 'planless', new TextContext(none), body.container, kind, body.prefix, undefined);
59
+ return outlineOfBlocks(x.extractBlocks(body.container, kind, body.prefix, undefined, blocks, starts).blocks);
60
+ }
61
+ const ctx = await contextFor(pkg, context, o);
62
+ const part = body.part;
63
+ if (blocks === undefined)
64
+ return outlineOfBlocks(new Extractor(ctx.trees, o, 'view', ctx).extractContent(body.container, kind, body.prefix, part).blocks);
65
+ // some blocks (section 17.13): the plan made in the context itself, so that the siblings share it, and the
66
+ // reading under a sibling, so that it numbers no note and counts no list in the caller's context
67
+ if (o.view !== 'markup')
68
+ ctx.plan(o.view === 'accepted');
69
+ const starts = ctx.blockStarts(body.container, JSON.stringify(o.toMap()), () => Extractor.fieldsAtBlocks(ctx.trees, o, 'view', ctx, body.container, kind, body.prefix, part));
70
+ return outlineOfBlocks(new Extractor(ctx.trees, o, 'view', ctx.sibling()).extractBlocks(body.container, kind, body.prefix, part, blocks, starts).blocks);
71
+ });
@@ -1,10 +1,12 @@
1
- import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
1
  import type { Range } from '../model/content/Range.mjs';
3
2
  import type { Element } from '../model/content/tree.mjs';
4
3
  /**
5
- * A detached `w:p` holding exactly the range's text, with the runs and their formatting. The
6
- * paragraph is copied first, so the document is untouched.
4
+ * The blocks a range holds, as detached copies: within one paragraph, that paragraph trimmed to the range; across
5
+ * paragraphs, the first paragraph from the range's start, every block between whole (a table, a content control),
6
+ * and the last paragraph to the range's end, left out where the range ends at its start. The blocks are those
7
+ * `delete()` takes (`extentOf`), so a range starting or ending inside a table or a content control it doesn't hold
8
+ * whole is refused: a fragment of part of one isn't written.
7
9
  */
8
- export declare function paragraphElementOfRange(range: Range): Element<wml.P>;
9
- /** The range as a `w:p` fragment (extension; Office JS wraps a range in a pkg:package). */
10
+ export declare function blocksOfRange(range: Range): Element[];
11
+ /** The range as a fragment: its blocks' XML, one after another (extension; Office JS wraps a range in a pkg:package). */
10
12
  export declare function xmlOfRange(range: Range): Promise<string>;
@@ -1,23 +1,72 @@
1
1
  // Marshalling a view back to a fragment: what `getOoxml()` and `toApiScript`'s fallback need.
2
- // A `w:p` copy holding only a range's text is the one piece the views do not offer (Range has no
3
- // element of its own: it is a span of the paragraph's text).
2
+ // Copies of the blocks a range holds, its paragraphs trimmed to it, are the one piece the views do
3
+ // not offer (Range has no element of its own: it is a span of its paragraphs' text).
4
4
  import { deepCopy, marshalString } from '@docx4j/generated-objects-ts';
5
+ import { Body } from '../model/content/Body.mjs';
5
6
  import { Paragraph } from '../model/content/Paragraph.mjs';
7
+ import { extentOf } from '../model/content/spanEdits.mjs';
8
+ import { GeneralException } from '../model/content/generalException.mjs';
9
+ import { NotSupportedError } from './errors.mjs';
6
10
  /**
7
- * A detached `w:p` holding exactly the range's text, with the runs and their formatting. The
8
- * paragraph is copied first, so the document is untouched.
11
+ * The body a detached copy is trimmed through. It reads as the paragraph's own body does (its package's styles
12
+ * decide which runs are hidden, and so the offsets), and writes untracked: trimming a copy is no edit of the
13
+ * document, and under tracking the trim would otherwise mark the rest of the copy deleted. What the trim queues on
14
+ * the package (a picture's relationship, a comment whose reference the copy lost) is settled at save against the
15
+ * document as it stands, which still holds them.
9
16
  */
10
- export function paragraphElementOfRange(range) {
11
- const copy = deepCopy(range.paragraph.element);
12
- const view = new Paragraph(copy, [copy], range.paragraph.parentBody);
17
+ class CopyBody extends Body {
18
+ get changeTracker() {
19
+ return undefined;
20
+ }
21
+ }
22
+ /** A detached copy of `paragraph` holding only its text from `start` to `end`, with the runs and their formatting. */
23
+ function trimmed(paragraph, start, end) {
24
+ const copy = deepCopy(paragraph.element);
25
+ const own = paragraph.parentBody;
26
+ const view = new Paragraph(copy, [copy], new CopyBody(own.part, { content: [copy] }, own.prefix, own.package_));
13
27
  const length = view.text.length;
14
- if (range.end < length)
15
- view.splice(range.end, length, '');
16
- if (range.start > 0)
17
- view.splice(0, range.start, '');
28
+ if (end < length)
29
+ view.splice(end, length, '');
30
+ if (start > 0)
31
+ view.splice(0, start, '');
18
32
  return copy;
19
33
  }
20
- /** The range as a `w:p` fragment (extension; Office JS wraps a range in a pkg:package). */
21
- export function xmlOfRange(range) {
22
- return marshalString(paragraphElementOfRange(range));
34
+ /**
35
+ * The blocks a range holds, as detached copies: within one paragraph, that paragraph trimmed to the range; across
36
+ * paragraphs, the first paragraph from the range's start, every block between whole (a table, a content control),
37
+ * and the last paragraph to the range's end, left out where the range ends at its start. The blocks are those
38
+ * `delete()` takes (`extentOf`), so a range starting or ending inside a table or a content control it doesn't hold
39
+ * whole is refused: a fragment of part of one isn't written.
40
+ */
41
+ export function blocksOfRange(range) {
42
+ if (!range.spansParagraphs)
43
+ return [trimmed(range.paragraph, range.start, range.end)];
44
+ let extent;
45
+ try {
46
+ extent = extentOf(range, 'getOoxml');
47
+ }
48
+ catch (error) {
49
+ if (!(error instanceof GeneralException) && !(error instanceof NotSupportedError))
50
+ throw error;
51
+ throw new NotSupportedError('Range', 'getOoxml', "this range starts or ends inside a table or a content control it doesn't hold whole, "
52
+ + "and a fragment of part of one isn't written; take the whole table or control, or a range inside one cell");
53
+ }
54
+ const out = [];
55
+ for (let k = extent.first; k <= extent.last; k++) {
56
+ const block = extent.list[k];
57
+ if (k === extent.first && extent.head)
58
+ out.push(trimmed(extent.head, range.start, extent.head.text.length));
59
+ else if (k === extent.last && extent.tail) {
60
+ if (range.end > 0)
61
+ out.push(trimmed(extent.tail, 0, range.end));
62
+ }
63
+ else
64
+ out.push(deepCopy(block));
65
+ }
66
+ return out;
67
+ }
68
+ /** The range as a fragment: its blocks' XML, one after another (extension; Office JS wraps a range in a pkg:package). */
69
+ export async function xmlOfRange(range) {
70
+ const xml = await Promise.all(blocksOfRange(range).map((block) => marshalString(block)));
71
+ return xml.join('');
23
72
  }
@@ -23,6 +23,14 @@ export declare class ClientResult<T = unknown> {
23
23
  resolveWith(value: T): void;
24
24
  /** True once `context.sync()` has filled it. */
25
25
  get isLoaded(): boolean;
26
+ /**
27
+ * Office JS's `load` on what an asynchronous member returned: nothing to load here, so the result itself.
28
+ * With `items`, a collection a member returns asynchronously (`range.getComments()`, the comment parts
29
+ * being read first) reads as Office JS code expects: `load`, `context.sync()`, then `items` (CR-014 phase 1).
30
+ */
31
+ load(): this;
32
+ /** The collection's items, once `context.sync()` has filled the result: an asynchronous collection's. */
33
+ get items(): T extends readonly (infer I)[] ? I[] : never;
26
34
  toJSON(): {
27
35
  value: T | undefined;
28
36
  };
@@ -47,6 +47,21 @@ export class ClientResult {
47
47
  get isLoaded() {
48
48
  return this.loaded;
49
49
  }
50
+ /**
51
+ * Office JS's `load` on what an asynchronous member returned: nothing to load here, so the result itself.
52
+ * With `items`, a collection a member returns asynchronously (`range.getComments()`, the comment parts
53
+ * being read first) reads as Office JS code expects: `load`, `context.sync()`, then `items` (CR-014 phase 1).
54
+ */
55
+ load() {
56
+ return this;
57
+ }
58
+ /** The collection's items, once `context.sync()` has filled the result: an asynchronous collection's. */
59
+ get items() {
60
+ const value = this.value;
61
+ if (!Array.isArray(value))
62
+ throw new NotSupportedError('ClientResult', 'items', `the result of ${this.member}() is not a collection`);
63
+ return value;
64
+ }
50
65
  toJSON() {
51
66
  return { value: this.loaded ? this.result : undefined };
52
67
  }
@@ -219,6 +234,9 @@ export class Wrapper {
219
234
  return wrapped;
220
235
  });
221
236
  result.attach(settled);
237
+ // the next sync() reports a failure; code that throws before it (a member of the result called at once, as
238
+ // Office JS code chains `insertComment(...).getRange()`) leaves it unawaited, which must not end the process
239
+ settled.catch(() => { });
222
240
  this.context.enqueue(settled);
223
241
  return result;
224
242
  }
@@ -253,6 +271,12 @@ export class Wrapper {
253
271
  result.resolveWith(wrapped.length);
254
272
  return result;
255
273
  });
274
+ if (itemClass === 'ContentControl') {
275
+ // Office JS's ContentControlCollection lookups: the controls with the tag or title, as a collection
276
+ const matching = (key, value) => this.collection(items.filter((i) => unwrap(i)[key] === value), 'contentControls');
277
+ define('getByTag', (tag) => matching('tag', tag));
278
+ define('getByTitle', (title) => matching('title', title));
279
+ }
256
280
  define('load', () => collectionProxy);
257
281
  define('track', () => collectionProxy);
258
282
  define('untrack', () => collectionProxy);
@@ -268,6 +292,11 @@ export class Wrapper {
268
292
  if (!(prop in obj))
269
293
  throw new NotSupportedError(className, prop);
270
294
  const member2 = Reflect.get(obj, prop, obj);
295
+ // the array's own methods (map, filter, forEach) give what they give: an Office JS collection has
296
+ // none, and a map's empty result made into a collection wouldn't serialize (CR-014 phase 1)
297
+ if (typeof member2 === 'function' && Object.prototype.hasOwnProperty.call(Array.prototype, prop)) {
298
+ return member2.bind(obj);
299
+ }
271
300
  if (typeof member2 === 'function' && !/^\d+$/.test(prop)) {
272
301
  return (...args) => wrapperSelf.result(member2.apply(obj, args.map(unwrap)), prop);
273
302
  }
@@ -43,6 +43,8 @@ export class RequestContext {
43
43
  async function prepare(pkg, options) {
44
44
  const main = pkg.getMainDocumentPart();
45
45
  await main.getContents();
46
+ // a control's placeholderText reads the glossary's entries, read privately (CR-002 section 63)
47
+ await pkg.glossaryEntries.load(main.glossaryDocumentPart);
46
48
  if (options.unmarshalSideParts === false)
47
49
  return;
48
50
  const sideParts = [pkg.docPropsCorePart, pkg.docPropsExtendedPart, main.documentSettingsPart];
@@ -5,6 +5,8 @@ export const SUBSET_MEMBERS = [
5
5
  'Body.clear',
6
6
  'Body.contentControls',
7
7
  'Body.getComments',
8
+ 'Body.getContentControls',
9
+ 'Body.getRange',
8
10
  'Body.getTrackedChanges',
9
11
  'Body.inlinePictures',
10
12
  'Body.insertBreak',
@@ -23,6 +25,7 @@ export const SUBSET_MEMBERS = [
23
25
  'Comment.content',
24
26
  'Comment.creationDate',
25
27
  'Comment.delete',
28
+ 'Comment.getRange',
26
29
  'Comment.replies',
27
30
  'Comment.reply',
28
31
  'Comment.resolved',
@@ -36,6 +39,7 @@ export const SUBSET_MEMBERS = [
36
39
  'ContentControl.datePickerContentControl',
37
40
  'ContentControl.delete',
38
41
  'ContentControl.dropDownListContentControl',
42
+ 'ContentControl.getContentControls',
39
43
  'ContentControl.getRange',
40
44
  'ContentControl.groupContentControl',
41
45
  'ContentControl.id',
@@ -47,6 +51,7 @@ export const SUBSET_MEMBERS = [
47
51
  'ContentControl.removeWhenEdited',
48
52
  'ContentControl.repeatingSectionContentControl',
49
53
  'ContentControl.search',
54
+ 'ContentControl.showingPlaceholderText',
50
55
  'ContentControl.tables',
51
56
  'ContentControl.tag',
52
57
  'ContentControl.text',
@@ -57,6 +62,7 @@ export const SUBSET_MEMBERS = [
57
62
  'ContentControlListItem.displayText',
58
63
  'ContentControlListItem.index',
59
64
  'ContentControlListItem.value',
65
+ 'ContentControlOptions.types',
60
66
  'CustomXmlNode.attributes',
61
67
  'CustomXmlNode.baseName',
62
68
  'CustomXmlNode.childNodes',
@@ -151,6 +157,7 @@ export const SUBSET_MEMBERS = [
151
157
  'Paragraph.firstLineIndent',
152
158
  'Paragraph.font',
153
159
  'Paragraph.getComments',
160
+ 'Paragraph.getContentControls',
154
161
  'Paragraph.getRange',
155
162
  'Paragraph.getTrackedChanges',
156
163
  'Paragraph.insertBreak',
@@ -172,19 +179,27 @@ export const SUBSET_MEMBERS = [
172
179
  'Paragraph.startNewList',
173
180
  'Paragraph.style',
174
181
  'Paragraph.styleBuiltIn',
182
+ 'Paragraph.tableNestingLevel',
175
183
  'Paragraph.text',
176
184
  'PictureContentControl.inlinePicture',
185
+ 'Range.contentControls',
177
186
  'Range.delete',
187
+ 'Range.expandTo',
178
188
  'Range.font',
179
189
  'Range.getComments',
190
+ 'Range.getContentControls',
180
191
  'Range.getRange',
181
192
  'Range.getTrackedChanges',
182
193
  'Range.hyperlink',
183
194
  'Range.insertComment',
184
195
  'Range.insertParagraph',
185
196
  'Range.insertText',
197
+ 'Range.isEmpty',
198
+ 'Range.paragraphs',
186
199
  'Range.search',
187
200
  'Range.style',
201
+ 'Range.styleBuiltIn',
202
+ 'Range.tables',
188
203
  'Range.text',
189
204
  'RepeatingSectionContentControl.allowInsertDeleteSection',
190
205
  'RepeatingSectionContentControl.items',
@@ -196,6 +211,7 @@ export const SUBSET_MEMBERS = [
196
211
  'Table.delete',
197
212
  'Table.deleteRows',
198
213
  'Table.getCell',
214
+ 'Table.getRange',
199
215
  'Table.headerRowCount',
200
216
  'Table.rowCount',
201
217
  'Table.rows',
@@ -9,8 +9,8 @@ import { Docx4JException } from '../opc/exceptions.mjs';
9
9
  import { Body } from '../model/content/Body.mjs';
10
10
  import { Paragraph } from '../model/content/Paragraph.mjs';
11
11
  import { Range } from '../model/content/Range.mjs';
12
- import { isElement, typeNameOf, textOf } from '../model/content/tree.mjs';
13
- import { paragraphElementOfRange } from './fragment.mjs';
12
+ import { isElement, typeNameOf, textOf, selectedBranchOf, ALTERNATE_CONTENT } from '../model/content/tree.mjs';
13
+ import { blocksOfRange } from './fragment.mjs';
14
14
  import { builtInOf, displayNameOf } from '../model/content/styles.mjs';
15
15
  const TWIPS_PER_POINT = 20;
16
16
  /** Paragraph properties the verbs express; anything else sends the paragraph to `insertXml`. */
@@ -97,13 +97,23 @@ class Emitter {
97
97
  line(text) {
98
98
  this.lines.push(this.indent + text);
99
99
  }
100
- /** A block-level element: a paragraph, a table, or anything else through `insertXml`. */
100
+ /**
101
+ * A block-level element: a paragraph, a table, or anything else through `insertXml`. A kept
102
+ * `mc:AlternateContent` is its selected branch's content, as Word reads it and as its save writes it:
103
+ * `insertXml` refuses the element itself (CR-013 phase 3).
104
+ */
101
105
  async element(element) {
106
+ var _a;
102
107
  const typeName = typeNameOf(element);
103
108
  if (typeName === 'org_docx4j_wml.P')
104
109
  await this.paragraph(element);
105
110
  else if (typeName === 'org_docx4j_wml.Tbl')
106
111
  await this.table(element);
112
+ else if (typeName === ALTERNATE_CONTENT) {
113
+ this.line('// an mc:AlternateContent: its selected branch, as Word reads it');
114
+ for (const child of (_a = selectedBranchOf(element.value)) !== null && _a !== void 0 ? _a : [])
115
+ await this.element(child);
116
+ }
107
117
  else
108
118
  await this.fallback(element);
109
119
  }
@@ -117,9 +127,10 @@ class Emitter {
117
127
  await this.fallback(element, error.message);
118
128
  }
119
129
  }
120
- /** A range: its text and run formatting, as one paragraph. */
130
+ /** A range: its text and run formatting, as one paragraph; across paragraphs, each block it holds. */
121
131
  async range(range) {
122
- await this.paragraph(paragraphElementOfRange(range));
132
+ for (const block of blocksOfRange(range))
133
+ await this.element(block);
123
134
  }
124
135
  emitParagraph(p) {
125
136
  const properties = this.paragraphProperties(p);
@@ -326,10 +337,15 @@ function tableGrid(tbl) {
326
337
  const typeName = typeNameOf(item);
327
338
  if (typeName === 'org_docx4j_wml.CTTblGridCol' || typeName === 'org_docx4j_wml.TblGrid')
328
339
  continue;
340
+ // a row- or cell-level mc:AlternateContent is no plain grid: the table goes whole through insertXml (CR-013 phase 3)
341
+ if (typeName === ALTERNATE_CONTENT)
342
+ return undefined;
329
343
  if (typeName !== 'org_docx4j_wml.Tr')
330
344
  continue;
331
345
  const row = [];
332
346
  for (const cell of ((_b = item.value.content) !== null && _b !== void 0 ? _b : [])) {
347
+ if (typeNameOf(cell) === ALTERNATE_CONTENT)
348
+ return undefined;
333
349
  if (typeNameOf(cell) !== 'org_docx4j_wml.Tc')
334
350
  continue;
335
351
  const tc = cell.value;
@@ -1,5 +1,22 @@
1
1
  import type { PartSink } from './PartStore.mjs';
2
2
  import type { OpcPackage } from '../packages/OpcPackage.mjs';
3
+ /**
4
+ * A value a save wrote in its normal form, or left out (CR-015 D2 and D8; the objects facade's normaliser, its
5
+ * CR-013 phase C, docx4j CR-061's rule): `path` from the part's root, `before` as read, `after` as written, null
6
+ * where the attribute or the element was left out. The code is docx4j-python CR-018's.
7
+ */
8
+ export interface ValueNormalised {
9
+ code: 'value.normalised';
10
+ partName: string;
11
+ path: string;
12
+ before: string | undefined;
13
+ after: string | null;
14
+ message: string;
15
+ }
16
+ /** What the last save of a package did beyond writing it: each value it normalised (CR-015 D8). */
17
+ export interface SaveReport {
18
+ normalised: ValueNormalised[];
19
+ }
3
20
  /**
4
21
  * Writes a package to a sink (docx4j io3.Save): `[Content_Types].xml` first, then the package
5
22
  * relationships, then every part reachable through relationships, each once. Relationship parts
package/dist/opc/Save.mjs CHANGED
@@ -4,6 +4,13 @@ import { Docx4JException } from './exceptions.mjs';
4
4
  import { Namespaces } from '../parts/Namespaces.mjs';
5
5
  import { isExternal } from '../parts/RelationshipsPart.mjs';
6
6
  import { encodeText } from '../xml/dom.mjs';
7
+ function valueNormalised(partName, n) {
8
+ const after = n.after === null ? 'left out' : `written ${JSON.stringify(n.after)}`;
9
+ return {
10
+ code: 'value.normalised', partName, path: n.path, before: n.before, after: n.after,
11
+ message: `${partName}: ${n.path} ${n.before === undefined ? '(no value)' : JSON.stringify(n.before)} ${after}, as Word reads it`,
12
+ };
13
+ }
7
14
  /**
8
15
  * Writes a package to a sink (docx4j io3.Save): `[Content_Types].xml` first, then the package
9
16
  * relationships, then every part reachable through relationships, each once. Relationship parts
@@ -14,13 +21,16 @@ export async function savePackage(pkg, sink) {
14
21
  const saver = new Saver(pkg, sink);
15
22
  sink.put(CONTENT_TYPES_NAME, encodeText(pkg.contentTypeManager.toXml()), { contentType: ContentTypes.CONTENT_TYPES_PART });
16
23
  await saver.saveRels(pkg.relationshipsPart);
17
- return sink.finish();
24
+ const result = await sink.finish();
25
+ pkg.lastSave = { normalised: saver.normalised };
26
+ return result;
18
27
  }
19
28
  class Saver {
20
29
  constructor(pkg, sink) {
21
30
  this.pkg = pkg;
22
31
  this.sink = sink;
23
32
  this.handled = new Set();
33
+ this.normalised = [];
24
34
  }
25
35
  async saveRels(rp) {
26
36
  this.sink.put(rp.partName.storeName, await rp.getBytes(), { contentType: ContentTypes.RELATIONSHIPS_PART });
@@ -39,7 +49,7 @@ class Saver {
39
49
  this.handled.add(part.partName.key);
40
50
  let bytes;
41
51
  try {
42
- bytes = await part.getBytes();
52
+ bytes = await part.getBytes({ onNormalised: (n) => this.normalised.push(valueNormalised(part.partName.name, n)) });
43
53
  }
44
54
  catch (e) {
45
55
  throw new Docx4JException(`Problem saving part ${part.partName}`, { cause: e });
@@ -5,8 +5,8 @@ export { type PartStore, type PartSink, type PutOptions, MemoryPartStore, Memory
5
5
  export { ZipPartStore, ZipPartSink } from './ZipPartStore.mjs';
6
6
  export { FlatOpcPartStore, FlatOpcPartSink, type FlatOpcSinkOptions, PKG_NS } from './FlatOpcPartStore.mjs';
7
7
  export { loadPackage, type LoadOptions } from './Load.mjs';
8
- export { savePackage } from './Save.mjs';
9
- export { mceKeep, mcePreprocess, createMcePreprocessor, resolveAlternateContent, preprocessAlternateContent, MCE_NS, KEPT_HOSTS, type McePreprocessOptions, type McBranchesDifferWarning, type PartWarning, type MceResult } from './mce/preprocessor.mjs';
10
- export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
8
+ export { savePackage, type ValueNormalised, type SaveReport } from './Save.mjs';
9
+ export { mceKeep, mcePreprocess, createMcePreprocessor, resolveAlternateContent, preprocessAlternateContent, MCE_NS, KEPT_HOSTS, type McePreprocessOptions, type McBranchesDifferWarning, type ValueKeptWarning, type PartWarning, type MceResult } from './mce/preprocessor.mjs';
10
+ export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES, READ_ONLY_UNDERSTOOD_NAMESPACES, READ_UNDERSTOOD_NAMESPACES, READ_UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
11
11
  export { Docx4JException, InvalidFormatException, PartUnrecognisedException, PartUnmarshalException, CyclicStylesException, PropertyResolverNotCreatedException } from './exceptions.mjs';
12
12
  export { parseXml, serializeXml, serializeXmlPart, createDocument, encodeText, decodeXmlText, stripXmlDeclaration, base64Encode, base64Decode, childElements, XML_DECLARATION } from '../xml/dom.mjs';
@@ -8,6 +8,6 @@ export { FlatOpcPartStore, FlatOpcPartSink, PKG_NS } from './FlatOpcPartStore.mj
8
8
  export { loadPackage } from './Load.mjs';
9
9
  export { savePackage } from './Save.mjs';
10
10
  export { mceKeep, mcePreprocess, createMcePreprocessor, resolveAlternateContent, preprocessAlternateContent, MCE_NS, KEPT_HOSTS } from './mce/preprocessor.mjs';
11
- export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
11
+ export { UNDERSTOOD_NAMESPACES, UNDERSTOOD_PREFIXES, READ_ONLY_UNDERSTOOD_NAMESPACES, READ_UNDERSTOOD_NAMESPACES, READ_UNDERSTOOD_PREFIXES } from './mce/understood.mjs';
12
12
  export { Docx4JException, InvalidFormatException, PartUnrecognisedException, PartUnmarshalException, CyclicStylesException, PropertyResolverNotCreatedException } from './exceptions.mjs';
13
13
  export { parseXml, serializeXml, serializeXmlPart, createDocument, encodeText, decodeXmlText, stripXmlDeclaration, base64Encode, base64Decode, childElements, XML_DECLARATION } from '../xml/dom.mjs';
@@ -6,7 +6,7 @@ export declare const MCE_NS = "http://schemas.openxmlformats.org/markup-compatib
6
6
  */
7
7
  export declare const KEPT_HOSTS: ReadonlySet<string>;
8
8
  export interface McePreprocessOptions {
9
- /** Namespaces counted as understood; defaults to the object model's modules. */
9
+ /** Namespaces counted as understood; defaults to `READ_UNDERSTOOD_NAMESPACES` (CR-013 D3). */
10
10
  understood?: ReadonlySet<string>;
11
11
  /** Hosts, as `{namespace}localName`, whose mc:AlternateContent is left whole; defaults to `KEPT_HOSTS`. */
12
12
  keptHosts?: ReadonlySet<string>;
@@ -36,8 +36,33 @@ export interface McBranchesDifferWarning {
36
36
  }[];
37
37
  message: string;
38
38
  }
39
+ /**
40
+ * An attribute value of Word's own WordprocessingML that its type refuses, read as Word 365 reads it
41
+ * rather than failing the part (CR-015 D8; the objects facade's `wordInvalidValue`, its CR-013):
42
+ * `value.kept.exact` where the text, trimmed of XML whitespace, is a form its type reads (`" false"`),
43
+ * else `value.kept.invalid` (`"yes"`, `"Single"`). The names are docx4j-python CR-018's.
44
+ */
45
+ export interface ValueKeptWarning {
46
+ code: 'value.kept.exact' | 'value.kept.invalid';
47
+ partName: string | undefined;
48
+ /** Where the element starts in the part, where the DOM records it (xmldom does; a browser's DOMParser does not). */
49
+ line?: number;
50
+ column?: number;
51
+ /** The element and the attribute, as the part writes them: `w:b`, `w:val`. */
52
+ element: string;
53
+ attribute: string;
54
+ /** The attribute's text, as written. */
55
+ text: string;
56
+ /**
57
+ * What the tree holds instead: `true` for an on/off value (Word reads only exactly `false`, `off` and
58
+ * `0` as off), `'none'` for `w:u`'s on/off words, and undefined where the value was dropped, so the
59
+ * attribute reads as absent (a style's value beneath shows).
60
+ */
61
+ value: boolean | string | undefined;
62
+ message: string;
63
+ }
39
64
  /** A warning load records on a part. */
40
- export type PartWarning = McBranchesDifferWarning;
65
+ export type PartWarning = McBranchesDifferWarning | ValueKeptWarning;
41
66
  /**
42
67
  * What one pass over a DOM did. `kept` counts the elements it met and left whole; it doesn't enter a
43
68
  * branch the rule doesn't select, so an element nested there is not counted.
@@ -52,7 +77,7 @@ export interface MceResult {
52
77
  * warnings are what resolving would report. Returns them.
53
78
  */
54
79
  export declare function mceKeep(doc: Document, part?: XmlPart<unknown>): PartWarning[];
55
- /** The resolving preprocessor (`mcePreprocess: true`), over the object model's namespaces. Returns the part's warnings. */
80
+ /** The resolving preprocessor (`mcePreprocess: true`), over `READ_UNDERSTOOD_NAMESPACES`. Returns the part's warnings. */
56
81
  export declare function mcePreprocess(doc: Document, part?: XmlPart<unknown>): PartWarning[];
57
82
  /** A preprocessor over a namespace set (or kept hosts) of your own. */
58
83
  export declare function createMcePreprocessor(options: McePreprocessOptions): (doc: Document, part?: XmlPart<unknown>) => PartWarning[];
@@ -1,4 +1,4 @@
1
- import { UNDERSTOOD_NAMESPACES } from './understood.mjs';
1
+ import { READ_UNDERSTOOD_NAMESPACES } from './understood.mjs';
2
2
  export const MCE_NS = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
3
3
  /**
4
4
  * The hosts whose mc:AlternateContent is kept whole on load, as `{namespace}localName`
@@ -15,7 +15,7 @@ export const KEPT_HOSTS = new Set([
15
15
  export function mceKeep(doc, part) {
16
16
  return preprocessAlternateContent(doc, { resolve: false, partName: part === null || part === void 0 ? void 0 : part.partName.name }).warnings;
17
17
  }
18
- /** The resolving preprocessor (`mcePreprocess: true`), over the object model's namespaces. Returns the part's warnings. */
18
+ /** The resolving preprocessor (`mcePreprocess: true`), over `READ_UNDERSTOOD_NAMESPACES`. Returns the part's warnings. */
19
19
  export function mcePreprocess(doc, part) {
20
20
  return preprocessAlternateContent(doc, { partName: part === null || part === void 0 ? void 0 : part.partName.name }).warnings;
21
21
  }
@@ -27,7 +27,7 @@ export function createMcePreprocessor(options) {
27
27
  * Resolves every mc:AlternateContent under a node, outermost first, but those `KEPT_HOSTS` and the
28
28
  * nothing-selectable rule keep (see the file comment). Returns the number resolved.
29
29
  */
30
- export function resolveAlternateContent(root, understood = UNDERSTOOD_NAMESPACES) {
30
+ export function resolveAlternateContent(root, understood = READ_UNDERSTOOD_NAMESPACES) {
31
31
  return preprocessAlternateContent(root, { understood }).resolved;
32
32
  }
33
33
  /**
@@ -37,7 +37,7 @@ export function resolveAlternateContent(root, understood = UNDERSTOOD_NAMESPACES
37
37
  */
38
38
  export function preprocessAlternateContent(root, options = {}) {
39
39
  var _a, _b, _c;
40
- const understood = (_a = options.understood) !== null && _a !== void 0 ? _a : UNDERSTOOD_NAMESPACES;
40
+ const understood = (_a = options.understood) !== null && _a !== void 0 ? _a : READ_UNDERSTOOD_NAMESPACES;
41
41
  const keptHosts = (_b = options.keptHosts) !== null && _b !== void 0 ? _b : KEPT_HOSTS;
42
42
  const resolve = (_c = options.resolve) !== null && _c !== void 0 ? _c : true;
43
43
  const result = { resolved: 0, kept: 0, warnings: [] };
@@ -1,11 +1,35 @@
1
1
  export declare const UNDERSTOOD_NAMESPACES: ReadonlySet<string>;
2
2
  /**
3
- * The conventional prefixes of `UNDERSTOOD_NAMESPACES` (`NAMESPACE_PREFIXES`), which is what a reader of a
4
- * kept mc:AlternateContent matches `mc:Choice/@Requires` against (CR-010 section 11.5): the unmarshalled
5
- * model does not keep the declarations in force at the element, so the objects package's `mcBranchOf`
6
- * compares prefixes, and its default set, every prefix `NAMESPACE_PREFIXES` knows, is wider than what
7
- * load understands. Through this set a reader takes the branch the preprocessor would have resolved to,
8
- * wherever the document writes the conventional prefixes. An understood namespace with no conventional
3
+ * The conventional prefixes of `UNDERSTOOD_NAMESPACES` (`NAMESPACE_PREFIXES`), the object model's namespaces
4
+ * expressed as prefixes (CR-010 section 11.5): the unmarshalled model does not keep the declarations in
5
+ * force at the element, so the objects package's `mcBranchOf` compares prefixes, and its default set,
6
+ * every prefix `NAMESPACE_PREFIXES` knows, is wider than what load understands. The readers match
7
+ * against `READ_UNDERSTOOD_PREFIXES`, this and four more. An understood namespace with no conventional
9
8
  * prefix has no entry.
10
9
  */
11
10
  export declare const UNDERSTOOD_PREFIXES: ReadonlySet<string>;
11
+ /**
12
+ * The namespaces a reader of a kept mc:AlternateContent counts as understood beyond `UNDERSTOOD_NAMESPACES`
13
+ * (CR-010 section 15): Word 365 takes a Choice requiring any of them (measured 2026-10-20), and docx4j's
14
+ * published understood list (`understood-namespaces.txt`) has all four, among the 11 its JAXB contexts
15
+ * bind and no generated module here does. The name is CR-010's, when only the readers counted them;
16
+ * since CR-013 (D3) resolving does too (`READ_UNDERSTOOD_NAMESPACES`). Resolving turns the Choice it takes
17
+ * into typed content, which no module types here, but what Word writes in these namespaces survives a
18
+ * re-marshal: in every file seen it is attributes only (`w16du:dateUtc`, `w16sdtdh:storeItemChecksum`,
19
+ * `x14ac:dyDescent` and `x14ac:knownFonts`; `w16sdtfl` is only declared), measured 2026-10-21.
20
+ */
21
+ export declare const READ_ONLY_UNDERSTOOD_NAMESPACES: ReadonlySet<string>;
22
+ /**
23
+ * What a reader of a kept mc:AlternateContent matches `mc:Choice/@Requires` against (`selectedBranchOf`,
24
+ * docx4j's `McMode.READ`): `UNDERSTOOD_PREFIXES` and the conventional prefixes of
25
+ * `READ_ONLY_UNDERSTOOD_NAMESPACES`. A Choice requiring only these is taken by the gate alone, as REQ-302
26
+ * of the text extraction specification reads, whatever its content (CR-010 section 15).
27
+ */
28
+ export declare const READ_UNDERSTOOD_PREFIXES: ReadonlySet<string>;
29
+ /**
30
+ * What load selects by, as namespaces: `UNDERSTOOD_NAMESPACES` and `READ_ONLY_UNDERSTOOD_NAMESPACES`, the
31
+ * readers' set (`READ_UNDERSTOOD_PREFIXES`) by URI. Resolving (`mcePreprocess: true`) takes the Choice it
32
+ * names, and the default load's `mce.branches_differ` reports it as the selected branch, so a resolved load
33
+ * takes the branch the readers of a kept load take, as Word 365 does (CR-013 D3).
34
+ */
35
+ export declare const READ_UNDERSTOOD_NAMESPACES: ReadonlySet<string>;