@docx4j/core-ts 0.1.0 → 0.1.1

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 (168) hide show
  1. package/README.md +410 -10
  2. package/dist/model/content/Body.d.mts +109 -4
  3. package/dist/model/content/Body.mjs +300 -29
  4. package/dist/model/content/Comment.d.mts +78 -0
  5. package/dist/model/content/Comment.mjs +429 -0
  6. package/dist/model/content/ContentControl.d.mts +173 -0
  7. package/dist/model/content/ContentControl.mjs +638 -0
  8. package/dist/model/content/Font.d.mts +48 -8
  9. package/dist/model/content/Font.mjs +55 -13
  10. package/dist/model/content/InlinePicture.d.mts +103 -0
  11. package/dist/model/content/InlinePicture.mjs +361 -0
  12. package/dist/model/content/List.d.mts +253 -0
  13. package/dist/model/content/List.mjs +778 -0
  14. package/dist/model/content/Paragraph.d.mts +213 -16
  15. package/dist/model/content/Paragraph.mjs +611 -57
  16. package/dist/model/content/Range.d.mts +48 -4
  17. package/dist/model/content/Range.mjs +161 -4
  18. package/dist/model/content/Table.d.mts +130 -0
  19. package/dist/model/content/Table.mjs +429 -0
  20. package/dist/model/content/TrackedChange.d.mts +96 -0
  21. package/dist/model/content/TrackedChange.mjs +352 -0
  22. package/dist/model/content/comments.d.mts +122 -0
  23. package/dist/model/content/comments.mjs +248 -0
  24. package/dist/model/content/index.d.mts +13 -3
  25. package/dist/model/content/index.mjs +15 -3
  26. package/dist/model/content/ooxml.d.mts +17 -0
  27. package/dist/model/content/ooxml.mjs +214 -0
  28. package/dist/model/content/styles.d.mts +21 -0
  29. package/dist/model/content/styles.mjs +86 -0
  30. package/dist/model/content/tracking.d.mts +104 -0
  31. package/dist/model/content/tracking.mjs +313 -0
  32. package/dist/model/content/tree.d.mts +56 -9
  33. package/dist/model/content/tree.mjs +104 -31
  34. package/dist/model/customxml/CustomXmlPart.d.mts +158 -0
  35. package/dist/model/customxml/CustomXmlPart.mjs +415 -0
  36. package/dist/model/customxml/CustomXmlPartCollection.d.mts +68 -0
  37. package/dist/model/customxml/CustomXmlPartCollection.mjs +183 -0
  38. package/dist/model/customxml/XmlMapping.d.mts +46 -0
  39. package/dist/model/customxml/XmlMapping.mjs +98 -0
  40. package/dist/model/customxml/bindings.d.mts +41 -0
  41. package/dist/model/customxml/bindings.mjs +230 -0
  42. package/dist/model/customxml/index.d.mts +7 -0
  43. package/dist/model/customxml/index.mjs +9 -0
  44. package/dist/model/customxml/insert.d.mts +20 -0
  45. package/dist/model/customxml/insert.mjs +22 -0
  46. package/dist/model/customxml/kinds.d.mts +103 -0
  47. package/dist/model/customxml/kinds.mjs +267 -0
  48. package/dist/model/customxml/xpath.d.mts +52 -0
  49. package/dist/model/customxml/xpath.mjs +205 -0
  50. package/dist/model/fonts/CJKToEnglish.d.mts +2 -0
  51. package/dist/model/fonts/CJKToEnglish.mjs +16 -0
  52. package/dist/model/fonts/FontFallback.d.mts +58 -0
  53. package/dist/model/fonts/FontFallback.mjs +219 -0
  54. package/dist/model/fonts/IdentityPlusMapper.d.mts +7 -0
  55. package/dist/model/fonts/IdentityPlusMapper.mjs +19 -0
  56. package/dist/model/fonts/LanguageTagToScriptMapping.d.mts +7 -0
  57. package/dist/model/fonts/LanguageTagToScriptMapping.mjs +67 -0
  58. package/dist/model/fonts/Mapper.d.mts +190 -0
  59. package/dist/model/fonts/Mapper.mjs +481 -0
  60. package/dist/model/fonts/PhysicalFont.d.mts +34 -0
  61. package/dist/model/fonts/PhysicalFont.mjs +62 -0
  62. package/dist/model/fonts/RunFontSelector.d.mts +194 -0
  63. package/dist/model/fonts/RunFontSelector.mjs +441 -0
  64. package/dist/model/fonts/ThemeFonts.d.mts +17 -0
  65. package/dist/model/fonts/ThemeFonts.mjs +76 -0
  66. package/dist/model/fonts/defaultTheme.d.mts +37 -0
  67. package/dist/model/fonts/defaultTheme.mjs +57 -0
  68. package/dist/model/fonts/families.generated.d.mts +11 -0
  69. package/dist/model/fonts/families.generated.mjs +17 -0
  70. package/dist/model/fonts/fontsInUse.d.mts +39 -0
  71. package/dist/model/fonts/fontsInUse.mjs +183 -0
  72. package/dist/model/fonts/index.d.mts +14 -0
  73. package/dist/model/fonts/index.mjs +29 -0
  74. package/dist/model/fonts/lookup.d.mts +5 -0
  75. package/dist/model/fonts/lookup.mjs +12 -0
  76. package/dist/model/fonts/registry.d.mts +31 -0
  77. package/dist/model/fonts/registry.mjs +103 -0
  78. package/dist/model/fonts/scripts.d.mts +2 -0
  79. package/dist/model/fonts/scripts.mjs +78 -0
  80. package/dist/model/fonts/substitutions.generated.d.mts +30 -0
  81. package/dist/model/fonts/substitutions.generated.mjs +12 -0
  82. package/dist/model/fonts/themes.generated.d.mts +6 -0
  83. package/dist/model/fonts/themes.generated.mjs +11 -0
  84. package/dist/model/index.d.mts +5 -0
  85. package/dist/model/index.mjs +13 -1
  86. package/dist/model/listnumbering/Emulator.d.mts +187 -0
  87. package/dist/model/listnumbering/Emulator.mjs +332 -0
  88. package/dist/model/listnumbering/definitions.d.mts +251 -0
  89. package/dist/model/listnumbering/definitions.mjs +571 -0
  90. package/dist/model/listnumbering/formats.d.mts +25 -0
  91. package/dist/model/listnumbering/formats.mjs +320 -0
  92. package/dist/model/listnumbering/index.d.mts +4 -0
  93. package/dist/model/listnumbering/index.mjs +15 -0
  94. package/dist/model/listnumbering/state.d.mts +97 -0
  95. package/dist/model/listnumbering/state.mjs +179 -0
  96. package/dist/model/properties/PropertyResolver.d.mts +216 -0
  97. package/dist/model/properties/PropertyResolver.mjs +524 -0
  98. package/dist/model/properties/catalogue.d.mts +179 -0
  99. package/dist/model/properties/catalogue.mjs +860 -0
  100. package/dist/model/properties/index.d.mts +4 -0
  101. package/dist/model/properties/index.mjs +9 -0
  102. package/dist/model/properties/log.d.mts +7 -0
  103. package/dist/model/properties/log.mjs +25 -0
  104. package/dist/model/properties/styleUtil.d.mts +112 -0
  105. package/dist/model/properties/styleUtil.mjs +359 -0
  106. package/dist/node/index.d.mts +1 -0
  107. package/dist/node/index.mjs +12 -0
  108. package/dist/office-js/document.d.mts +111 -0
  109. package/dist/office-js/document.mjs +267 -0
  110. package/dist/office-js/enums.d.mts +187 -0
  111. package/dist/office-js/enums.mjs +176 -0
  112. package/dist/office-js/errors.d.mts +29 -0
  113. package/dist/office-js/errors.mjs +39 -0
  114. package/dist/office-js/extras.d.mts +5 -0
  115. package/dist/office-js/extras.mjs +29 -0
  116. package/dist/office-js/fragment.d.mts +10 -0
  117. package/dist/office-js/fragment.mjs +23 -0
  118. package/dist/office-js/index.d.mts +183 -0
  119. package/dist/office-js/index.mjs +54 -0
  120. package/dist/office-js/proxy.d.mts +72 -0
  121. package/dist/office-js/proxy.mjs +279 -0
  122. package/dist/office-js/run.d.mts +32 -0
  123. package/dist/office-js/run.mjs +66 -0
  124. package/dist/office-js/supported.d.mts +7 -0
  125. package/dist/office-js/supported.generated.d.mts +1 -0
  126. package/dist/office-js/supported.generated.mjs +235 -0
  127. package/dist/office-js/supported.mjs +53 -0
  128. package/dist/office-js/toApiScript.d.mts +25 -0
  129. package/dist/office-js/toApiScript.mjs +394 -0
  130. package/dist/opc/ContentTypes.d.mts +1 -0
  131. package/dist/opc/ContentTypes.mjs +1 -0
  132. package/dist/opc/DirectoryPartStore.d.mts +47 -0
  133. package/dist/opc/DirectoryPartStore.mjs +123 -0
  134. package/dist/opc/exceptions.d.mts +30 -0
  135. package/dist/opc/exceptions.mjs +38 -0
  136. package/dist/opc/index.d.mts +1 -1
  137. package/dist/opc/index.mjs +1 -1
  138. package/dist/packages/OpcPackage.d.mts +19 -1
  139. package/dist/packages/OpcPackage.mjs +29 -0
  140. package/dist/packages/PresentationMLPackage.d.mts +71 -1
  141. package/dist/packages/PresentationMLPackage.mjs +179 -0
  142. package/dist/packages/SpreadsheetMLPackage.d.mts +30 -1
  143. package/dist/packages/SpreadsheetMLPackage.mjs +78 -0
  144. package/dist/packages/WordprocessingMLPackage.d.mts +124 -2
  145. package/dist/packages/WordprocessingMLPackage.mjs +309 -2
  146. package/dist/packages/index.d.mts +1 -1
  147. package/dist/packages/index.mjs +1 -1
  148. package/dist/parts/DefaultXmlPart.d.mts +18 -0
  149. package/dist/parts/DefaultXmlPart.mjs +34 -5
  150. package/dist/parts/PartRegistry.mjs +1 -0
  151. package/dist/parts/XmlPart.d.mts +33 -0
  152. package/dist/parts/XmlPart.mjs +105 -2
  153. package/dist/parts/pml/defaults.d.mts +6 -0
  154. package/dist/parts/pml/defaults.mjs +53 -0
  155. package/dist/parts/pml/index.d.mts +66 -3
  156. package/dist/parts/pml/index.mjs +206 -7
  157. package/dist/parts/pml/placeholders.d.mts +71 -0
  158. package/dist/parts/pml/placeholders.mjs +141 -0
  159. package/dist/parts/sml/index.d.mts +19 -1
  160. package/dist/parts/sml/index.mjs +60 -1
  161. package/dist/parts/wml/comments.d.mts +1 -0
  162. package/dist/parts/wml/comments.mjs +179 -0
  163. package/dist/parts/wml/defaultNumbering.d.mts +1 -0
  164. package/dist/parts/wml/defaultNumbering.mjs +216 -0
  165. package/dist/parts/wml/index.d.mts +121 -0
  166. package/dist/parts/wml/index.mjs +291 -0
  167. package/dist/xml/dom.mjs +1 -2
  168. package/package.json +25 -4
package/README.md CHANGED
@@ -6,12 +6,18 @@ and font resolution, over the Office Open XML object model of
6
6
  [`@docx4j/generated-objects-ts`](https://github.com/plutext/docx4j-generated-objects-ts). Node,
7
7
  browsers and Word add-ins.
8
8
 
9
- Status: the Open Packaging layer, the typed parts and the packages (Phase A of
10
- [CR-001](docs/change-requests/CR-001-engine.md)) are implemented; the resolution utilities
11
- (`PropertyResolver`, list numbering, fonts: Phase B) are next.
9
+ Status: [CR-001](docs/change-requests/CR-001-engine.md), the engine, is **complete**. The Open
10
+ Packaging layer, the typed parts and the packages (Phase A); `PropertyResolver`, list numbering,
11
+ so a paragraph can be asked for the label Word would paint, and font selection, so a run's font
12
+ resolves through the theme, with paragraph and run reads reporting the formatting that actually
13
+ applies (Phase B); the pptx and xlsx packages with `createPackage()`, the unzipped-directory
14
+ container, `clone()`, and the guides and examples below (Phase C). Every resolution answer is
15
+ held to docx4j's own on 45 documents (`test/golden/`, made by the Java harness in `test/java/`):
16
+ the resolver, the emulator and the selector agree with docx4j `VERSION_17_1_1` exactly.
12
17
 
13
18
  ```
14
19
  npm install @docx4j/core-ts
20
+ npm install xpath # optional, Node only: XPath over custom XML parts (browsers use document.evaluate)
15
21
  ```
16
22
 
17
23
  ```ts
@@ -39,11 +45,28 @@ const ooxml = await pkg.saveFlatOpc(); // pkg:package, for insertOo
39
45
  A part that is never touched is written back byte for byte; `getContents()` (or a view over
40
46
  it) marks a part for re-marshalling. `mc:AlternateContent` is resolved when a part is
41
47
  unmarshalled, as Word does on open (`{ mcePreprocess: false }` to keep it). Subpaths
42
- `@docx4j/core-ts/opc`, `/parts`, `/packages` and `/model` give the layers separately.
48
+ `@docx4j/core-ts/opc`, `/parts`, `/packages` and `/model` give the layers separately,
49
+ `/office-js` the `Word` shim (below), and `/node` the one Node-only piece (the unzipped-directory
50
+ container; nothing else in the package imports a `node:` builtin, so a browser or add-in bundle
51
+ never sees one).
52
+
53
+ ### Guides
54
+
55
+ | | |
56
+ |---|---|
57
+ | [Getting started in Node](docs/guides/getting-started-node.md) | install, load, the content API, save |
58
+ | [In a Word add-in](docs/guides/office-addin.md) | flat OPC in and out, testing add-in code in Node, bundling |
59
+ | [pptx and xlsx](docs/guides/presentationml-spreadsheetml.md) | what the PresentationML and SpreadsheetML packages give |
60
+ | [Parity with docx4j](docs/guides/parity.md) | what the goldens are, and running the harness against a newer docx4j |
61
+
62
+ Runnable code: [`examples/node/`](examples/node) (create, report, pptx, xlsx, an unzipped
63
+ directory) and [`examples/office-addin/`](examples/office-addin) (a sideloadable Word task pane
64
+ whose edit also runs in Node). `test/examples.test.mjs` runs them all, so they cannot rot.
43
65
 
44
66
  ### The content API
45
67
 
46
- `Body`, `Paragraph`, `Range` and `Font` follow Office JS's `Word.*` shapes (a compile-time
68
+ `Body`, `Paragraph`, `Range`, `Font`, `Table`, `InlinePicture` and `ContentControl` follow
69
+ Office JS's `Word.*` shapes (a compile-time
47
70
  check keeps them assignable to a subset of those types), so add-in code runs against a
48
71
  package with its `load` and `sync` lines removed. Hello World, from nothing to a `.docx`:
49
72
 
@@ -61,7 +84,7 @@ Editing what is there:
61
84
  ```ts
62
85
  const body = await pkg.getBody();
63
86
  const title = body.insertParagraph('Report', 'Start');
64
- title.styleBuiltIn = 'Heading 1';
87
+ title.styleBuiltIn = 'Heading1'; // Word.Style value; title.style reads 'Heading 1'
65
88
  title.alignment = 'Centered';
66
89
  const [hit] = body.search('quick brown fox'); // across runs
67
90
  hit.font.italic = true; // runs split at the boundaries
@@ -84,6 +107,129 @@ Docx4j's names are there as aliases (`addParagraphOfText`, `addStyledParagraphOf
84
107
  `addObject`, `getContent`), and the tree stays reachable: `paragraph.p` is the `P`,
85
108
  `body.content` the live array.
86
109
 
110
+ ### List numbering
111
+
112
+ `Emulator` is docx4j's list-numbering emulator: the label Word would paint in front of a
113
+ paragraph, given its `w:pPr`. Counters live in a `NumberingState`, one per **story** — Word
114
+ numbers the body, the headers and footers together, each notes part and each text box from their
115
+ own counters — so a caller walks a story in document order with the state for it.
116
+
117
+ ```ts
118
+ import { WordprocessingMLPackage, Emulator, NumberingStates } from '@docx4j/core-ts';
119
+
120
+ const pkg = await WordprocessingMLPackage.load(bytes);
121
+ const emulator = await pkg.getNumberingEmulator(); // undefined when the document has no lists
122
+ const states = new NumberingStates();
123
+ const main = pkg.getMainDocumentPart();
124
+ const state = states.forPart(main); // states.forPart(headerPart), states.newStory(), ...
125
+
126
+ for (const paragraph of (await main.getBody()).paragraphs) {
127
+ const label = emulator?.getNumber(paragraph.p.pPr, state);
128
+ if (label) console.log(label.numString, label.isBullet, label.indResolved?.left);
129
+ }
130
+
131
+ Emulator.peek(pkg, pPr, state); // the same answer, without taking the number
132
+ Emulator.numRefFor(pkg, pPr); // which list and level, or why Word numbers nothing
133
+ pkg.numberingDefinitionsPart.getIndOf('3', '0'); // the indent that level contributes
134
+ ```
135
+
136
+ Reading labels does not unmarshal `word/numbering.xml`, so a document you only read still saves
137
+ byte for byte. After editing the numbering or styles parts, `await pkg.refresh()`.
138
+
139
+ ### Lists
140
+
141
+ `Word.List` and `Word.ListItem` over the same numbering, so an add-in's list code runs here
142
+ unchanged. A paragraph knows whether Word numbers it — through its own `w:numPr` or through a
143
+ numbered style — which list it is in and what label it shows.
144
+
145
+ ```ts
146
+ const body = await pkg.getBody();
147
+
148
+ for (const p of body.paragraphs) {
149
+ if (p.isListItem) console.log(p.listItem.listString, p.listItem.level, p.text);
150
+ }
151
+
152
+ body.lists; // every list a paragraph of this body names, in order of first use
153
+ body.listLabels(); // Map<P, { listString, level, numId, siblingIndex, isBullet }>, in one walk
154
+ ```
155
+
156
+ `listItem.listString` is the label Word paints, counted by walking the paragraph's story from
157
+ its start — Word's numbering is a running count, so that walk is the answer. Use
158
+ `body.listLabels()` when you want every label at once.
159
+
160
+ Making and changing lists:
161
+
162
+ ```ts
163
+ const list = await p.startNewList(); // { bullet: true } for the bullet set
164
+ p2.attachToList(list.id, 1); // join it, at level 1
165
+ p2.listItem.level = 2; // or move it
166
+ p3.detachFromList();
167
+
168
+ list.setLevelNumbering(0, 'UpperRoman', ['(', 0, ')']); // w:numFmt and w:lvlText "(%1)"
169
+ list.setLevelBullet(1, 'Square');
170
+ list.setLevelIndents(0, 36, 18); // points, as Office JS
171
+ list.setLevelStartingNumber(0, 5);
172
+
173
+ const again = p4.restartList(); // the same list, numbering again from its start
174
+ ```
175
+
176
+ A level writer changes the `w:abstractNum`, so it copies that definition first when another
177
+ `w:num` shares it — the change stays local to this list. `startNewList()` is asynchronous here
178
+ (Office JS's is not): it copies its definition from docx4j's default `numbering.xml` and adds
179
+ the numbering part when the document has none.
180
+
181
+ ### Change tracking
182
+
183
+ `pkg.changeTrackingMode` is Office JS's `document.changeTrackingMode`, backed by
184
+ `w:trackRevisions` in the settings part. While it is on, every edit made through the content
185
+ API writes Word's revision markup instead of changing the tree in place: an insertion becomes
186
+ `w:ins`, a deletion `w:del` with `w:delText`, a replacement a `w:del` followed by a `w:ins`, a
187
+ new paragraph's mark `w:pPr/w:rPr/w:ins`, a deleted one `w:pPr/w:rPr/w:del`, and a formatting
188
+ change keeps what was there in `w:rPrChange` or `w:pPrChange`. The author is `pkg.author` and
189
+ the date `pkg.trackedChangeDate` (now, unless you fix one).
190
+
191
+ ```ts
192
+ pkg.author = { name: 'Ada Lovelace' }; // the same author phase G's comments use
193
+ pkg.changeTrackingMode = 'TrackAll'; // await pkg.setChangeTrackingMode(...) on a loaded package
194
+ body.search('quick brown fox')[0].insertText('slow red fox', 'Replace');
195
+ body.replaceText('colour', 'color'); // returns the number replaced, tracked like any other edit
196
+
197
+ body.text; // the accepted view: w:ins in, w:del out
198
+ body.getText({ view: 'original' }); // the other way round
199
+
200
+ for (const change of body.getTrackedChanges()) {
201
+ change.type; // 'Added' | 'Deleted' | 'Formatted'
202
+ change.author; change.date; change.text;
203
+ change.getRange(); // where it is
204
+ }
205
+ body.acceptAll(); // or rejectAll(), or accept()/reject() one at a time
206
+ ```
207
+
208
+ `getTrackedChanges()` is also on `Paragraph` and `Range`. Text and searches read the accepted
209
+ view throughout, so an agent sees the document as it will read once the changes are taken.
210
+
211
+ ### Comments
212
+
213
+ `getComments()` on the body, a paragraph or a range, and `insertComment` on a range or a
214
+ paragraph, over the four parts Word writes (`w:comments`, `w15:commentsEx`, `w16cid` and
215
+ `w:people`, plus `w16cex` when the document has one); threads, `resolved` (`w15:done`) and
216
+ `delete()` keep them all in step, and any of the parts the document lacks is created with its
217
+ relationship, content type and the `CommentText` and `CommentReference` styles.
218
+
219
+ ```ts
220
+ pkg.author = { name: 'Ada Lovelace', initials: 'AL', email: 'ada@example.com' }; // whose comments these are
221
+
222
+ const [hit] = body.search('quick brown fox');
223
+ const comment = await hit.insertComment('Is this the right idiom?');
224
+ await comment.reply('Yes, Word writes it this way');
225
+ comment.resolved = true; // w15:done
226
+
227
+ for (const c of await body.getComments()) { // document order, replies nested
228
+ console.log(c.authorName, c.creationDate, c.content, c.replies.length, c.getRange().map((r) => r.text));
229
+ if (c.resolved) await c.delete(); // the comment, its replies, the markers and every side entry
230
+ }
231
+ ```
232
+
87
233
  ### XML in
88
234
 
89
235
  A whole `word/document.xml` becomes the main document part's contents with `setXml`, and a
@@ -107,18 +253,272 @@ import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
107
253
  body.addObject(el.p({ content: [el.r({ content: [el.t({ value: 'Hello World' })] })] }));
108
254
  ```
109
255
 
110
- Tables, pictures, `insertOoxml` from a `pkg:package`, custom XML parts, XML mapping and typed
111
- content controls are the next phases of
112
- [CR-002](docs/change-requests/CR-002-content-api.md); effective formatting (styles resolved,
113
- as Office JS reports it) comes with [CR-001](docs/change-requests/CR-001-engine.md) Phase B.
256
+ ### Tables, pictures and content controls
257
+
258
+ `Table`, `TableRow` and `TableCell` are Office JS shapes too; a cell is a `Body` of its own, so
259
+ everything above works inside one:
260
+
261
+ ```ts
262
+ const table = body.insertTable(3, 2, 'End', [['Item', 'Price'], ['apples', '$20']]);
263
+ table.styleBuiltIn = 'TableGrid'; // Word.Style value; table.style is then 'Table Grid', table.styleId 'TableGrid'
264
+ table.headerRowCount = 1; // w:tblHeader: the row repeats on every page
265
+ table.getCell(1, 1).value = '$25';
266
+ table.addRows('End', 1, [['pears', '$30']]);
267
+ table.values; // [['Item', 'Price'], ['apples', '$25'], ['pears', '$30']]
268
+ body.paragraphs[4].parentTableCell?.cellIndex;
269
+ ```
270
+
271
+ A picture adds its own `ImagePart`, the relationship and a `w:drawing` sized from the image's
272
+ pixels and resolution (96 dpi when the file declares none, as docx4j does), scaled down if it
273
+ is wider than the text area:
274
+
275
+ ```ts
276
+ const picture = body.insertInlinePictureFromBase64(pngBase64, 'End', { altTextDescription: 'The logo' });
277
+ picture.width = 144; // points; the height follows the aspect ratio
278
+ picture.imageFormat; // 'Png'
279
+ await body.inlinePictures[0].getBase64();
280
+ ```
281
+
282
+ `insertOoxml` takes what Word's does, a flat OPC `pkg:package` string: the body content comes
283
+ over with the parts it references (images, embedded objects) copied in under fresh names and
284
+ relationship ids. A bare fragment works too, as `insertXml`.
285
+
286
+ ```ts
287
+ await body.insertOoxml(await other.saveFlatOpc(), 'End'); // or an add-in's getOoxml() string
288
+ ```
289
+
290
+ Content controls (`w:sdt`) are read at block, row, cell and run level:
291
+
292
+ ```ts
293
+ for (const control of body.contentControls) {
294
+ console.log(control.form, control.type, control.title, control.tag, control.text);
295
+ }
296
+ body.contentControls[0].insertText('Jane Doe', 'Replace');
297
+ body.contentControls[1].delete(true); // unwrap, keeping what it held
298
+ ```
299
+
300
+ `Font` and `Paragraph` reads report **effective** formatting - the document defaults, the style
301
+ chain and the numbering level resolved, as Office JS reports it - through docx4j's
302
+ `PropertyResolver` (CR-001 Phase B step 2). Writes are always direct formatting, and
303
+ `paragraph.getFont({ direct: true })`, `range.getFont({ direct: true })` and
304
+ `paragraph.formatting({ direct: true })` read the direct values.
305
+
306
+ ```ts
307
+ const p = (await pkg.getBody()).paragraphs[0];
308
+ p.font.size; // 14: the style's w:sz, not the run's
309
+ p.getFont({ direct: true }).size; // 0: the run states none
310
+ p.alignment; // 'Left' when nothing states a w:jc, as Word lays it out
311
+ p.formatting({ direct: true }).alignment; // 'Unknown'
312
+ p.effectivePPr; // docx4j's resolved w:pPr, for anything the view does not cover
313
+ ```
314
+
315
+ ### Custom XML and content controls
316
+
317
+ `pkg.customXmlParts` is Office JS's `CustomXmlPartCollection` over the custom XML data storage
318
+ parts, with XPath 1.0 over their DOM: `document.evaluate` in a browser or a Word add-in, the
319
+ optional peer dependency [`xpath`](https://www.npmjs.com/package/xpath) in Node (`npm install
320
+ xpath`; `pkg.xpathEngine` takes any other engine). One `await` warms it, and the node model is
321
+ synchronous from then on:
322
+
323
+ ```ts
324
+ const parts = await pkg.customXmlParts.load(); // parses the DOMs, warms the XPath engine
325
+ const data = pkg.customXmlParts.getItem('{8B049945-9DFE-4726-9DE9-CF5691E53858}');
326
+ data.selectSingleNode('/invoice/customer/name').text; // 'Joe Bloggs'
327
+ data.selectNodes('/invoice/items/item').map((n) => n.text);
328
+ data.documentElement.appendChildNode('<note>paid</note>');
329
+ ```
330
+
331
+ A content control's `xmlMapping` is its `w:dataBinding`, and the two directions are docx4j's,
332
+ under docx4j's names:
333
+
334
+ ```ts
335
+ const control = (await pkg.getBody()).contentControls[0];
336
+ control.xmlMapping.isMapped; // true
337
+ control.xmlMapping.xpath; // '/invoice[1]/customer[1]/name[1]'
338
+ control.xmlMapping.customXmlNode.text; // 'Joe Bloggs'
339
+
340
+ await pkg.customXmlParts.applyBindings(); // docx4j BindingHandler: XML -> controls
341
+ control.insertText('Jane Doe', 'Replace'); // writes through to the bound node as well
342
+ await pkg.customXmlParts.updateFromContentControls(); // docx4j: controls -> XML
343
+ ```
344
+
345
+ That last pair matters: Word refreshes a bound control from the custom XML part when it opens a
346
+ document, so an edit that only touched `w:sdtContent` would not show.
347
+
348
+ A new part comes with its properties part, a fresh `ds:itemID` and the relationship from the main
349
+ document part (Word drops a custom XML part the main part does not relate to):
350
+
351
+ ```ts
352
+ const part = pkg.customXmlParts.add('<greeting xmlns="http://example.com/g"><to>World</to></greeting>');
353
+ control.xmlMapping.setMapping('/ns0:greeting[1]/ns0:to[1]', "xmlns:ns0='http://example.com/g'", part);
354
+ control.xmlMapping.setMappingByNode(part.selectSingleNode('/ns0:greeting/ns0:to', "xmlns:ns0='http://example.com/g'"));
355
+ ```
356
+
357
+ Controls are created by wrapping what is there, and the kind-specific views are Office JS's:
358
+
359
+ ```ts
360
+ const control = paragraph.insertContentControl('CheckBox'); // also on body and range
361
+ control.checkboxContentControl.isChecked = true; // shows ☒, as Word does
362
+ control.title = 'Applies'; control.appearance = 'Tags'; control.cannotDelete = true;
363
+
364
+ const list = body.paragraphs[1].insertContentControl('DropDownList').dropDownListContentControl;
365
+ list.addListItem('Apples', 'apples');
366
+ ```
367
+
368
+ ### Fonts
369
+
370
+ Two questions, kept apart as they are in docx4j: which **document** font formats each character
371
+ of a run, and which physical face draws that name.
372
+
373
+ `RunFontSelector` answers the first. It resolves the run's effective properties, resolves every
374
+ theme reference (`w:asciiTheme` and friends) through the theme part for the document's
375
+ `w:themeFontLang`, and then walks the text through the [MS-OI29500] 17.3.2.26 character-range
376
+ table, folding the answer into spans:
377
+
378
+ ```ts
379
+ const main = pkg.getMainDocumentPart();
380
+ const selector = await main.getRunFontSelector();
381
+
382
+ selector.spans(paragraph.pPr, run.rPr, 'Hello 日本 שלום');
383
+ // [ { text: 'Hello ', documentFont: 'Calibri', bold: false, italic: false,
384
+ // cs: false, rtl: false, script: 'LATIN' },
385
+ // { text: '日本', documentFont: 'MS Gothic', ..., script: 'CJK' },
386
+ // { text: ' ', documentFont: 'Calibri', ..., script: null },
387
+ // { text: 'שלום', documentFont: 'Calibri', ..., script: 'HEBREW' } ]
388
+
389
+ selector.documentFontFor(paragraph.pPr, run.rPr, 0x1F600); // one code point
390
+ selector.asciiFontName(effectiveRPr); // what `font.name` reports
391
+ selector.defaultFont; // the document's default face
392
+ ```
393
+
394
+ `w:cs` and `w:rtl` are read **by value** (`<w:cs w:val="0"/>` turns an inherited complex-script
395
+ flag off), the symbol fonts are matched case-insensitively, and the space between two East Asian
396
+ words takes the ascii font — each of those verified against Word, in docx4j's CR-016.
397
+
398
+ A package with **no theme part** is read as coming from a particular version of Word:
399
+ `pkg.fonts.defaultTheme` is `'2023'` (Word 365: Aptos Display / Aptos), `'2013'` (Calibri Light /
400
+ Calibri) or `'2007'` (Cambria / Calibri). `createPackage()` gives a new package that theme's
401
+ theme part, as Word does, so a document created here and one created by docx4j resolve alike.
402
+
403
+ `Mapper` answers the second question, over a `FontRegistry` the caller supplies — the names the
404
+ consumer can actually draw with. `IdentityPlusMapper` takes the font itself where the registry
405
+ has it, then the document's embedded form, a variant of the name, docx4j's measured substitute
406
+ table (`font-substitutes.xml`: Arial to Arimo, Calibri to Carlito, Cambria to Caladea, Aptos to
407
+ Akasia), the document's own `w:altName` chain, a face of the same class, and finally what Word
408
+ itself draws a font it cannot find:
409
+
410
+ ```ts
411
+ const mapper = await main.getFontMapper(); // IdentityPlusMapper over DEFAULT_FONT_REGISTRY
412
+ mapper.get('Calibri'); // PhysicalFont { name: 'Carlito Regular', familyName: 'Carlito' }
413
+ mapper.getDecision('Calibri'); // { source: 'METRIC_CLONE', via: null, widthError: '0.07% ...' }
414
+
415
+ await main.fontsInUse(); // every name the document could ask for
416
+ await main.getStylesInUse(); // the style ids used directly
417
+
418
+ // your own font environment
419
+ const mine = new IdentityPlusMapper(new SimpleFontRegistry(faces.map((f) => new PhysicalFont(f.fullName, f.family))));
420
+ await main.getFontMapper(mine);
421
+ ```
422
+
423
+ The default registry is the faces docx4j's four font jars carry, which is the environment the
424
+ parity goldens were made in. Reading font files (`PhysicalFonts.discover`, glyph coverage, font
425
+ metrics) is a later CR; the `Mapper` interface is what a consumer with `fontkit` plugs into.
426
+
427
+ ### pptx and xlsx
428
+
429
+ The Open Packaging layer is one layer for all three formats: a `.pptx` and an `.xlsx` load, round
430
+ -trip byte for byte and save exactly as a `.docx` does, with their parts typed by the same object
431
+ model. What they do not have is the content API, which is WordprocessingML.
432
+
433
+ ```ts
434
+ import { PresentationMLPackage, SpreadsheetMLPackage } from '@docx4j/core-ts';
435
+
436
+ const deck = await PresentationMLPackage.createPackage({ slideSize: 'SCREEN16x9' });
437
+ await deck.addSlide();
438
+ deck.slideParts; // in p:sldIdLst order; .slideMasterParts, .slideLayoutParts
439
+
440
+ const book = await SpreadsheetMLPackage.createPackage();
441
+ const sheet = book.createWorksheetPart('Sales'); // the tab, its r:id and the worksheet part
442
+ book.worksheetParts; // in `sheets` order; .sharedStringsPart, .stylesPart
443
+ ```
444
+
445
+ Both follow docx4j's part set, so the files open in PowerPoint and Excel; the detail is in
446
+ [docs/guides/presentationml-spreadsheetml.md](docs/guides/presentationml-spreadsheetml.md).
447
+
448
+ ### Use in Node: running add-in code against a package
449
+
450
+ `@docx4j/core-ts/office-js` is a `Word` shim: `Word.run(pkg, fn)` gives the callback a
451
+ `context` whose `document.body` is the package's body, `load()` is a no-op and `context.sync()`
452
+ resolves what was asynchronous. An add-in's batch therefore runs unchanged in a test, in CI,
453
+ or on a server, with the saved `.docx` as the assertion — and a member this package does not
454
+ implement throws `NotSupportedError` naming it, rather than doing nothing.
455
+
456
+ ```ts
457
+ import { WordprocessingMLPackage } from '@docx4j/core-ts';
458
+ import { Word } from '@docx4j/core-ts/office-js';
459
+
460
+ const pkg = await WordprocessingMLPackage.load(bytes);
461
+ await Word.run(pkg, async (context) => {
462
+ const paragraphs = context.document.body.paragraphs;
463
+ paragraphs.load('text');
464
+ await context.sync();
465
+
466
+ paragraphs.items[0].styleBuiltIn = Word.Style.heading1;
467
+ for (const range of context.document.body.search('draft')) range.font.highlightColor = '#FFFF00';
468
+ context.document.properties.lastAuthor = 'an add-in';
469
+ context.document.changeTrackingMode = Word.ChangeTrackingMode.trackAll;
470
+
471
+ const ooxml = context.document.body.getOoxml(); // a ClientResult, as in Office JS
472
+ await context.sync();
473
+ ooxml.value; // the flat OPC package
474
+ });
475
+ await writeFile('edited.docx', await pkg.save());
476
+ ```
477
+
478
+ `Word.supported` is the set of `Class.member` strings this package implements
479
+ (`Word.supported.has('Body.insertParagraph')`), generated from the compile-time subset, so a
480
+ tool can check a script before running it. The selection an add-in edits is passed in:
481
+ `Word.run(pkg, fn, { selection })`.
482
+
483
+ `toApiScript` goes the other way: given a paragraph, a range or an element, it emits the
484
+ content-API calls that reproduce it, falling back to `insertXml` for what the verbs cannot say.
485
+
486
+ ```ts
487
+ import { toApiScript } from '@docx4j/core-ts/model';
488
+
489
+ await toApiScript(body.paragraphs[1]);
490
+ // const p1 = body.insertParagraph('Chapter 1', 'End');
491
+ // p1.styleBuiltIn = 'Heading1';
492
+ // p1.alignment = 'Centered';
493
+ // const r1 = p1.insertText(' (draft)', 'End');
494
+ // r1.font.italic = true;
495
+ ```
496
+
497
+ The bundling rules for an add-in, and a complete sideloadable example, are in
498
+ [docs/guides/office-addin.md](docs/guides/office-addin.md). A generated script reproduces the
499
+ document's *markup*, so it emits direct formatting only, not the effective values the reads
500
+ report (CR-001 Phase B step 2).
114
501
 
115
502
  ## Development
116
503
 
117
504
  ```
118
505
  npm ci # the locked dependencies from npm
119
506
  npm run typecheck && npm test # test = build, then node --test test/*.test.mjs
507
+ npm run typecheck:examples # examples/office-addin (also run by npm run typecheck)
508
+ npm run generate # src/office-js/supported.generated.mts from test/office-js-subset.ts
509
+ npm run generate:fonts # src/model/fonts/*.generated.mts from a docx4j checkout
120
510
  ```
121
511
 
512
+ `npm run generate` is committed output: the build does not depend on the script, but `npm test`
513
+ regenerates it so that `Word.supported` stays in step with the Office JS subset.
514
+ `npm run generate:fonts` is committed output too, re-run when docx4j's font tables change; it
515
+ reads a sibling `../docx4j` checkout and records the commit it ran against in each header.
516
+
517
+ The parity goldens under `test/golden/` are docx4j's own answers for 45 documents, produced by
518
+ the Java harness in [`test/java/`](test/java/README.md) and compared by `test/parity.test.mjs`.
519
+ They are regenerated by hand when docx4j or the fixtures change, and weekly by
520
+ `.github/workflows/parity.yml`, which opens a pull request when docx4j's answer moves.
521
+
122
522
  Releases are published to npm from GitHub Actions; see `RELEASING.md`.
123
523
 
124
524
  ## Licence
@@ -1,10 +1,18 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import type { PropertyResolver } from '../properties/PropertyResolver.mjs';
2
3
  import type { XmlPart } from '../../parts/XmlPart.mjs';
3
4
  import type { OpcPackage } from '../../packages/OpcPackage.mjs';
4
- import { type Element } from './tree.mjs';
5
+ import { type Element, type TextViewOptions } from './tree.mjs';
5
6
  import { Paragraph } from './Paragraph.mjs';
6
7
  import { Range } from './Range.mjs';
8
+ import { Table } from './Table.mjs';
9
+ import { ContentControl, type ContentControlType } from './ContentControl.mjs';
10
+ import { InlinePicture, type InlinePictureOptions } from './InlinePicture.mjs';
7
11
  import type { SearchOptions } from './search.mjs';
12
+ import type { Comment } from './Comment.mjs';
13
+ import { type ChangeTracker } from './tracking.mjs';
14
+ import { type TrackedChange } from './TrackedChange.mjs';
15
+ import { type List, type ListLabel } from './List.mjs';
8
16
  /** Where a paragraph or table is, for agents and across tool calls (CR-002 section 3.3). */
9
17
  export type Address = string | {
10
18
  contains: string;
@@ -55,17 +63,71 @@ export declare class Body {
55
63
  },
56
64
  /** The address prefix: 'body', 'header:rId3', 'footer:rId4', 'footnote:2'. */
57
65
  prefix?: string, package_?: OpcPackage | undefined);
66
+ /**
67
+ * The package's `PropertyResolver`, which every effective-formatting read goes through
68
+ * (CR-001 Phase B step 2). Throws `PropertyResolverNotCreatedException` when this body has
69
+ * no package, or the package has not built one yet: building it unmarshals the styles part,
70
+ * which cannot be done synchronously, so `await pkg.getPropertyResolver()` (or `getBody()`,
71
+ * which does it) first - or read direct formatting with `{ direct: true }`.
72
+ */
73
+ get propertyResolver(): PropertyResolver;
58
74
  /** The live content array (docx4j getContent()); block-level elements only, sectPr excluded. */
59
75
  get content(): Element[];
60
76
  /** Every paragraph in document order, descending into tables and content controls. */
61
77
  get paragraphs(): Paragraph[];
62
- /** The tables directly in this body (as elements; `Table` views are CR-002 Phase C). */
63
- get tables(): BlockElement[];
78
+ /** The tables directly in this body, as Office JS reports them (nested tables are a cell's). */
79
+ get tables(): Table[];
80
+ /** Every content control in this body, in document order, nested ones included. */
81
+ get contentControls(): ContentControl[];
82
+ /** Every inline picture in this body, in document order (Office JS Body.inlinePictures). */
83
+ get inlinePictures(): InlinePicture[];
84
+ /**
85
+ * Office JS `body.lists`: every `w:num` a paragraph of this body names, in the order they are
86
+ * first used (CR-002 phase H). Empty where the document has no numbering part, or where
87
+ * nothing has read it yet (`await pkg.getBody()` builds the resolver, which does).
88
+ */
89
+ get lists(): List[];
90
+ /**
91
+ * Every numbered paragraph of this body's **story** with its label, level, list and sibling
92
+ * index, in one walk (extension). `listItem.listString` walks the story per read, which is
93
+ * what Word's counting needs; this is the way to ask for all of them at once.
94
+ *
95
+ * Keyed by the `w:p` itself: `labels.get(paragraph.p)?.listString`.
96
+ */
97
+ listLabels(): Map<wml.P, ListLabel>;
98
+ /**
99
+ * A view over a container nested in this body (a cell, a content control), sharing this body's
100
+ * part and package. The address prefix is the container's own address, so that the addresses a
101
+ * nested body reports carry on from this one.
102
+ */
103
+ sub(container: {
104
+ content?: Element[];
105
+ }, prefix?: string): Body;
106
+ /** The view of a `w:p` somewhere in this body's tree; its container comes from PARENT. */
107
+ paragraphFor(p: wml.P): Paragraph | undefined;
64
108
  /** The text, a paragraph per line. */
65
109
  get text(): string;
110
+ /**
111
+ * The text in one of the two views (extension; `text` is the accepted one).
112
+ * `{ view: 'original' }` reads the document as it was before the tracked changes.
113
+ */
114
+ getText(options?: TextViewOptions): string;
115
+ /** The package's change tracker while `changeTrackingMode` is on; undefined when it is off (CR-002 phase F). */
116
+ get changeTracker(): ChangeTracker | undefined;
66
117
  insertParagraph(text: string, location: 'Start' | 'End'): Paragraph;
67
118
  /** Text at the start of the first paragraph, the end of the last, or replacing everything with one paragraph. */
68
119
  insertText(text: string, location: 'Start' | 'End' | 'Replace'): Range;
120
+ /**
121
+ * A table of `rowCount` by `columnCount` cells at the start or end (Office JS insertTable);
122
+ * `values` fills it row by row. The columns are equal over the section's text width.
123
+ */
124
+ insertTable(rowCount: number, columnCount: number, location: 'Start' | 'End', values?: string[][]): Table;
125
+ /**
126
+ * Adds the image as a part of this body's part, with a relationship, and shows it in a new
127
+ * paragraph at the start or the end (Office JS insertInlinePictureFromBase64). An image wider
128
+ * than the text area is scaled down, as docx4j's CxCy.scale does.
129
+ */
130
+ insertInlinePictureFromBase64(base64: string, location: 'Start' | 'End', options?: InlinePictureOptions): InlinePicture;
69
131
  insertBreak(type: 'Page' | 'Line', location: 'Start' | 'End'): void;
70
132
  /**
71
133
  * Inserts block-level elements (docx4j addObject): at the start or end of this body, or
@@ -75,10 +137,41 @@ export declare class Body {
75
137
  insertElement(element: Element | Element[], location: 'Start' | 'End' | 'Before' | 'After', target?: Paragraph | BlockElement | Address): Paragraph | BlockElement;
76
138
  /** Inserts a WML fragment (one or more w:p / w:tbl / ... as inside document.xml); the standard prefixes are declared for it. */
77
139
  insertXml(xml: string, location: 'Start' | 'End' | 'Before' | 'After', target?: Paragraph | BlockElement | Address): Promise<(Paragraph | BlockElement)[]>;
140
+ /**
141
+ * Word's `insertOoxml`: a flat OPC `pkg:package` string, whose body content is inserted with
142
+ * the parts it references (images, embedded objects) copied into this package under fresh
143
+ * names and relationship ids; styles and numbering are not merged (CR-002 open question 5).
144
+ * A bare `w:p` / `w:tbl` fragment is accepted too, as `insertXml` takes. Returns the views of
145
+ * what was inserted, as `insertXml` does, since a package may bring several blocks.
146
+ */
147
+ insertOoxml(ooxml: string, location: 'Start' | 'End' | 'Replace' | 'Before' | 'After', target?: Paragraph | BlockElement | Address): Promise<(Paragraph | BlockElement)[]>;
148
+ /** The package's DOM preprocessor (the MCE one), for fragments and incoming packages. */
149
+ private preprocessor;
150
+ private insertContent;
78
151
  /** Matches over every paragraph, in document order. */
79
152
  search(text: string, options?: SearchOptions): Range[];
80
- /** Removes everything (section properties stay). */
153
+ /**
154
+ * Replaces every match of `find` with `replace`, last match first so that the offsets of the
155
+ * ones still to do stay valid (CR-002 section 3.7). Returns the number replaced; tracked when
156
+ * the package's `changeTrackingMode` is on.
157
+ */
158
+ replaceText(find: string, replace: string, options?: SearchOptions): number;
159
+ /**
160
+ * Removes everything (section properties stay). While the package tracks changes, every
161
+ * paragraph is marked deleted and every row's `w:trPr` takes a `w:del` instead.
162
+ */
81
163
  clear(): void;
164
+ /** Every tracked change in this body, in document order. */
165
+ getTrackedChanges(): TrackedChange[];
166
+ /**
167
+ * Accepts every tracked change, last first so that a paragraph join never disturbs one still
168
+ * to do (docx4j `AcceptTrackedChanges` is the reference). Returns how many were accepted.
169
+ */
170
+ acceptAll(): number;
171
+ /** Rejects every tracked change, last first. Returns how many were rejected. */
172
+ rejectAll(): number;
173
+ /** Every `w:tr` in this body's tables, in document order (rows of nested tables included). */
174
+ private rowElements;
82
175
  /** The part's XML (extension; Office JS getOoxml wraps it in a package). */
83
176
  getXml(): Promise<string>;
84
177
  /** The element at an address: 'body/3' (fourth block), 'body/3/1/0/2' (table row 1, cell 0, block 2), a paraId, or a text match. */
@@ -104,4 +197,16 @@ export declare class Body {
104
197
  /** Gives a new paragraph a w14:paraId when the document already uses them. */
105
198
  private assignParaId;
106
199
  private usesParaIds;
200
+ /**
201
+ * The comments anchored in this body, in document order, with replies nested under their
202
+ * parent (only top-level comments are in the array). Asynchronous because it unmarshals the
203
+ * comment parts.
204
+ */
205
+ getComments(): Promise<Comment[]>;
206
+ /**
207
+ * Wraps everything this body holds in a new content control (Office JS
208
+ * `Body.insertContentControl`), typed for the kind when one is given; the `w:id` is free in this
209
+ * body. Returns the control.
210
+ */
211
+ insertContentControl(kind?: ContentControlType): ContentControl;
107
212
  }