docxmlater 1.4.2 → 1.5.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.
package/README.md CHANGED
@@ -1,1400 +1,1418 @@
1
- # docXMLater - Professional DOCX Framework
2
-
3
- [![npm version](https://img.shields.io/npm/v/docxmlater.svg)](https://www.npmjs.com/package/docxmlater)
4
- [![Tests](https://img.shields.io/badge/tests-1119%20passing-brightgreen)](https://github.com/ItMeDiaTech/docXMLater)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
-
8
- A comprehensive, production-ready TypeScript/JavaScript library for creating, reading, and manipulating Microsoft Word (.docx) documents programmatically. Full OpenXML compliance with extensive API coverage and robust test suite.
9
-
10
- Built for professional documentation work, docXMLater provides a complete solution for programmatic DOCX manipulation with an intuitive API and helper functions for all aspects of document creation and modification.
11
-
12
- ## Latest Updates - v1.3.0
13
-
14
- **Enhanced Parsing & Helper Functions:**
15
-
16
- ### What's New in v1.3.0
17
-
18
- - **TOC Parsing:** Parse Table of Contents from existing documents with full SDT support
19
- - **TOC Modification:** Modify TOC field instructions (add/remove switches: \h, \u, \z, \n, \o, \t)
20
- - **Header/Footer Removal:** New `removeAllHeadersFooters()` helper function
21
- - **Complete Feature Set:** All 102 major features implemented
22
- - **Table Styles:** Full support with 12 conditional formatting types
23
- - **Content Controls:** 9 control types (rich text, plain text, combo box, dropdown, date picker, checkbox, picture, building block, group)
24
- - **Field Types:** 11 field types (PAGE, NUMPAGES, DATE, TIME, FILENAME, AUTHOR, TITLE, REF, HYPERLINK, SEQ, TC/XE)
25
- - **Drawing Elements:** Shapes and textboxes with full positioning
26
- - **Document Properties:** Core, extended, and custom properties
27
- - **Production Ready:** Full ECMA-376 compliance
28
-
29
- **Test Results:** 1,119/1,150 tests passing (97.3% pass rate - 1,119 core features validated)
30
-
31
- ## Quick Start
32
-
33
- ```bash
34
- npm install docxmlater
35
- ```
36
-
37
- ```typescript
38
- import { Document } from "docxmlater";
39
-
40
- // Create document
41
- const doc = Document.create();
42
- doc.createParagraph("Hello World").setStyle("Title");
43
-
44
- // Save document
45
- await doc.save("output.docx");
46
- ```
47
-
48
- ## Complete API Reference
49
-
50
- ### Document Operations
51
-
52
- | Method | Description | Example |
53
- | --------------------------------- | ----------------------- | ------------------------------------------------ |
54
- | `Document.create(options?)` | Create new document | `const doc = Document.create()` |
55
- | `Document.createEmpty()` | Create minimal document | `const doc = Document.createEmpty()` |
56
- | `Document.load(path)` | Load from file | `const doc = await Document.load('file.docx')` |
57
- | `Document.loadFromBuffer(buffer)` | Load from buffer | `const doc = await Document.loadFromBuffer(buf)` |
58
- | `save(path)` | Save to file | `await doc.save('output.docx')` |
59
- | `toBuffer()` | Export as buffer | `const buffer = await doc.toBuffer()` |
60
- | `dispose()` | Clean up resources | `doc.dispose()` |
61
-
62
- ### Content Creation
63
-
64
- | Method | Description | Example |
65
- | -------------------------------- | ------------------------ | -------------------------------- |
66
- | `createParagraph(text?)` | Add paragraph | `doc.createParagraph('Text')` |
67
- | `createTable(rows, cols)` | Add table | `doc.createTable(3, 4)` |
68
- | `addParagraph(para)` | Add existing paragraph | `doc.addParagraph(myPara)` |
69
- | `addTable(table)` | Add existing table | `doc.addTable(myTable)` |
70
- | `addImage(image)` | Add image | `doc.addImage(myImage)` |
71
- | `addTableOfContents(toc?)` | Add TOC | `doc.addTableOfContents()` |
72
- | `insertParagraphAt(index, para)` | Insert at position | `doc.insertParagraphAt(0, para)` |
73
- | `insertTableAt(index, table)` | Insert table at position | `doc.insertTableAt(5, table)` |
74
- | `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
75
-
76
- ### Content Manipulation
77
-
78
- | Method | Description | Returns |
79
- | --------------------------------- | ---------------------------- | --------- |
80
- | `replaceParagraphAt(index, para)` | Replace paragraph | `boolean` |
81
- | `replaceTableAt(index, table)` | Replace table | `boolean` |
82
- | `moveElement(fromIndex, toIndex)` | Move element to new position | `boolean` |
83
- | `swapElements(index1, index2)` | Swap two elements | `boolean` |
84
- | `removeTocAt(index)` | Remove TOC element | `boolean` |
85
-
86
- ### Content Retrieval
87
-
88
- | Method | Description | Returns |
89
- | ---------------------- | --------------------------------------- | ------------------------------------------ |
90
- | `getParagraphs()` | Get top-level paragraphs | `Paragraph[]` |
91
- | `getAllParagraphs()` | Get all paragraphs (recursive) | `Paragraph[]` |
92
- | `getTables()` | Get top-level tables | `Table[]` |
93
- | `getAllTables()` | Get all tables (recursive) | `Table[]` |
94
- | `getBodyElements()` | Get all body elements | `BodyElement[]` |
95
- | `getParagraphCount()` | Count paragraphs | `number` |
96
- | `getTableCount()` | Count tables | `number` |
97
- | `getHyperlinks()` | Get all links | `Array<{hyperlink, paragraph}>` |
98
- | `getBookmarks()` | Get all bookmarks | `Array<{bookmark, paragraph}>` |
99
- | `getImages()` | Get all images | `Array<{image, relationshipId, filename}>` |
100
-
101
- **Note**: The `getAllParagraphs()` and `getAllTables()` methods recursively search inside tables and SDTs (Structured Document Tags), while the non-prefixed methods only return top-level elements.
102
-
103
- **Example - Recursive Element Access:**
104
-
105
- ```typescript
106
- import { Document, Hyperlink } from 'docxmlater';
107
-
108
- // Load document with complex structure (tables, SDTs, nested content)
109
- const doc = await Document.load('complex.docx');
110
-
111
- // Get only top-level paragraphs (misses nested content)
112
- const topLevel = doc.getParagraphs();
113
- console.log(`Top-level paragraphs: ${topLevel.length}`); // e.g., 37
114
-
115
- // Get ALL paragraphs including those in tables and SDTs
116
- const allParas = doc.getAllParagraphs();
117
- console.log(`All paragraphs: ${allParas.length}`); // e.g., 52
118
-
119
- // Apply formatting to ALL paragraphs (including nested ones)
120
- for (const para of allParas) {
121
- para.setSpaceAfter(120); // Set 6pt spacing after each paragraph
122
- }
123
-
124
- // Get all tables including those inside SDTs
125
- const allTables = doc.getAllTables();
126
- for (const table of allTables) {
127
- table.setWidth(5000).setWidthType('pct'); // Set to 100% width
128
- }
129
-
130
- // Find all hyperlinks in the entire document
131
- let hyperlinkCount = 0;
132
- for (const para of allParas) {
133
- for (const content of para.getContent()) {
134
- if (content instanceof Hyperlink) {
135
- hyperlinkCount++;
136
- content.setFormatting({ color: '0000FF' }); // Make all links blue
137
- }
138
- }
139
- }
140
- console.log(`Updated ${hyperlinkCount} hyperlinks`);
141
- ```
142
-
143
- ### Content Removal
144
-
145
- | Method | Description | Returns |
146
- | ------------------------------ | ------------------ | --------- |
147
- | `removeParagraph(paraOrIndex)` | Remove paragraph | `boolean` |
148
- | `removeTable(tableOrIndex)` | Remove table | `boolean` |
149
- | `clearParagraphs()` | Remove all content | `this` |
150
-
151
- ### Search & Replace
152
-
153
- | Method | Description | Options |
154
- | -------------------------------------- | --------------------- | ------------------------------ |
155
- | `findText(text, options?)` | Find text occurrences | `{caseSensitive?, wholeWord?}` |
156
- | `replaceText(find, replace, options?)` | Replace all text | `{caseSensitive?, wholeWord?}` |
157
- | `updateHyperlinkUrls(urlMap)` | Update hyperlink URLs | `Map<oldUrl, newUrl>` |
158
-
159
- ### Style Application
160
-
161
- | Method | Description | Returns |
162
- | ------------------------------------- | -------------------------------- | ------------------- |
163
- | `applyStyleToAll(styleId, predicate)` | Apply style to matching elements | `number` |
164
- | `findElementsByStyle(styleId)` | Find all elements using a style | `Array<Para\|Cell>` |
165
-
166
- **Example:**
167
-
168
- ```typescript
169
- // Apply Heading1 to all paragraphs containing "Chapter"
170
- const count = doc.applyStyleToAll("Heading1", (el) => {
171
- return el instanceof Paragraph && el.getText().includes("Chapter");
172
- });
173
-
174
- // Find all Heading1 elements
175
- const headings = doc.findElementsByStyle("Heading1");
176
- ```
177
-
178
- ### Document Statistics
179
-
180
- | Method | Description | Returns |
181
- | ----------------------------------- | ------------------- | ------------------------------ |
182
- | `getWordCount()` | Total word count | `number` |
183
- | `getCharacterCount(includeSpaces?)` | Character count | `number` |
184
- | `estimateSize()` | Size estimation | `{totalEstimatedMB, warning?}` |
185
- | `getSizeStats()` | Detailed size stats | `{elements, size, warnings}` |
186
-
187
- ### Text Formatting
188
-
189
- | Property | Values | Example |
190
- | ------------- | -------------------------------- | ----------------------- |
191
- | `bold` | `true/false` | `{bold: true}` |
192
- | `italic` | `true/false` | `{italic: true}` |
193
- | `underline` | `'single'/'double'/'dotted'/etc` | `{underline: 'single'}` |
194
- | `strike` | `true/false` | `{strike: true}` |
195
- | `font` | Font name | `{font: 'Arial'}` |
196
- | `size` | Points | `{size: 12}` |
197
- | `color` | Hex color | `{color: 'FF0000'}` |
198
- | `highlight` | Color name | `{highlight: 'yellow'}` |
199
- | `subscript` | `true/false` | `{subscript: true}` |
200
- | `superscript` | `true/false` | `{superscript: true}` |
201
- | `smallCaps` | `true/false` | `{smallCaps: true}` |
202
- | `allCaps` | `true/false` | `{allCaps: true}` |
203
-
204
- ### Paragraph Operations
205
-
206
- #### Creating Detached Paragraphs
207
-
208
- Create paragraphs independently before adding to a document:
209
-
210
- ```typescript
211
- // Create empty paragraph
212
- const para1 = Paragraph.create();
213
-
214
- // Create with text
215
- const para2 = Paragraph.create("Hello World");
216
-
217
- // Create with text and formatting
218
- const para3 = Paragraph.create("Centered text", { alignment: "center" });
219
-
220
- // Create with just formatting
221
- const para4 = Paragraph.create({
222
- alignment: "right",
223
- spacing: { before: 240 },
224
- });
225
-
226
- // Create with style
227
- const heading = Paragraph.createWithStyle("Chapter 1", "Heading1");
228
-
229
- // Create with both run and paragraph formatting
230
- const important = Paragraph.createFormatted(
231
- "Important Text",
232
- { bold: true, color: "FF0000" },
233
- { alignment: "center" }
234
- );
235
-
236
- // Add to document later
237
- doc.addParagraph(para1);
238
- doc.addParagraph(heading);
239
- ```
240
-
241
- #### Paragraph Factory Methods
242
-
243
- | Method | Description | Example |
244
- | --------------------------------------------------- | --------------------------- | --------------------------------------- |
245
- | `Paragraph.create(text?, formatting?)` | Create detached paragraph | `Paragraph.create('Text')` |
246
- | `Paragraph.create(formatting?)` | Create with formatting only | `Paragraph.create({alignment: 'left'})` |
247
- | `Paragraph.createWithStyle(text, styleId)` | Create with style | `Paragraph.createWithStyle('', 'H1')` |
248
- | `Paragraph.createEmpty()` | Create empty paragraph | `Paragraph.createEmpty()` |
249
- | `Paragraph.createFormatted(text, run?, paragraph?)` | Create with dual formatting | See example above |
250
-
251
- #### Paragraph Formatting Methods
252
-
253
- | Method | Description | Values |
254
- | ------------------------------ | ------------------- | ----------------------------------- |
255
- | `setAlignment(align)` | Text alignment | `'left'/'center'/'right'/'justify'` |
256
- | `setLeftIndent(twips)` | Left indentation | Twips value |
257
- | `setRightIndent(twips)` | Right indentation | Twips value |
258
- | `setFirstLineIndent(twips)` | First line indent | Twips value |
259
- | `setSpaceBefore(twips)` | Space before | Twips value |
260
- | `setSpaceAfter(twips)` | Space after | Twips value |
261
- | `setLineSpacing(twips, rule?)` | Line spacing | Twips + rule |
262
- | `setStyle(styleId)` | Apply style | Style ID |
263
- | `setKeepNext()` | Keep with next | - |
264
- | `setKeepLines()` | Keep lines together | - |
265
- | `setPageBreakBefore()` | Page break before | - |
266
-
267
- #### Paragraph Manipulation Methods
268
-
269
- | Method | Description | Returns |
270
- | -------------------------------------- | ----------------------- | ----------- |
271
- | `insertRunAt(index, run)` | Insert run at position | `this` |
272
- | `removeRunAt(index)` | Remove run at position | `boolean` |
273
- | `replaceRunAt(index, run)` | Replace run at position | `boolean` |
274
- | `findText(text, options?)` | Find text in runs | `number[]` |
275
- | `replaceText(find, replace, options?)` | Replace text in runs | `number` |
276
- | `mergeWith(otherPara)` | Merge another paragraph | `this` |
277
- | `clone()` | Clone paragraph | `Paragraph` |
278
-
279
- **Example:**
280
-
281
- ```typescript
282
- const para = doc.createParagraph("Hello World");
283
-
284
- // Find and replace
285
- const indices = para.findText("World"); // [1]
286
- const count = para.replaceText("World", "Universe", { caseSensitive: true });
287
-
288
- // Manipulate runs
289
- para.insertRunAt(0, new Run("Start: ", { bold: true }));
290
- para.replaceRunAt(1, new Run("HELLO", { allCaps: true }));
291
-
292
- // Merge paragraphs
293
- const para2 = Paragraph.create(" More text");
294
- para.mergeWith(para2); // Combines runs
295
- ```
296
-
297
- ### Run (Text Span) Operations
298
-
299
- | Method | Description | Returns |
300
- | ------------------------------- | ------------------------- | ------- |
301
- | `clone()` | Clone run with formatting | `Run` |
302
- | `insertText(index, text)` | Insert text at position | `this` |
303
- | `appendText(text)` | Append text to end | `this` |
304
- | `replaceText(start, end, text)` | Replace text range | `this` |
305
-
306
- **Example:**
307
-
308
- ```typescript
309
- const run = new Run("Hello World", { bold: true });
310
-
311
- // Text manipulation
312
- run.insertText(6, "Beautiful "); // "Hello Beautiful World"
313
- run.appendText("!"); // "Hello Beautiful World!"
314
- run.replaceText(0, 5, "Hi"); // "Hi Beautiful World!"
315
-
316
- // Clone for reuse
317
- const copy = run.clone();
318
- copy.setColor("FF0000"); // Original unchanged
319
- ```
320
-
321
- ### Table Operations
322
-
323
- | Method | Description | Example |
324
- | ----------------------- | -------------------- | ---------------------------------------- |
325
- | `getRow(index)` | Get table row | `table.getRow(0)` |
326
- | `getCell(row, col)` | Get table cell | `table.getCell(0, 1)` |
327
- | `addRow()` | Add new row | `table.addRow()` |
328
- | `removeRow(index)` | Remove row | `table.removeRow(2)` |
329
- | `insertColumn(index)` | Insert column | `table.insertColumn(1)` |
330
- | `removeColumn(index)` | Remove column | `table.removeColumn(3)` |
331
- | `setWidth(twips)` | Set table width | `table.setWidth(8640)` |
332
- | `setAlignment(align)` | Table alignment | `table.setAlignment('center')` |
333
- | `setAllBorders(border)` | Set all borders | `table.setAllBorders({style: 'single'})` |
334
- | `setBorders(borders)` | Set specific borders | `table.setBorders({top: {...}})` |
335
-
336
- #### Advanced Table Operations
337
-
338
- | Method | Description | Returns |
339
- | ------------------------------------------------ | ------------------------- | ------------ |
340
- | `mergeCells(startRow, startCol, endRow, endCol)` | Merge cells | `this` |
341
- | `splitCell(row, col)` | Remove cell spanning | `this` |
342
- | `moveCell(fromRow, fromCol, toRow, toCol)` | Move cell contents | `this` |
343
- | `swapCells(row1, col1, row2, col2)` | Swap two cells | `this` |
344
- | `setColumnWidth(index, width)` | Set specific column width | `this` |
345
- | `setColumnWidths(widths)` | Set all column widths | `this` |
346
- | `insertRows(startIndex, count)` | Insert multiple rows | `TableRow[]` |
347
- | `removeRows(startIndex, count)` | Remove multiple rows | `boolean` |
348
- | `clone()` | Clone entire table | `Table` |
349
-
350
- **Example:**
351
-
352
- ```typescript
353
- const table = doc.createTable(3, 3);
354
-
355
- // Merge cells horizontally (row 0, columns 0-2)
356
- table.mergeCells(0, 0, 0, 2);
357
-
358
- // Move cell contents
359
- table.moveCell(1, 1, 2, 2);
360
-
361
- // Swap cells
362
- table.swapCells(0, 0, 2, 2);
363
-
364
- // Batch row operations
365
- table.insertRows(1, 3); // Insert 3 rows at position 1
366
- table.removeRows(4, 2); // Remove 2 rows starting at position 4
367
-
368
- // Set column widths
369
- table.setColumnWidth(0, 2000); // First column = 2000 twips
370
- table.setColumnWidths([2000, 3000, 2000]); // All columns
371
-
372
- // Clone table for reuse
373
- const tableCopy = table.clone();
374
- ```
375
-
376
- ### Table Cell Operations
377
-
378
- | Method | Description | Example |
379
- | ----------------------------- | --------------------- | ------------------------------------- |
380
- | `createParagraph(text?)` | Add paragraph to cell | `cell.createParagraph('Text')` |
381
- | `setShading(shading)` | Cell background | `cell.setShading({fill: 'E0E0E0'})` |
382
- | `setVerticalAlignment(align)` | Vertical align | `cell.setVerticalAlignment('center')` |
383
- | `setColumnSpan(cols)` | Merge columns | `cell.setColumnSpan(3)` |
384
- | `setRowSpan(rows)` | Merge rows | `cell.setRowSpan(2)` |
385
- | `setBorders(borders)` | Cell borders | `cell.setBorders({top: {...}})` |
386
- | `setWidth(width, type?)` | Cell width | `cell.setWidth(2000, 'dxa')` |
387
-
388
- ### Style Management
389
-
390
- | Method | Description | Example |
391
- | ----------------------------- | ------------------ | ---------------------------------- |
392
- | `addStyle(style)` | Add custom style | `doc.addStyle(myStyle)` |
393
- | `getStyle(styleId)` | Get style by ID | `doc.getStyle('Heading1')` |
394
- | `hasStyle(styleId)` | Check style exists | `doc.hasStyle('CustomStyle')` |
395
- | `getStyles()` | Get all styles | `doc.getStyles()` |
396
- | `removeStyle(styleId)` | Remove style | `doc.removeStyle('OldStyle')` |
397
- | `updateStyle(styleId, props)` | Update style | `doc.updateStyle('Normal', {...})` |
398
-
399
- #### Style Manipulation
400
-
401
- | Method | Description | Returns |
402
- | ------------------------ | ----------------------------------- | ------- |
403
- | `style.clone()` | Clone style | `Style` |
404
- | `style.mergeWith(other)` | Merge properties from another style | `this` |
405
-
406
- **Example:**
407
-
408
- ```typescript
409
- // Clone a style
410
- const heading1 = doc.getStyle("Heading1");
411
- const customHeading = heading1.clone();
412
- customHeading.setRunFormatting({ color: "FF0000" });
413
-
414
- // Merge styles
415
- const baseStyle = Style.createNormalStyle();
416
- const overrideStyle = Style.create({
417
- styleId: "Override",
418
- name: "Override",
419
- type: "paragraph",
420
- runFormatting: { bold: true, color: "FF0000" },
421
- });
422
- baseStyle.mergeWith(overrideStyle); // baseStyle now has bold red text
423
- ```
424
-
425
- #### Built-in Styles
426
-
427
- - `Normal` - Default paragraph
428
- - `Title` - Document title
429
- - `Subtitle` - Document subtitle
430
- - `Heading1` through `Heading9` - Section headings
431
- - `ListParagraph` - List items
432
-
433
- ### List Management
434
-
435
- | Method | Description | Returns |
436
- | --------------------------------------- | ----------------------- | ------- |
437
- | `createBulletList(levels?, bullets?)` | Create bullet list | `numId` |
438
- | `createNumberedList(levels?, formats?)` | Create numbered list | `numId` |
439
- | `createMultiLevelList()` | Create multi-level list | `numId` |
440
-
441
- ### Table of Contents (TOC)
442
-
443
- #### Basic TOC Creation
444
-
445
- | Method | Description | Example |
446
- | -------------------------- | ---------------------- | -------------------------- |
447
- | `addTableOfContents(toc?)` | Add TOC to document | `doc.addTableOfContents()` |
448
- | `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
449
- | `removeTocAt(index)` | Remove TOC at position | `doc.removeTocAt(0)` |
450
-
451
- #### TOC Factory Methods
452
-
453
- | Method | Description | Example |
454
- | -------------------------------------------------------- | ------------------------ | ---------------------------------------------------- |
455
- | `TableOfContents.createStandard(title?)` | Standard TOC (3 levels) | `TableOfContents.createStandard()` |
456
- | `TableOfContents.createSimple(title?)` | Simple TOC (2 levels) | `TableOfContents.createSimple()` |
457
- | `TableOfContents.createDetailed(title?)` | Detailed TOC (4 levels) | `TableOfContents.createDetailed()` |
458
- | `TableOfContents.createHyperlinked(title?)` | Hyperlinked TOC | `TableOfContents.createHyperlinked()` |
459
- | `TableOfContents.createNoPageNumbers(opts?)` | TOC without page numbers | `TableOfContents.createNoPageNumbers()` |
460
- | `TableOfContents.createWithStyles(styles, opts?)` | TOC with specific styles | `TableOfContents.createWithStyles(['H1','H3'])` |
461
- | `TableOfContents.createFlat(title?, styles?)` | Flat TOC (no indent) | `TableOfContents.createFlat()` |
462
- | `TableOfContents.createNumbered(title?, format?)` | Numbered TOC | `TableOfContents.createNumbered('TOC', 'roman')` |
463
- | `TableOfContents.createWithSpacing(spacing, opts?)` | TOC with custom spacing | `TableOfContents.createWithSpacing(120)` |
464
- | `TableOfContents.createWithHyperlinkColor(color, opts?)` | Custom hyperlink color | `TableOfContents.createWithHyperlinkColor('FF0000')` |
465
-
466
- **Note:** All TOC elements are automatically wrapped in an SDT (Structured Document Tag) for native Word integration. This enables Word's "Update Table" button and provides better compatibility with Microsoft Word's TOC features.
467
-
468
- #### TOC Configuration Methods
469
-
470
- | Method | Description | Values |
471
- | --------------------------------- | ------------------------------ | -------------------------- |
472
- | `setIncludeStyles(styles)` | Select specific heading styles | `['Heading1', 'Heading3']` |
473
- | `setNumbered(numbered, format?)` | Enable/disable numbering | `(true, 'roman')` |
474
- | `setNoIndent(noIndent)` | Remove indentation | `true/false` |
475
- | `setCustomIndents(indents)` | Custom indents per level | `[0, 200, 400]` (twips) |
476
- | `setSpaceBetweenEntries(spacing)` | Spacing between entries | `120` (twips) |
477
- | `setHyperlinkColor(color)` | Hyperlink color | `'0000FF'` (default blue) |
478
- | `setHideInWebLayout(hide)` | Hide page numbers in web view | `true/false` |
479
- | `configure(options)` | Bulk configuration | See example below |
480
-
481
- #### TOC Properties
482
-
483
- | Property | Type | Default | Description |
484
- | --------------------- | ------------------------------------ | --------------------- | ---------------------------------- |
485
- | `title` | `string` | `'Table of Contents'` | TOC title |
486
- | `levels` | `number` (1-9) | `3` | Heading levels to include |
487
- | `includeStyles` | `string[]` | `undefined` | Specific styles (overrides levels) |
488
- | `showPageNumbers` | `boolean` | `true` | Show page numbers |
489
- | `useHyperlinks` | `boolean` | `false` | Use hyperlinks instead of page #s |
490
- | `hideInWebLayout` | `boolean` | `false` | Hide page numbers in web layout |
491
- | `numbered` | `boolean` | `false` | Number TOC entries |
492
- | `numberingFormat` | `'decimal'/'roman'/'alpha'` | `'decimal'` | Numbering format |
493
- | `noIndent` | `boolean` | `false` | Remove all indentation |
494
- | `customIndents` | `number[]` | `undefined` | Custom indents in twips |
495
- | `spaceBetweenEntries` | `number` | `0` | Spacing in twips |
496
- | `hyperlinkColor` | `string` | `'0000FF'` | Hyperlink color (hex without #) |
497
- | `tabLeader` | `'dot'/'hyphen'/'underscore'/'none'` | `'dot'` | Tab leader character |
498
-
499
- **Example:**
500
-
501
- ```typescript
502
- // Basic TOC
503
- const simpleToc = TableOfContents.createStandard();
504
- doc.addTableOfContents(simpleToc);
505
-
506
- // Select specific styles (e.g., only Heading1 and Heading3)
507
- const customToc = TableOfContents.createWithStyles(["Heading1", "Heading3"]);
508
-
509
- // Flat TOC with no indentation
510
- const flatToc = TableOfContents.createFlat("Contents");
511
-
512
- // Numbered TOC with roman numerals
513
- const numberedToc = TableOfContents.createNumbered(
514
- "Table of Contents",
515
- "roman"
516
- );
517
-
518
- // Custom hyperlink color (red instead of blue)
519
- const coloredToc = TableOfContents.createWithHyperlinkColor("FF0000");
520
-
521
- // Advanced configuration
522
- const toc = TableOfContents.create()
523
- .setIncludeStyles(["Heading1", "Heading2", "Heading3"])
524
- .setNumbered(true, "decimal")
525
- .setSpaceBetweenEntries(120) // 6pt spacing
526
- .setHyperlinkColor("0000FF")
527
- .setNoIndent(false);
528
-
529
- // Or use configure() for bulk settings
530
- toc.configure({
531
- title: "Table of Contents",
532
- includeStyles: ["Heading1", "CustomHeader"],
533
- numbered: true,
534
- numberingFormat: "alpha",
535
- spaceBetweenEntries: 100,
536
- hyperlinkColor: "FF0000",
537
- noIndent: true,
538
- });
539
-
540
- // Insert at specific position
541
- doc.insertTocAt(0, toc);
542
- ```
543
-
544
- ### Image Handling
545
-
546
- | Method | Description | Example |
547
- | --------------------------------------- | ------------------ | -------------------------------- |
548
- | `Image.fromFile(path, width?, height?)` | Load from file | `Image.fromFile('pic.jpg')` |
549
- | `Image.fromBuffer(buffer, ext, w?, h?)` | Load from buffer | `Image.fromBuffer(buf, 'png')` |
550
- | `setWidth(emus, maintainRatio?)` | Set width | `img.setWidth(inchesToEmus(3))` |
551
- | `setHeight(emus, maintainRatio?)` | Set height | `img.setHeight(inchesToEmus(2))` |
552
- | `setSize(width, height)` | Set dimensions | `img.setSize(w, h)` |
553
- | `setRotation(degrees)` | Rotate image | `img.setRotation(90)` |
554
- | `setAltText(text)` | Accessibility text | `img.setAltText('Description')` |
555
-
556
- ### Hyperlinks
557
-
558
- | Method | Description | Example |
559
- | ------------------------------------------------- | ---------------- | ---------------------------------------------------------- |
560
- | `Hyperlink.createExternal(url, text, format?)` | Web link | `Hyperlink.createExternal('https://example.com', 'Click')` |
561
- | `Hyperlink.createEmail(email, text?, format?)` | Email link | `Hyperlink.createEmail('user@example.com')` |
562
- | `Hyperlink.createInternal(anchor, text, format?)` | Internal link | `Hyperlink.createInternal('Section1', 'Go to')` |
563
- | `para.addHyperlink(hyperlink)` | Add to paragraph | `para.addHyperlink(link)` |
564
-
565
- ### Headers & Footers
566
-
567
- | Method | Description | Example |
568
- | ---------------------------- | ------------------ | -------------------------------- |
569
- | `setHeader(header)` | Set default header | `doc.setHeader(myHeader)` |
570
- | `setFooter(footer)` | Set default footer | `doc.setFooter(myFooter)` |
571
- | `setFirstPageHeader(header)` | First page header | `doc.setFirstPageHeader(header)` |
572
- | `setFirstPageFooter(footer)` | First page footer | `doc.setFirstPageFooter(footer)` |
573
- | `setEvenPageHeader(header)` | Even page header | `doc.setEvenPageHeader(header)` |
574
- | `setEvenPageFooter(footer)` | Even page footer | `doc.setEvenPageFooter(footer)` |
575
-
576
- ### Page Setup
577
-
578
- | Method | Description | Example |
579
- | ------------------------------------- | ----------------- | ------------------------------------- |
580
- | `setPageSize(width, height, orient?)` | Page dimensions | `doc.setPageSize(12240, 15840)` |
581
- | `setPageOrientation(orientation)` | Page orientation | `doc.setPageOrientation('landscape')` |
582
- | `setMargins(margins)` | Page margins | `doc.setMargins({top: 1440, ...})` |
583
- | `setLanguage(language)` | Document language | `doc.setLanguage('en-US')` |
584
-
585
- ### Document Properties
586
-
587
- | Method | Description | Properties |
588
- | ---------------------- | ------------ | ------------------------------------- |
589
- | `setProperties(props)` | Set metadata | `{title, subject, creator, keywords}` |
590
- | `getProperties()` | Get metadata | Returns all properties |
591
-
592
- ### Advanced Features
593
-
594
- #### Bookmarks
595
-
596
- | Method | Description |
597
- | ---------------------------------------- | ------------------- |
598
- | `createBookmark(name)` | Create bookmark |
599
- | `createHeadingBookmark(text)` | Auto-named bookmark |
600
- | `getBookmark(name)` | Get by name |
601
- | `hasBookmark(name)` | Check existence |
602
- | `addBookmarkToParagraph(para, bookmark)` | Add to paragraph |
603
-
604
- #### Comments
605
-
606
- | Method | Description |
607
- | ------------------------------------------- | ----------------- |
608
- | `createComment(author, content, initials?)` | Add comment |
609
- | `createReply(parentId, author, content)` | Reply to comment |
610
- | `getComment(id)` | Get by ID |
611
- | `getAllComments()` | Get all top-level |
612
- | `addCommentToParagraph(para, comment)` | Add to paragraph |
613
-
614
- #### Track Changes
615
-
616
- | Method | Description |
617
- | ------------------------------------ | ----------------- |
618
- | `trackInsertion(para, author, text)` | Track insertion |
619
- | `trackDeletion(para, author, text)` | Track deletion |
620
- | `isTrackingChanges()` | Check if tracking |
621
- | `getRevisionStats()` | Get statistics |
622
-
623
- #### Footnotes & Endnotes
624
-
625
- | Method | Description |
626
- | -------------------------- | ---------------- |
627
- | `FootnoteManager.create()` | Manage footnotes |
628
- | `EndnoteManager.create()` | Manage endnotes |
629
-
630
- #### Document Helper Functions
631
-
632
- High-level helper methods for common document formatting tasks:
633
-
634
- | Method | Description |
635
- | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
636
- | `applyCustomFormattingToExistingStyles()`| Modify Heading1, Heading2, Normal styles with Verdana font, specific spacing, single line spacing, wrap Heading2 in tables, right-align "Top of Document" hyperlinks, set all hyperlinks to blue, and hide TOC page numbers |
637
- | `wrapParagraphInTable(para, options?)` | Wrap a paragraph in a 1x1 table with optional shading, margins, and width settings |
638
- | `isParagraphInTable(para)` | Check if a paragraph is inside a table; returns `{inTable: boolean, cell?: TableCell}` |
639
- | `updateAllHyperlinkColors(color)` | Set all hyperlinks in the document to a specific color (e.g., '0000FF' for blue) |
640
- | `removeAllHeadersFooters()` | Remove all headers and footers from the document; returns count of headers/footers removed |
641
-
642
- **Example - Using Helper Functions:**
643
-
644
- ```typescript
645
- import { Document } from 'docxmlater';
646
-
647
- const doc = await Document.load('document.docx');
648
-
649
- // Apply comprehensive formatting to standard styles
650
- const results = doc.applyCustomFormattingToExistingStyles();
651
- console.log(`Modified styles:`, results);
652
- // Output: { heading1: true, heading2: true, normal: true }
653
-
654
- // Wrap a specific paragraph in a table
655
- const para = doc.getParagraphs()[0];
656
- doc.wrapParagraphInTable(para, {
657
- shading: 'BFBFBF', // Gray background
658
- marginLeft: 101, // 5pt margins
659
- marginRight: 101,
660
- tableWidthPercent: 5000 // 100% width
661
- });
662
-
663
- // Check if a paragraph is in a table
664
- const { inTable, cell } = doc.isParagraphInTable(para);
665
- if (inTable && cell) {
666
- console.log('Paragraph is in a table cell');
667
- cell.setShading({ fill: 'FFFF00' }); // Change to yellow
668
- }
669
-
670
- // Set all hyperlinks to blue
671
- doc.updateAllHyperlinkColors('0000FF');
672
-
673
- // Remove all headers and footers
674
- const removedCount = doc.removeAllHeadersFooters();
675
- console.log(`Removed ${removedCount} headers and footers`);
676
-
677
- await doc.save('formatted.docx');
678
- ```
679
-
680
- **Note on `applyCustomFormattingToExistingStyles()`:**
681
-
682
- This helper function applies a comprehensive set of formatting rules:
683
- - **Heading1**: 18pt black bold Verdana, left aligned, 0pt before/12pt after, single line spacing
684
- - **Heading2**: 14pt black bold Verdana, left aligned, 6pt before/after, single line spacing, wrapped in gray tables (100% width)
685
- - **Normal**: 12pt Verdana, left aligned, 3pt before/after, single line spacing
686
- - **All Styles**: Removes italic and underline formatting
687
- - **Hyperlinks**: "Top of the Document" links are right-aligned with 0pt spacing; all hyperlinks set to blue (#0000FF)
688
- - **Empty Paragraphs**: Empty Heading2 paragraphs are skipped (not wrapped in tables)
689
- - **TOC Elements**: All Table of Contents have page numbers hidden (showPageNumbers=false, hideInWebLayout=true with \n and \z switches)
690
-
691
- Per ECMA-376 §17.7.2, direct formatting in document.xml overrides style definitions. This method automatically clears conflicting direct formatting to ensure style changes take effect.
692
-
693
- ### Low-Level Document Parts
694
-
695
- | Method | Description | Example |
696
- | ---------------------------- | --------------------- | ------------------------------------------------- |
697
- | `getPart(partName)` | Get document part | `doc.getPart('word/document.xml')` |
698
- | `setPart(partName, content)` | Set document part | `doc.setPart('custom.xml', data)` |
699
- | `removePart(partName)` | Remove part | `doc.removePart('custom.xml')` |
700
- | `listParts()` | List all parts | `const parts = await doc.listParts()` |
701
- | `partExists(partName)` | Check part exists | `if (await doc.partExists('...'))` |
702
- | `getContentTypes()` | Get content types | `const types = await doc.getContentTypes()` |
703
- | `addContentType(part, type)` | Register content type | `doc.addContentType('.json', 'application/json')` |
704
-
705
- ### Unit Conversion Utilities
706
-
707
- #### Twips Conversions
708
- | Function | Description | Example |
709
- | ------------------------- | ------------------- | --------------------------- |
710
- | `twipsToPoints(twips)` | Twips to points | `twipsToPoints(240)` // 12 |
711
- | `twipsToInches(twips)` | Twips to inches | `twipsToInches(1440)` // 1 |
712
- | `twipsToCm(twips)` | Twips to cm | `twipsToCm(1440)` // 2.54 |
713
- | `twipsToEmus(twips)` | Twips to EMUs | `twipsToEmus(1440)` |
714
-
715
- #### EMUs (English Metric Units) Conversions
716
- | Function | Description | Example |
717
- | --------------------------- | -------------------- | ----------------------------- |
718
- | `emusToTwips(emus)` | EMUs to twips | `emusToTwips(914400)` // 1440 |
719
- | `emusToInches(emus)` | EMUs to inches | `emusToInches(914400)` // 1 |
720
- | `emusToCm(emus)` | EMUs to cm | `emusToCm(914400)` // 2.54 |
721
- | `emusToPoints(emus)` | EMUs to points | `emusToPoints(914400)` // 72 |
722
- | `emusToPixels(emus, dpi?)` | EMUs to pixels | `emusToPixels(914400)` // 96 |
723
-
724
- #### Points Conversions
725
- | Function | Description | Example |
726
- | ------------------------ | ------------------ | -------------------------- |
727
- | `pointsToTwips(points)` | Points to twips | `pointsToTwips(12)` // 240 |
728
- | `pointsToEmus(points)` | Points to EMUs | `pointsToEmus(72)` |
729
- | `pointsToInches(points)` | Points to inches | `pointsToInches(72)` // 1 |
730
- | `pointsToCm(points)` | Points to cm | `pointsToCm(72)` // 2.54 |
731
-
732
- #### Inches Conversions
733
- | Function | Description | Example |
734
- | ----------------------------- | ------------------- | ----------------------------- |
735
- | `inchesToTwips(inches)` | Inches to twips | `inchesToTwips(1)` // 1440 |
736
- | `inchesToEmus(inches)` | Inches to EMUs | `inchesToEmus(1)` // 914400 |
737
- | `inchesToPoints(inches)` | Inches to points | `inchesToPoints(1)` // 72 |
738
- | `inchesToCm(inches)` | Inches to cm | `inchesToCm(1)` // 2.54 |
739
- | `inchesToPixels(inches, dpi)` | Inches to pixels | `inchesToPixels(1, 96)` // 96 |
740
-
741
- #### Centimeters Conversions
742
- | Function | Description | Example |
743
- | ----------------------- | ---------------- | --------------------------- |
744
- | `cmToTwips(cm)` | cm to twips | `cmToTwips(2.54)` // 1440 |
745
- | `cmToEmus(cm)` | cm to EMUs | `cmToEmus(2.54)` // 914400 |
746
- | `cmToInches(cm)` | cm to inches | `cmToInches(2.54)` // 1 |
747
- | `cmToPoints(cm)` | cm to points | `cmToPoints(2.54)` // 72 |
748
- | `cmToPixels(cm, dpi?)` | cm to pixels | `cmToPixels(2.54, 96)` // 96|
749
-
750
- #### Pixels Conversions
751
- | Function | Description | Example |
752
- | ---------------------------- | ------------------- | ------------------------------ |
753
- | `pixelsToEmus(pixels, dpi?)` | Pixels to EMUs | `pixelsToEmus(96)` // 914400 |
754
- | `pixelsToInches(pixels, dpi?)`| Pixels to inches | `pixelsToInches(96, 96)` // 1 |
755
- | `pixelsToTwips(pixels, dpi?)`| Pixels to twips | `pixelsToTwips(96, 96)` // 1440|
756
- | `pixelsToCm(pixels, dpi?)` | Pixels to cm | `pixelsToCm(96, 96)` // 2.54 |
757
- | `pixelsToPoints(pixels, dpi?)`| Pixels to points | `pixelsToPoints(96, 96)` // 72 |
758
-
759
- **Note:** Default DPI is 96 for pixel conversions
760
-
761
- ### ZIP Archive Helper Methods
762
-
763
- #### File Operations
764
- | Method | Description | Example |
765
- | ------------------------------- | ------------------------- | -------------------------------------------- |
766
- | `addFile(path, content)` | Add file to archive | `handler.addFile('doc.xml', xmlContent)` |
767
- | `updateFile(path, content)` | Update existing file | `handler.updateFile('doc.xml', newContent)` |
768
- | `removeFile(path)` | Remove file from archive | `handler.removeFile('old.xml')` |
769
- | `renameFile(oldPath, newPath)` | Rename file | `handler.renameFile('a.xml', 'b.xml')` |
770
- | `copyFile(srcPath, destPath)` | Copy file | `handler.copyFile('a.xml', 'copy-a.xml')` |
771
- | `moveFile(srcPath, destPath)` | Move file | `handler.moveFile('a.xml', 'folder/a.xml')` |
772
-
773
- #### File Retrieval
774
- | Method | Description | Returns |
775
- | ------------------------- | ---------------------- | --------------- |
776
- | `getFile(path)` | Get file object | `ZipFile` |
777
- | `getFileAsString(path)` | Get file as string | `string` |
778
- | `getFileAsBuffer(path)` | Get file as buffer | `Buffer` |
779
- | `hasFile(path)` | Check if file exists | `boolean` |
780
- | `getFilePaths()` | Get all file paths | `string[]` |
781
- | `getAllFiles()` | Get all files | `FileMap` |
782
-
783
- #### Batch Operations
784
- | Method | Description | Returns |
785
- | ------------------------------- | ---------------------------- | -------------- |
786
- | `removeFiles(paths[])` | Remove multiple files | `number` |
787
- | `getFilesByExtension(ext)` | Get files by extension | `ZipFile[]` |
788
- | `getTextFiles()` | Get all text files | `ZipFile[]` |
789
- | `getBinaryFiles()` | Get all binary files | `ZipFile[]` |
790
- | `getMediaFiles()` | Get media files | `ZipFile[]` |
791
-
792
- #### Archive Information
793
- | Method | Description | Returns |
794
- | ------------------ | ------------------------- | ------------------------ |
795
- | `getFileCount()` | Count files in archive | `number` |
796
- | `getTotalSize()` | Get total size in bytes | `number` |
797
- | `getStats()` | Get detailed statistics | `{fileCount, size, ...}` |
798
- | `isEmpty()` | Check if archive is empty | `boolean` |
799
-
800
- #### Import/Export
801
- | Method | Description | Returns |
802
- | -------------------------------- | ------------------------ | -------------------- |
803
- | `exportFile(internal, external)` | Export file from archive | `Promise<void>` |
804
- | `importFile(external, internal)` | Import file to archive | `Promise<void>` |
805
-
806
- ## Common Recipes
807
-
808
- ### Create a Simple Document
809
-
810
- ```typescript
811
- const doc = Document.create();
812
- doc.createParagraph("Title").setStyle("Title");
813
- doc.createParagraph("This is a simple document.");
814
- await doc.save("simple.docx");
815
- ```
816
-
817
- ### Add Formatted Text
818
-
819
- ```typescript
820
- const para = doc.createParagraph();
821
- para.addText("Bold", { bold: true });
822
- para.addText(" and ");
823
- para.addText("Colored", { color: "FF0000" });
824
- ```
825
-
826
- ### Create a Table with Borders
827
-
828
- ```typescript
829
- const table = doc.createTable(3, 3);
830
- table.setAllBorders({ style: "single", size: 8, color: "000000" });
831
- table.getCell(0, 0)?.createParagraph("Header 1");
832
- table.getRow(0)?.getCell(0)?.setShading({ fill: "4472C4" });
833
- ```
834
-
835
- ### Insert an Image
836
-
837
- ```typescript
838
- import { Image, inchesToEmus } from "docxmlater";
839
-
840
- const image = Image.fromFile("./photo.jpg");
841
- image.setWidth(inchesToEmus(4), true); // 4 inches, maintain ratio
842
- doc.addImage(image);
843
- ```
844
-
845
- ### Add a Hyperlink
846
-
847
- ```typescript
848
- const para = doc.createParagraph();
849
- para.addText("Visit ");
850
- para.addHyperlink(
851
- Hyperlink.createExternal("https://example.com", "our website")
852
- );
853
- ```
854
-
855
- ### Search and Replace Text
856
-
857
- ```typescript
858
- // Find all occurrences
859
- const results = doc.findText("old text", { caseSensitive: true });
860
- console.log(`Found ${results.length} occurrences`);
861
-
862
- // Replace all
863
- const count = doc.replaceText("old text", "new text", { wholeWord: true });
864
- console.log(`Replaced ${count} occurrences`);
865
- ```
866
-
867
- ### Load and Modify Existing Document
868
-
869
- ```typescript
870
- const doc = await Document.load("existing.docx");
871
- doc.createParagraph("Added paragraph");
872
-
873
- // Update all hyperlinks
874
- const urlMap = new Map([["https://old-site.com", "https://new-site.com"]]);
875
- doc.updateHyperlinkUrls(urlMap);
876
-
877
- await doc.save("modified.docx");
878
- ```
879
-
880
- ### Create Lists
881
-
882
- ```typescript
883
- // Bullet list
884
- const bulletId = doc.createBulletList(3);
885
- doc.createParagraph("First item").setNumbering(bulletId, 0);
886
- doc.createParagraph("Second item").setNumbering(bulletId, 0);
887
-
888
- // Numbered list
889
- const numberId = doc.createNumberedList(3);
890
- doc.createParagraph("Step 1").setNumbering(numberId, 0);
891
- doc.createParagraph("Step 2").setNumbering(numberId, 0);
892
- ```
893
-
894
- ### Apply Custom Styles
895
-
896
- ```typescript
897
- import { Style } from "docxmlater";
898
-
899
- const customStyle = Style.create({
900
- styleId: "CustomHeading",
901
- name: "Custom Heading",
902
- basedOn: "Normal",
903
- runFormatting: { bold: true, size: 14, color: "2E74B5" },
904
- paragraphFormatting: { alignment: "center", spaceAfter: 240 },
905
- });
906
-
907
- doc.addStyle(customStyle);
908
- doc.createParagraph("Custom Styled Text").setStyle("CustomHeading");
909
- ```
910
-
911
- ### Build Content with Detached Paragraphs
912
-
913
- Create paragraphs independently and add them conditionally:
914
-
915
- ```typescript
916
- import { Paragraph } from "docxmlater";
917
-
918
- // Create reusable paragraph templates
919
- const warningTemplate = Paragraph.createFormatted(
920
- "WARNING: ",
921
- { bold: true, color: "FF6600" },
922
- { spacing: { before: 120, after: 120 } }
923
- );
924
-
925
- // Clone and customize
926
- const warning1 = warningTemplate.clone();
927
- warning1.addText("Please read the documentation before proceeding.");
928
-
929
- // Build content from data
930
- const items = [
931
- { title: "First Item", description: "Description here" },
932
- { title: "Second Item", description: "Another description" },
933
- ];
934
-
935
- items.forEach((item, index) => {
936
- const titlePara = Paragraph.create(`${index + 1}. `);
937
- titlePara.addText(item.title, { bold: true });
938
-
939
- const descPara = Paragraph.create(item.description, {
940
- indentation: { left: 360 },
941
- });
942
-
943
- doc.addParagraph(titlePara);
944
- doc.addParagraph(descPara);
945
- });
946
-
947
- // See examples/advanced/detached-paragraphs.ts for more patterns
948
- ```
949
-
950
- ### Add Headers and Footers
951
-
952
- ```typescript
953
- import { Header, Footer, Field } from "docxmlater";
954
-
955
- // Header with page numbers
956
- const header = Header.create();
957
- header.addParagraph("Document Title").setAlignment("center");
958
-
959
- // Footer with page numbers
960
- const footer = Footer.create();
961
- const footerPara = footer.addParagraph();
962
- footerPara.addText("Page ");
963
- footerPara.addField(Field.create({ type: "PAGE" }));
964
- footerPara.addText(" of ");
965
- footerPara.addField(Field.create({ type: "NUMPAGES" }));
966
-
967
- doc.setHeader(header);
968
- doc.setFooter(footer);
969
- ```
970
-
971
- ### Work with Document Statistics
972
-
973
- ```typescript
974
- // Get word and character counts
975
- console.log("Words:", doc.getWordCount());
976
- console.log("Characters:", doc.getCharacterCount());
977
- console.log("Characters (no spaces):", doc.getCharacterCount(false));
978
-
979
- // Check document size
980
- const size = doc.estimateSize();
981
- if (size.warning) {
982
- console.warn(size.warning);
983
- }
984
- console.log(`Estimated size: ${size.totalEstimatedMB} MB`);
985
- ```
986
-
987
- ### Handle Large Documents Efficiently
988
-
989
- ```typescript
990
- const doc = Document.create({
991
- maxMemoryUsagePercent: 80,
992
- maxRssMB: 2048,
993
- maxImageCount: 50,
994
- maxTotalImageSizeMB: 100,
995
- });
996
-
997
- // Process document...
998
-
999
- // Clean up resources after saving
1000
- await doc.save("large-document.docx");
1001
- doc.dispose(); // Free memory
1002
- ```
1003
-
1004
- ### Direct XML Access (Advanced)
1005
-
1006
- ```typescript
1007
- // Get raw XML
1008
- const documentXml = await doc.getPart("word/document.xml");
1009
- console.log(documentXml?.content);
1010
-
1011
- // Modify raw XML (use with caution)
1012
- await doc.setPart("word/custom.xml", "<custom>data</custom>");
1013
- await doc.addContentType("/word/custom.xml", "application/xml");
1014
-
1015
- // List all parts
1016
- const parts = await doc.listParts();
1017
- console.log("Document contains:", parts.length, "parts");
1018
- ```
1019
-
1020
- ## Features
1021
-
1022
- - **Full OpenXML Compliance** - Follows ECMA-376 standard
1023
- - **TypeScript First** - Complete type definitions
1024
- - **Memory Efficient** - Handles large documents with streaming
1025
- - **Atomic Saves** - Prevents corruption with temp file pattern
1026
- - **Rich Formatting** - Complete text and paragraph formatting
1027
- - **Tables** - Full support with borders, shading, merging
1028
- - **Images** - PNG, JPEG, GIF with sizing and positioning
1029
- - **Hyperlinks** - External, internal, and email links
1030
- - **Styles** - 13 built-in styles + custom style creation
1031
- - **Lists** - Bullets, numbering, multi-level
1032
- - **Headers/Footers** - Different first/even/odd pages
1033
- - **Search & Replace** - With case and whole word options
1034
- - **Document Stats** - Word count, character count, size estimation
1035
- - **Track Changes** - Insertions and deletions with authors
1036
- - **Comments** - With replies and threading
1037
- - **Bookmarks** - For internal navigation
1038
- - **Low-level Access** - Direct ZIP and XML manipulation
1039
-
1040
- ## Performance
1041
-
1042
- - Process 100+ page documents efficiently
1043
- - Atomic save pattern prevents corruption
1044
- - Memory management for large files
1045
- - Lazy loading of document parts
1046
- - Resource cleanup with `dispose()`
1047
-
1048
- ## Testing
1049
-
1050
- ```bash
1051
- npm test # Run all tests
1052
- npm run test:watch # Watch mode
1053
- npm run test:coverage # Coverage report
1054
- ```
1055
-
1056
- **Current:** 1,119 tests passing (97.3% pass rate) | 100% core functionality covered
1057
-
1058
- ## Development
1059
-
1060
- ```bash
1061
- # Install dependencies
1062
- npm install
1063
-
1064
- # Build TypeScript
1065
- npm run build
1066
-
1067
- # Run examples
1068
- npx ts-node examples/simple-document.ts
1069
- ```
1070
-
1071
- ## Project Structure
1072
-
1073
- ```text
1074
- src/
1075
- ├── core/ # Document, Parser, Generator, Validator
1076
- ├── elements/ # Paragraph, Run, Table, Image, Hyperlink
1077
- ├── formatting/ # Style, NumberingManager
1078
- ├── xml/ # XMLBuilder, XMLParser
1079
- ├── zip/ # ZipHandler for DOCX manipulation
1080
- └── utils/ # Validation, Units conversion
1081
-
1082
- examples/
1083
- ├── 01-basic/ # Simple document creation
1084
- ├── 02-text/ # Text formatting examples
1085
- ├── 03-tables/ # Table examples
1086
- ├── 04-styles/ # Style examples
1087
- ├── 05-images/ # Image handling
1088
- ├── 06-complete/ # Full document examples
1089
- └── 07-hyperlinks/ # Link examples
1090
- ```
1091
-
1092
- ## Hierarchy
1093
-
1094
- ```text
1095
- w:document (root)
1096
- └── w:body (body container)
1097
- ├── w:p (paragraph) [1..n]
1098
- │ ├── w:pPr (paragraph properties) [0..1]
1099
- │ │ ├── w:pStyle (style reference)
1100
- │ │ ├── w:jc (justification/alignment)
1101
- │ │ ├── w:ind (indentation)
1102
- │ │ └── w:spacing (spacing before/after)
1103
- ├── w:r (run) [1..n]
1104
- │ │ ├── w:rPr (run properties) [0..1]
1105
- │ │ │ ├── w:b (bold)
1106
- │ │ │ ├── w:i (italic)
1107
- │ │ │ ├── w:u (underline)
1108
- │ │ │ ├── w:sz (font size)
1109
- │ │ │ └── w:color (text color)
1110
- │ │ └── w:t (text content) [1]
1111
- │ ├── w:hyperlink (hyperlink) [0..n]
1112
- │ │ └── w:r (run with hyperlink text)
1113
- │ └── w:drawing (embedded image/shape) [0..n]
1114
- ├── w:tbl (table) [1..n]
1115
- ├── w:tblPr (table properties)
1116
- └── w:tr (table row) [1..n]
1117
- └── w:tc (table cell) [1..n]
1118
- └── w:p (paragraph in cell)
1119
- └── w:sectPr (section properties) [1] (must be last child of w:body)
1120
- ```
1121
-
1122
- ## Requirements
1123
-
1124
- - Node.js 16+
1125
- - TypeScript 5.0+ (for development)
1126
-
1127
- ## Installation Options
1128
-
1129
- ```bash
1130
- # NPM
1131
- npm install docxmlater
1132
-
1133
- # Yarn
1134
- yarn add docxmlater
1135
-
1136
- # PNPM
1137
- pnpm add docxmlater
1138
- ```
1139
-
1140
- ## Troubleshooting
1141
-
1142
- ### XML Corruption in Text
1143
-
1144
- **Problem**: Text displays with XML tags like `Important Information<w:t xml:space="preserve">1` in Word.
1145
-
1146
- **Cause**: Passing XML-like strings to text methods instead of using the API properly.
1147
-
1148
- ```typescript
1149
- // WRONG - Will display escaped XML as literal text
1150
- paragraph.addText("Important Information<w:t>1</w:t>");
1151
- // Displays as: "Important Information<w:t>1</w:t>"
1152
-
1153
- // CORRECT - Use separate text runs
1154
- paragraph.addText("Important Information");
1155
- paragraph.addText("1");
1156
- // Displays as: "Important Information1"
1157
-
1158
- // Or combine in one call
1159
- paragraph.addText("Important Information 1");
1160
- ```
1161
-
1162
- **Detection**: Use the corruption detection utility to find issues:
1163
-
1164
- ```typescript
1165
- import { detectCorruptionInDocument } from "docxmlater";
1166
-
1167
- const doc = await Document.load("file.docx");
1168
- const report = detectCorruptionInDocument(doc);
1169
-
1170
- if (report.isCorrupted) {
1171
- console.log(report.summary);
1172
- report.locations.forEach((loc) => {
1173
- console.log(`Paragraph ${loc.paragraphIndex}, Run ${loc.runIndex}:`);
1174
- console.log(` Original: ${loc.text}`);
1175
- console.log(` Fixed: ${loc.suggestedFix}`);
1176
- });
1177
- }
1178
- ```
1179
-
1180
- **Auto-Cleaning**: XML patterns are automatically removed by default for defensive data handling:
1181
-
1182
- ```typescript
1183
- // Default behavior - auto-clean enabled
1184
- const run = new Run("Text<w:t>value</w:t>");
1185
- // Result: "Textvalue" (XML tags removed automatically)
1186
-
1187
- // Disable auto-cleaning (for debugging)
1188
- const run = new Run("Text<w:t>value</w:t>", { cleanXmlFromText: false });
1189
- // Result: "Text<w:t>value</w:t>" (XML tags preserved, will display in Word)
1190
- ```
1191
-
1192
- **Why This Happens**: The framework correctly escapes XML special characters per the XML specification. When you pass XML tags as text, they are properly escaped (`<` becomes `&lt;`) and Word displays them as literal text, not as markup.
1193
-
1194
- **The Right Approach**: Use the framework's API methods instead of embedding XML:
1195
-
1196
- - Use `paragraph.addText()` multiple times for separate text runs
1197
- - Use formatting options: `{bold: true}`, `{italic: true}`, etc.
1198
- - Use `paragraph.addHyperlink()` for links
1199
- - Don't pass XML strings to text methods
1200
- - Don't try to embed `<w:t>` or other XML tags in your text
1201
-
1202
- For more details, see the [corruption detection examples](examples/troubleshooting/).
1203
-
1204
- ### Layout Conflicts (Massive Whitespace)
1205
-
1206
- **Problem**: Documents show massive whitespace between paragraphs when opened in Word, even though the XML looks correct.
1207
-
1208
- **Cause**: The `pageBreakBefore` property conflicting with `keepNext`/`keepLines` properties. When a paragraph has both `pageBreakBefore` and keep properties set to true, Word's layout engine tries to satisfy contradictory constraints (insert break vs. keep together), resulting in massive whitespace as it struggles to resolve the conflict.
1209
-
1210
- **Why This Causes Problems**:
1211
-
1212
- - `pageBreakBefore` tells Word to insert a page break before the paragraph
1213
- - `keepNext` tells Word to keep the paragraph with the next one (no break)
1214
- - `keepLines` tells Word to keep all lines together (no break)
1215
- - The combination creates layout conflicts that manifest as massive whitespace
1216
-
1217
- **Automatic Conflict Resolution** (v0.28.2+):
1218
-
1219
- The framework now automatically prevents these conflicts by **prioritizing keep properties over page breaks**:
1220
-
1221
- ```typescript
1222
- // When setting keepNext or keepLines, pageBreakBefore is automatically cleared
1223
- const para = new Paragraph()
1224
- .addText("Content")
1225
- .setPageBreakBefore(true) // Set to true
1226
- .setKeepNext(true); // Automatically clears pageBreakBefore
1227
-
1228
- // Result: keepNext=true, pageBreakBefore=false (conflict resolved)
1229
- ```
1230
-
1231
- **Why This Priority?**
1232
-
1233
- - Keep properties (`keepNext`/`keepLines`) represent explicit user intent to keep content together
1234
- - Page breaks are often layout hints that may conflict with document flow
1235
- - Removing `pageBreakBefore` eliminates whitespace while preserving the user's intention
1236
-
1237
- **Parsing Documents**:
1238
-
1239
- When loading existing DOCX files with conflicts, they are automatically resolved:
1240
-
1241
- ```typescript
1242
- // Load document with conflicts
1243
- const doc = await Document.load("document-with-conflicts.docx");
1244
-
1245
- // Conflicts are automatically resolved during parsing
1246
- // keepNext/keepLines take priority, pageBreakBefore is removed
1247
- ```
1248
-
1249
- **How It Works**:
1250
-
1251
- 1. When `setKeepNext(true)` is called, `pageBreakBefore` is automatically set to `false`
1252
- 2. When `setKeepLines(true)` is called, `pageBreakBefore` is automatically set to `false`
1253
- 3. When parsing documents, if both properties exist, `pageBreakBefore` is cleared
1254
- 4. Keep properties win because they represent explicit user intent
1255
-
1256
- **Manual Override**:
1257
-
1258
- If you need a page break despite keep properties, set it after:
1259
-
1260
- ```typescript
1261
- const para = new Paragraph()
1262
- .setKeepNext(true) // Set first
1263
- .setPageBreakBefore(true); // Override - you explicitly want this conflict
1264
-
1265
- // But note: This will cause layout issues (whitespace) in Word
1266
- ```
1267
-
1268
- ## Known Limitations
1269
-
1270
- While docXMLater provides comprehensive DOCX manipulation capabilities, there are some features that are not yet fully implemented:
1271
-
1272
- ### 1. Table Row Spanning with vMerge
1273
-
1274
- **Status:** FULLY IMPLEMENTED ✅
1275
-
1276
- **What Works:**
1277
- - Column spanning (horizontal cell merging) is fully supported
1278
- - Row spanning (vertical cell merging) is now fully implemented
1279
- - Both horizontal and vertical merging can be combined
1280
- - Uses Word's proper `vMerge` attribute ('restart' and 'continue')
1281
-
1282
- **Usage:**
1283
- ```typescript
1284
- // Merge cells horizontally (column spanning)
1285
- table.mergeCells(0, 0, 0, 2); // Merge columns 0-2 in row 0
1286
-
1287
- // Merge cells vertically (row spanning)
1288
- table.mergeCells(0, 0, 2, 0); // Merge rows 0-2 in column 0
1289
-
1290
- // Merge both horizontally and vertically (2x2 block)
1291
- table.mergeCells(0, 0, 1, 1); // Merge 2x2 block starting at (0,0)
1292
- ```
1293
-
1294
- ### 2. Structured Document Tags (SDT) Parsing
1295
-
1296
- **Status:** FULLY IMPLEMENTED
1297
-
1298
- **What Works:**
1299
- - Complete SDT parsing from existing documents
1300
- - All 9 control types supported (richText, plainText, comboBox, dropDownList, datePicker, checkbox, picture, buildingBlock, group)
1301
- - SDT properties fully extracted (id, tag, lock, alias, controlType)
1302
- - Nested content parsing (paragraphs, tables, nested SDTs)
1303
- - Preserves element order using XMLParser's `_orderedChildren` metadata
1304
- - Round-trip operations fully supported
1305
-
1306
- **Control Types Supported:**
1307
- - **Rich Text** - Multi-formatted text content
1308
- - **Plain Text** - Simple text with optional multiLine support
1309
- - **Combo Box** - User-editable dropdown with list items
1310
- - **Dropdown List** - Fixed selection from list items
1311
- - **Date Picker** - Date selection with format and calendar type
1312
- - **Checkbox** - Boolean selection with custom checked/unchecked states
1313
- - **Picture** - Image content control
1314
- - **Building Block** - Gallery and category-based content
1315
- - **Group** - Grouping of other controls
1316
-
1317
- **Usage:**
1318
- ```typescript
1319
- // Load documents with SDTs - fully parsed
1320
- const doc = await Document.load('document-with-sdts.docx');
1321
-
1322
- // Access parsed SDT content
1323
- const sdts = doc.getBodyElements().filter(el => el instanceof StructuredDocumentTag);
1324
- for (const sdt of sdts) {
1325
- console.log('ID:', sdt.getId());
1326
- console.log('Tag:', sdt.getTag());
1327
- console.log('Type:', sdt.getControlType());
1328
- console.log('Content:', sdt.getContent());
1329
- }
1330
-
1331
- // Create new SDTs programmatically
1332
- const sdt = new StructuredDocumentTag({
1333
- id: 123456,
1334
- tag: 'MyControl',
1335
- controlType: 'richText',
1336
- alias: 'Rich Text Control'
1337
- });
1338
- sdt.addContent(paragraph);
1339
- ```
1340
-
1341
- All known limitations have been resolved! For feature requests or bug reports, please visit our [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues).
1342
-
1343
- ## Contributing
1344
-
1345
- Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md).
1346
-
1347
- 1. Fork the repository
1348
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1349
- 3. Commit changes (`git commit -m 'Add amazing feature'`)
1350
- 4. Push to branch (`git push origin feature/amazing-feature`)
1351
- 5. Open a Pull Request
1352
-
1353
- ## Recent Updates (v1.3.0)
1354
-
1355
- **Enhanced Document Parsing & Helper Functions:**
1356
-
1357
- - **TOC Parsing** - Parse Table of Contents from existing DOCX files
1358
- - Extract TOC field instructions with all switches (\h, \u, \z, \n, \o, \t)
1359
- - Detect SDT wrappers with `docPartGallery="Table of Contents"`
1360
- - Create `TableOfContentsElement` objects from parsed TOCs
1361
- - Support for modifying TOC field instructions in loaded documents
1362
- - **removeAllHeadersFooters() Helper** - New document helper function
1363
- - Removes all headers and footers from the document
1364
- - Deletes header/footer XML files and relationships
1365
- - Returns count of removed headers/footers
1366
- - **Enhanced Test Suite** - 1,119/1,150 tests passing (97.3% pass rate)
1367
- - **Documentation Updates** - Complete API reference for new helper functions
1368
-
1369
- **Previous Enhancements (v1.2.0):**
1370
- - 5 advanced document helper functions
1371
- - Enhanced document modification capabilities
1372
- - Improved paragraph and table wrapping utilities
1373
-
1374
- ## License
1375
-
1376
- MIT © DiaTech
1377
-
1378
- ## Acknowledgments
1379
-
1380
- - Built with [JSZip](https://stuk.github.io/jszip/) for ZIP handling
1381
- - Follows [ECMA-376](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/) Office Open XML standard
1382
- - Inspired by [python-docx](https://python-docx.readthedocs.io/) and [docx](https://github.com/dolanmiu/docx)
1383
-
1384
- ## Support
1385
-
1386
- - **Documentation**: [Full Docs](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs)
1387
- - **Examples**: [Example Code](https://github.com/ItMeDiaTech/docXMLater/tree/main/examples)
1388
- - **Issues**: [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues)
1389
- - **Discussions**: [GitHub Discussions](https://github.com/ItMeDiaTech/docXMLater/discussions)
1390
-
1391
- ## Quick Links
1392
-
1393
- - [NPM Package](https://www.npmjs.com/package/docxmlater)
1394
- - [GitHub Repository](https://github.com/ItMeDiaTech/docXMLater)
1395
- - [API Reference](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs/api)
1396
- - [Change Log](https://github.com/ItMeDiaTech/docXMLater/blob/main/CHANGELOG.md)
1397
-
1398
- ---
1399
-
1400
- **Ready to create amazing Word documents?** Start with our [examples](https://github.com/ItMeDiaTech/docXMLater/tree/main/examples) or dive into the [API Reference](#complete-api-reference) above!
1
+ # docXMLater - Professional DOCX Framework
2
+
3
+ [![npm version](https://img.shields.io/npm/v/docxmlater.svg)](https://www.npmjs.com/package/docxmlater)
4
+ [![Tests](https://img.shields.io/badge/tests-1119%20passing-brightgreen)](https://github.com/ItMeDiaTech/docXMLater)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ A comprehensive, production-ready TypeScript/JavaScript library for creating, reading, and manipulating Microsoft Word (.docx) documents programmatically. Full OpenXML compliance with extensive API coverage and robust test suite.
9
+
10
+ Built for professional documentation work, docXMLater provides a complete solution for programmatic DOCX manipulation with an intuitive API and helper functions for all aspects of document creation and modification.
11
+
12
+ ## Latest Updates - v1.4.3
13
+
14
+ **Critical Fix: Header/Footer Parsing & Enhanced Management:**
15
+
16
+ ### What's New in v1.4.3
17
+
18
+ - **Header/Footer Parsing Fix:** Documents with headers/footers now load and save correctly
19
+ - Fixed [Content_Types].xml corruption when loading documents with headers/footers
20
+ - Headers and footers are now properly parsed when loading existing documents
21
+ - All header/footer XML files are correctly declared in [Content_Types].xml
22
+ - **New Header/Footer Management Methods:**
23
+ - `removeHeader(type)` - Remove specific header (default/first/even)
24
+ - `removeFooter(type)` - Remove specific footer (default/first/even)
25
+ - `clearHeaders()` - Remove all headers while preserving footers
26
+ - `clearFooters()` - Remove all footers while preserving headers
27
+ - **Round-Trip Support:** Full load-modify-save cycle for documents with headers/footers
28
+ - **MS Word Compliance:** Follows ECMA-376 specifications for header/footer handling
29
+
30
+ ### Previous Updates - v1.3.0
31
+
32
+ - **TOC Parsing:** Parse Table of Contents from existing documents with full SDT support
33
+ - **TOC Modification:** Modify TOC field instructions (add/remove switches)
34
+ - **Complete Feature Set:** All 102 major features implemented
35
+ - **Table Styles:** Full support with 12 conditional formatting types
36
+ - **Content Controls:** 9 control types supported
37
+ - **Field Types:** 11 field types (PAGE, NUMPAGES, DATE, TIME, FILENAME, AUTHOR, TITLE, REF, HYPERLINK, SEQ, TC/XE)
38
+ - **Production Ready:** Full ECMA-376 compliance
39
+
40
+ **Test Results:** 1,122/1,150 tests passing (97.6% pass rate - 1,122 core features validated)
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ npm install docxmlater
46
+ ```
47
+
48
+ ```typescript
49
+ import { Document } from "docxmlater";
50
+
51
+ // Create document
52
+ const doc = Document.create();
53
+ doc.createParagraph("Hello World").setStyle("Title");
54
+
55
+ // Save document
56
+ await doc.save("output.docx");
57
+ ```
58
+
59
+ ## Complete API Reference
60
+
61
+ ### Document Operations
62
+
63
+ | Method | Description | Example |
64
+ | --------------------------------- | ----------------------- | ------------------------------------------------ |
65
+ | `Document.create(options?)` | Create new document | `const doc = Document.create()` |
66
+ | `Document.createEmpty()` | Create minimal document | `const doc = Document.createEmpty()` |
67
+ | `Document.load(path)` | Load from file | `const doc = await Document.load('file.docx')` |
68
+ | `Document.loadFromBuffer(buffer)` | Load from buffer | `const doc = await Document.loadFromBuffer(buf)` |
69
+ | `save(path)` | Save to file | `await doc.save('output.docx')` |
70
+ | `toBuffer()` | Export as buffer | `const buffer = await doc.toBuffer()` |
71
+ | `dispose()` | Clean up resources | `doc.dispose()` |
72
+
73
+ ### Content Creation
74
+
75
+ | Method | Description | Example |
76
+ | -------------------------------- | ------------------------ | -------------------------------- |
77
+ | `createParagraph(text?)` | Add paragraph | `doc.createParagraph('Text')` |
78
+ | `createTable(rows, cols)` | Add table | `doc.createTable(3, 4)` |
79
+ | `addParagraph(para)` | Add existing paragraph | `doc.addParagraph(myPara)` |
80
+ | `addTable(table)` | Add existing table | `doc.addTable(myTable)` |
81
+ | `addImage(image)` | Add image | `doc.addImage(myImage)` |
82
+ | `addTableOfContents(toc?)` | Add TOC | `doc.addTableOfContents()` |
83
+ | `insertParagraphAt(index, para)` | Insert at position | `doc.insertParagraphAt(0, para)` |
84
+ | `insertTableAt(index, table)` | Insert table at position | `doc.insertTableAt(5, table)` |
85
+ | `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
86
+
87
+ ### Content Manipulation
88
+
89
+ | Method | Description | Returns |
90
+ | --------------------------------- | ---------------------------- | --------- |
91
+ | `replaceParagraphAt(index, para)` | Replace paragraph | `boolean` |
92
+ | `replaceTableAt(index, table)` | Replace table | `boolean` |
93
+ | `moveElement(fromIndex, toIndex)` | Move element to new position | `boolean` |
94
+ | `swapElements(index1, index2)` | Swap two elements | `boolean` |
95
+ | `removeTocAt(index)` | Remove TOC element | `boolean` |
96
+
97
+ ### Content Retrieval
98
+
99
+ | Method | Description | Returns |
100
+ | ---------------------- | --------------------------------------- | ------------------------------------------ |
101
+ | `getParagraphs()` | Get top-level paragraphs | `Paragraph[]` |
102
+ | `getAllParagraphs()` | Get all paragraphs (recursive) | `Paragraph[]` |
103
+ | `getTables()` | Get top-level tables | `Table[]` |
104
+ | `getAllTables()` | Get all tables (recursive) | `Table[]` |
105
+ | `getBodyElements()` | Get all body elements | `BodyElement[]` |
106
+ | `getParagraphCount()` | Count paragraphs | `number` |
107
+ | `getTableCount()` | Count tables | `number` |
108
+ | `getHyperlinks()` | Get all links | `Array<{hyperlink, paragraph}>` |
109
+ | `getBookmarks()` | Get all bookmarks | `Array<{bookmark, paragraph}>` |
110
+ | `getImages()` | Get all images | `Array<{image, relationshipId, filename}>` |
111
+
112
+ **Note**: The `getAllParagraphs()` and `getAllTables()` methods recursively search inside tables and SDTs (Structured Document Tags), while the non-prefixed methods only return top-level elements.
113
+
114
+ **Example - Recursive Element Access:**
115
+
116
+ ```typescript
117
+ import { Document, Hyperlink } from 'docxmlater';
118
+
119
+ // Load document with complex structure (tables, SDTs, nested content)
120
+ const doc = await Document.load('complex.docx');
121
+
122
+ // Get only top-level paragraphs (misses nested content)
123
+ const topLevel = doc.getParagraphs();
124
+ console.log(`Top-level paragraphs: ${topLevel.length}`); // e.g., 37
125
+
126
+ // Get ALL paragraphs including those in tables and SDTs
127
+ const allParas = doc.getAllParagraphs();
128
+ console.log(`All paragraphs: ${allParas.length}`); // e.g., 52
129
+
130
+ // Apply formatting to ALL paragraphs (including nested ones)
131
+ for (const para of allParas) {
132
+ para.setSpaceAfter(120); // Set 6pt spacing after each paragraph
133
+ }
134
+
135
+ // Get all tables including those inside SDTs
136
+ const allTables = doc.getAllTables();
137
+ for (const table of allTables) {
138
+ table.setWidth(5000).setWidthType('pct'); // Set to 100% width
139
+ }
140
+
141
+ // Find all hyperlinks in the entire document
142
+ let hyperlinkCount = 0;
143
+ for (const para of allParas) {
144
+ for (const content of para.getContent()) {
145
+ if (content instanceof Hyperlink) {
146
+ hyperlinkCount++;
147
+ content.setFormatting({ color: '0000FF' }); // Make all links blue
148
+ }
149
+ }
150
+ }
151
+ console.log(`Updated ${hyperlinkCount} hyperlinks`);
152
+ ```
153
+
154
+ ### Content Removal
155
+
156
+ | Method | Description | Returns |
157
+ | ------------------------------ | ------------------ | --------- |
158
+ | `removeParagraph(paraOrIndex)` | Remove paragraph | `boolean` |
159
+ | `removeTable(tableOrIndex)` | Remove table | `boolean` |
160
+ | `clearParagraphs()` | Remove all content | `this` |
161
+
162
+ ### Search & Replace
163
+
164
+ | Method | Description | Options |
165
+ | -------------------------------------- | --------------------- | ------------------------------ |
166
+ | `findText(text, options?)` | Find text occurrences | `{caseSensitive?, wholeWord?}` |
167
+ | `replaceText(find, replace, options?)` | Replace all text | `{caseSensitive?, wholeWord?}` |
168
+ | `updateHyperlinkUrls(urlMap)` | Update hyperlink URLs | `Map<oldUrl, newUrl>` |
169
+
170
+ ### Style Application
171
+
172
+ | Method | Description | Returns |
173
+ | ------------------------------------- | -------------------------------- | ------------------- |
174
+ | `applyStyleToAll(styleId, predicate)` | Apply style to matching elements | `number` |
175
+ | `findElementsByStyle(styleId)` | Find all elements using a style | `Array<Para\|Cell>` |
176
+
177
+ **Example:**
178
+
179
+ ```typescript
180
+ // Apply Heading1 to all paragraphs containing "Chapter"
181
+ const count = doc.applyStyleToAll("Heading1", (el) => {
182
+ return el instanceof Paragraph && el.getText().includes("Chapter");
183
+ });
184
+
185
+ // Find all Heading1 elements
186
+ const headings = doc.findElementsByStyle("Heading1");
187
+ ```
188
+
189
+ ### Document Statistics
190
+
191
+ | Method | Description | Returns |
192
+ | ----------------------------------- | ------------------- | ------------------------------ |
193
+ | `getWordCount()` | Total word count | `number` |
194
+ | `getCharacterCount(includeSpaces?)` | Character count | `number` |
195
+ | `estimateSize()` | Size estimation | `{totalEstimatedMB, warning?}` |
196
+ | `getSizeStats()` | Detailed size stats | `{elements, size, warnings}` |
197
+
198
+ ### Text Formatting
199
+
200
+ | Property | Values | Example |
201
+ | ------------- | -------------------------------- | ----------------------- |
202
+ | `bold` | `true/false` | `{bold: true}` |
203
+ | `italic` | `true/false` | `{italic: true}` |
204
+ | `underline` | `'single'/'double'/'dotted'/etc` | `{underline: 'single'}` |
205
+ | `strike` | `true/false` | `{strike: true}` |
206
+ | `font` | Font name | `{font: 'Arial'}` |
207
+ | `size` | Points | `{size: 12}` |
208
+ | `color` | Hex color | `{color: 'FF0000'}` |
209
+ | `highlight` | Color name | `{highlight: 'yellow'}` |
210
+ | `subscript` | `true/false` | `{subscript: true}` |
211
+ | `superscript` | `true/false` | `{superscript: true}` |
212
+ | `smallCaps` | `true/false` | `{smallCaps: true}` |
213
+ | `allCaps` | `true/false` | `{allCaps: true}` |
214
+
215
+ ### Paragraph Operations
216
+
217
+ #### Creating Detached Paragraphs
218
+
219
+ Create paragraphs independently before adding to a document:
220
+
221
+ ```typescript
222
+ // Create empty paragraph
223
+ const para1 = Paragraph.create();
224
+
225
+ // Create with text
226
+ const para2 = Paragraph.create("Hello World");
227
+
228
+ // Create with text and formatting
229
+ const para3 = Paragraph.create("Centered text", { alignment: "center" });
230
+
231
+ // Create with just formatting
232
+ const para4 = Paragraph.create({
233
+ alignment: "right",
234
+ spacing: { before: 240 },
235
+ });
236
+
237
+ // Create with style
238
+ const heading = Paragraph.createWithStyle("Chapter 1", "Heading1");
239
+
240
+ // Create with both run and paragraph formatting
241
+ const important = Paragraph.createFormatted(
242
+ "Important Text",
243
+ { bold: true, color: "FF0000" },
244
+ { alignment: "center" }
245
+ );
246
+
247
+ // Add to document later
248
+ doc.addParagraph(para1);
249
+ doc.addParagraph(heading);
250
+ ```
251
+
252
+ #### Paragraph Factory Methods
253
+
254
+ | Method | Description | Example |
255
+ | --------------------------------------------------- | --------------------------- | --------------------------------------- |
256
+ | `Paragraph.create(text?, formatting?)` | Create detached paragraph | `Paragraph.create('Text')` |
257
+ | `Paragraph.create(formatting?)` | Create with formatting only | `Paragraph.create({alignment: 'left'})` |
258
+ | `Paragraph.createWithStyle(text, styleId)` | Create with style | `Paragraph.createWithStyle('', 'H1')` |
259
+ | `Paragraph.createEmpty()` | Create empty paragraph | `Paragraph.createEmpty()` |
260
+ | `Paragraph.createFormatted(text, run?, paragraph?)` | Create with dual formatting | See example above |
261
+
262
+ #### Paragraph Formatting Methods
263
+
264
+ | Method | Description | Values |
265
+ | ------------------------------ | ------------------- | ----------------------------------- |
266
+ | `setAlignment(align)` | Text alignment | `'left'/'center'/'right'/'justify'` |
267
+ | `setLeftIndent(twips)` | Left indentation | Twips value |
268
+ | `setRightIndent(twips)` | Right indentation | Twips value |
269
+ | `setFirstLineIndent(twips)` | First line indent | Twips value |
270
+ | `setSpaceBefore(twips)` | Space before | Twips value |
271
+ | `setSpaceAfter(twips)` | Space after | Twips value |
272
+ | `setLineSpacing(twips, rule?)` | Line spacing | Twips + rule |
273
+ | `setStyle(styleId)` | Apply style | Style ID |
274
+ | `setKeepNext()` | Keep with next | - |
275
+ | `setKeepLines()` | Keep lines together | - |
276
+ | `setPageBreakBefore()` | Page break before | - |
277
+
278
+ #### Paragraph Manipulation Methods
279
+
280
+ | Method | Description | Returns |
281
+ | -------------------------------------- | ----------------------- | ----------- |
282
+ | `insertRunAt(index, run)` | Insert run at position | `this` |
283
+ | `removeRunAt(index)` | Remove run at position | `boolean` |
284
+ | `replaceRunAt(index, run)` | Replace run at position | `boolean` |
285
+ | `findText(text, options?)` | Find text in runs | `number[]` |
286
+ | `replaceText(find, replace, options?)` | Replace text in runs | `number` |
287
+ | `mergeWith(otherPara)` | Merge another paragraph | `this` |
288
+ | `clone()` | Clone paragraph | `Paragraph` |
289
+
290
+ **Example:**
291
+
292
+ ```typescript
293
+ const para = doc.createParagraph("Hello World");
294
+
295
+ // Find and replace
296
+ const indices = para.findText("World"); // [1]
297
+ const count = para.replaceText("World", "Universe", { caseSensitive: true });
298
+
299
+ // Manipulate runs
300
+ para.insertRunAt(0, new Run("Start: ", { bold: true }));
301
+ para.replaceRunAt(1, new Run("HELLO", { allCaps: true }));
302
+
303
+ // Merge paragraphs
304
+ const para2 = Paragraph.create(" More text");
305
+ para.mergeWith(para2); // Combines runs
306
+ ```
307
+
308
+ ### Run (Text Span) Operations
309
+
310
+ | Method | Description | Returns |
311
+ | ------------------------------- | ------------------------- | ------- |
312
+ | `clone()` | Clone run with formatting | `Run` |
313
+ | `insertText(index, text)` | Insert text at position | `this` |
314
+ | `appendText(text)` | Append text to end | `this` |
315
+ | `replaceText(start, end, text)` | Replace text range | `this` |
316
+
317
+ **Example:**
318
+
319
+ ```typescript
320
+ const run = new Run("Hello World", { bold: true });
321
+
322
+ // Text manipulation
323
+ run.insertText(6, "Beautiful "); // "Hello Beautiful World"
324
+ run.appendText("!"); // "Hello Beautiful World!"
325
+ run.replaceText(0, 5, "Hi"); // "Hi Beautiful World!"
326
+
327
+ // Clone for reuse
328
+ const copy = run.clone();
329
+ copy.setColor("FF0000"); // Original unchanged
330
+ ```
331
+
332
+ ### Table Operations
333
+
334
+ | Method | Description | Example |
335
+ | ----------------------- | -------------------- | ---------------------------------------- |
336
+ | `getRow(index)` | Get table row | `table.getRow(0)` |
337
+ | `getCell(row, col)` | Get table cell | `table.getCell(0, 1)` |
338
+ | `addRow()` | Add new row | `table.addRow()` |
339
+ | `removeRow(index)` | Remove row | `table.removeRow(2)` |
340
+ | `insertColumn(index)` | Insert column | `table.insertColumn(1)` |
341
+ | `removeColumn(index)` | Remove column | `table.removeColumn(3)` |
342
+ | `setWidth(twips)` | Set table width | `table.setWidth(8640)` |
343
+ | `setAlignment(align)` | Table alignment | `table.setAlignment('center')` |
344
+ | `setAllBorders(border)` | Set all borders | `table.setAllBorders({style: 'single'})` |
345
+ | `setBorders(borders)` | Set specific borders | `table.setBorders({top: {...}})` |
346
+
347
+ #### Advanced Table Operations
348
+
349
+ | Method | Description | Returns |
350
+ | ------------------------------------------------ | ------------------------- | ------------ |
351
+ | `mergeCells(startRow, startCol, endRow, endCol)` | Merge cells | `this` |
352
+ | `splitCell(row, col)` | Remove cell spanning | `this` |
353
+ | `moveCell(fromRow, fromCol, toRow, toCol)` | Move cell contents | `this` |
354
+ | `swapCells(row1, col1, row2, col2)` | Swap two cells | `this` |
355
+ | `setColumnWidth(index, width)` | Set specific column width | `this` |
356
+ | `setColumnWidths(widths)` | Set all column widths | `this` |
357
+ | `insertRows(startIndex, count)` | Insert multiple rows | `TableRow[]` |
358
+ | `removeRows(startIndex, count)` | Remove multiple rows | `boolean` |
359
+ | `clone()` | Clone entire table | `Table` |
360
+
361
+ **Example:**
362
+
363
+ ```typescript
364
+ const table = doc.createTable(3, 3);
365
+
366
+ // Merge cells horizontally (row 0, columns 0-2)
367
+ table.mergeCells(0, 0, 0, 2);
368
+
369
+ // Move cell contents
370
+ table.moveCell(1, 1, 2, 2);
371
+
372
+ // Swap cells
373
+ table.swapCells(0, 0, 2, 2);
374
+
375
+ // Batch row operations
376
+ table.insertRows(1, 3); // Insert 3 rows at position 1
377
+ table.removeRows(4, 2); // Remove 2 rows starting at position 4
378
+
379
+ // Set column widths
380
+ table.setColumnWidth(0, 2000); // First column = 2000 twips
381
+ table.setColumnWidths([2000, 3000, 2000]); // All columns
382
+
383
+ // Clone table for reuse
384
+ const tableCopy = table.clone();
385
+ ```
386
+
387
+ ### Table Cell Operations
388
+
389
+ | Method | Description | Example |
390
+ | ----------------------------- | --------------------- | ------------------------------------- |
391
+ | `createParagraph(text?)` | Add paragraph to cell | `cell.createParagraph('Text')` |
392
+ | `setShading(shading)` | Cell background | `cell.setShading({fill: 'E0E0E0'})` |
393
+ | `setVerticalAlignment(align)` | Vertical align | `cell.setVerticalAlignment('center')` |
394
+ | `setColumnSpan(cols)` | Merge columns | `cell.setColumnSpan(3)` |
395
+ | `setRowSpan(rows)` | Merge rows | `cell.setRowSpan(2)` |
396
+ | `setBorders(borders)` | Cell borders | `cell.setBorders({top: {...}})` |
397
+ | `setWidth(width, type?)` | Cell width | `cell.setWidth(2000, 'dxa')` |
398
+
399
+ ### Style Management
400
+
401
+ | Method | Description | Example |
402
+ | ----------------------------- | ------------------ | ---------------------------------- |
403
+ | `addStyle(style)` | Add custom style | `doc.addStyle(myStyle)` |
404
+ | `getStyle(styleId)` | Get style by ID | `doc.getStyle('Heading1')` |
405
+ | `hasStyle(styleId)` | Check style exists | `doc.hasStyle('CustomStyle')` |
406
+ | `getStyles()` | Get all styles | `doc.getStyles()` |
407
+ | `removeStyle(styleId)` | Remove style | `doc.removeStyle('OldStyle')` |
408
+ | `updateStyle(styleId, props)` | Update style | `doc.updateStyle('Normal', {...})` |
409
+
410
+ #### Style Manipulation
411
+
412
+ | Method | Description | Returns |
413
+ | ------------------------ | ----------------------------------- | ------- |
414
+ | `style.clone()` | Clone style | `Style` |
415
+ | `style.mergeWith(other)` | Merge properties from another style | `this` |
416
+
417
+ **Example:**
418
+
419
+ ```typescript
420
+ // Clone a style
421
+ const heading1 = doc.getStyle("Heading1");
422
+ const customHeading = heading1.clone();
423
+ customHeading.setRunFormatting({ color: "FF0000" });
424
+
425
+ // Merge styles
426
+ const baseStyle = Style.createNormalStyle();
427
+ const overrideStyle = Style.create({
428
+ styleId: "Override",
429
+ name: "Override",
430
+ type: "paragraph",
431
+ runFormatting: { bold: true, color: "FF0000" },
432
+ });
433
+ baseStyle.mergeWith(overrideStyle); // baseStyle now has bold red text
434
+ ```
435
+
436
+ #### Built-in Styles
437
+
438
+ - `Normal` - Default paragraph
439
+ - `Title` - Document title
440
+ - `Subtitle` - Document subtitle
441
+ - `Heading1` through `Heading9` - Section headings
442
+ - `ListParagraph` - List items
443
+
444
+ ### List Management
445
+
446
+ | Method | Description | Returns |
447
+ | --------------------------------------- | ----------------------- | ------- |
448
+ | `createBulletList(levels?, bullets?)` | Create bullet list | `numId` |
449
+ | `createNumberedList(levels?, formats?)` | Create numbered list | `numId` |
450
+ | `createMultiLevelList()` | Create multi-level list | `numId` |
451
+
452
+ ### Table of Contents (TOC)
453
+
454
+ #### Basic TOC Creation
455
+
456
+ | Method | Description | Example |
457
+ | -------------------------- | ---------------------- | -------------------------- |
458
+ | `addTableOfContents(toc?)` | Add TOC to document | `doc.addTableOfContents()` |
459
+ | `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
460
+ | `removeTocAt(index)` | Remove TOC at position | `doc.removeTocAt(0)` |
461
+
462
+ #### TOC Factory Methods
463
+
464
+ | Method | Description | Example |
465
+ | -------------------------------------------------------- | ------------------------ | ---------------------------------------------------- |
466
+ | `TableOfContents.createStandard(title?)` | Standard TOC (3 levels) | `TableOfContents.createStandard()` |
467
+ | `TableOfContents.createSimple(title?)` | Simple TOC (2 levels) | `TableOfContents.createSimple()` |
468
+ | `TableOfContents.createDetailed(title?)` | Detailed TOC (4 levels) | `TableOfContents.createDetailed()` |
469
+ | `TableOfContents.createHyperlinked(title?)` | Hyperlinked TOC | `TableOfContents.createHyperlinked()` |
470
+ | `TableOfContents.createNoPageNumbers(opts?)` | TOC without page numbers | `TableOfContents.createNoPageNumbers()` |
471
+ | `TableOfContents.createWithStyles(styles, opts?)` | TOC with specific styles | `TableOfContents.createWithStyles(['H1','H3'])` |
472
+ | `TableOfContents.createFlat(title?, styles?)` | Flat TOC (no indent) | `TableOfContents.createFlat()` |
473
+ | `TableOfContents.createNumbered(title?, format?)` | Numbered TOC | `TableOfContents.createNumbered('TOC', 'roman')` |
474
+ | `TableOfContents.createWithSpacing(spacing, opts?)` | TOC with custom spacing | `TableOfContents.createWithSpacing(120)` |
475
+ | `TableOfContents.createWithHyperlinkColor(color, opts?)` | Custom hyperlink color | `TableOfContents.createWithHyperlinkColor('FF0000')` |
476
+
477
+ **Note:** All TOC elements are automatically wrapped in an SDT (Structured Document Tag) for native Word integration. This enables Word's "Update Table" button and provides better compatibility with Microsoft Word's TOC features.
478
+
479
+ #### TOC Configuration Methods
480
+
481
+ | Method | Description | Values |
482
+ | --------------------------------- | ------------------------------ | -------------------------- |
483
+ | `setIncludeStyles(styles)` | Select specific heading styles | `['Heading1', 'Heading3']` |
484
+ | `setNumbered(numbered, format?)` | Enable/disable numbering | `(true, 'roman')` |
485
+ | `setNoIndent(noIndent)` | Remove indentation | `true/false` |
486
+ | `setCustomIndents(indents)` | Custom indents per level | `[0, 200, 400]` (twips) |
487
+ | `setSpaceBetweenEntries(spacing)` | Spacing between entries | `120` (twips) |
488
+ | `setHyperlinkColor(color)` | Hyperlink color | `'0000FF'` (default blue) |
489
+ | `setHideInWebLayout(hide)` | Hide page numbers in web view | `true/false` |
490
+ | `configure(options)` | Bulk configuration | See example below |
491
+
492
+ #### TOC Properties
493
+
494
+ | Property | Type | Default | Description |
495
+ | --------------------- | ------------------------------------ | --------------------- | ---------------------------------- |
496
+ | `title` | `string` | `'Table of Contents'` | TOC title |
497
+ | `levels` | `number` (1-9) | `3` | Heading levels to include |
498
+ | `includeStyles` | `string[]` | `undefined` | Specific styles (overrides levels) |
499
+ | `showPageNumbers` | `boolean` | `true` | Show page numbers |
500
+ | `useHyperlinks` | `boolean` | `false` | Use hyperlinks instead of page #s |
501
+ | `hideInWebLayout` | `boolean` | `false` | Hide page numbers in web layout |
502
+ | `numbered` | `boolean` | `false` | Number TOC entries |
503
+ | `numberingFormat` | `'decimal'/'roman'/'alpha'` | `'decimal'` | Numbering format |
504
+ | `noIndent` | `boolean` | `false` | Remove all indentation |
505
+ | `customIndents` | `number[]` | `undefined` | Custom indents in twips |
506
+ | `spaceBetweenEntries` | `number` | `0` | Spacing in twips |
507
+ | `hyperlinkColor` | `string` | `'0000FF'` | Hyperlink color (hex without #) |
508
+ | `tabLeader` | `'dot'/'hyphen'/'underscore'/'none'` | `'dot'` | Tab leader character |
509
+
510
+ **Example:**
511
+
512
+ ```typescript
513
+ // Basic TOC
514
+ const simpleToc = TableOfContents.createStandard();
515
+ doc.addTableOfContents(simpleToc);
516
+
517
+ // Select specific styles (e.g., only Heading1 and Heading3)
518
+ const customToc = TableOfContents.createWithStyles(["Heading1", "Heading3"]);
519
+
520
+ // Flat TOC with no indentation
521
+ const flatToc = TableOfContents.createFlat("Contents");
522
+
523
+ // Numbered TOC with roman numerals
524
+ const numberedToc = TableOfContents.createNumbered(
525
+ "Table of Contents",
526
+ "roman"
527
+ );
528
+
529
+ // Custom hyperlink color (red instead of blue)
530
+ const coloredToc = TableOfContents.createWithHyperlinkColor("FF0000");
531
+
532
+ // Advanced configuration
533
+ const toc = TableOfContents.create()
534
+ .setIncludeStyles(["Heading1", "Heading2", "Heading3"])
535
+ .setNumbered(true, "decimal")
536
+ .setSpaceBetweenEntries(120) // 6pt spacing
537
+ .setHyperlinkColor("0000FF")
538
+ .setNoIndent(false);
539
+
540
+ // Or use configure() for bulk settings
541
+ toc.configure({
542
+ title: "Table of Contents",
543
+ includeStyles: ["Heading1", "CustomHeader"],
544
+ numbered: true,
545
+ numberingFormat: "alpha",
546
+ spaceBetweenEntries: 100,
547
+ hyperlinkColor: "FF0000",
548
+ noIndent: true,
549
+ });
550
+
551
+ // Insert at specific position
552
+ doc.insertTocAt(0, toc);
553
+ ```
554
+
555
+ ### Image Handling
556
+
557
+ | Method | Description | Example |
558
+ | --------------------------------------- | ------------------ | -------------------------------- |
559
+ | `Image.fromFile(path, width?, height?)` | Load from file | `Image.fromFile('pic.jpg')` |
560
+ | `Image.fromBuffer(buffer, ext, w?, h?)` | Load from buffer | `Image.fromBuffer(buf, 'png')` |
561
+ | `setWidth(emus, maintainRatio?)` | Set width | `img.setWidth(inchesToEmus(3))` |
562
+ | `setHeight(emus, maintainRatio?)` | Set height | `img.setHeight(inchesToEmus(2))` |
563
+ | `setSize(width, height)` | Set dimensions | `img.setSize(w, h)` |
564
+ | `setRotation(degrees)` | Rotate image | `img.setRotation(90)` |
565
+ | `setAltText(text)` | Accessibility text | `img.setAltText('Description')` |
566
+
567
+ ### Hyperlinks
568
+
569
+ | Method | Description | Example |
570
+ | ------------------------------------------------- | ---------------- | ---------------------------------------------------------- |
571
+ | `Hyperlink.createExternal(url, text, format?)` | Web link | `Hyperlink.createExternal('https://example.com', 'Click')` |
572
+ | `Hyperlink.createEmail(email, text?, format?)` | Email link | `Hyperlink.createEmail('user@example.com')` |
573
+ | `Hyperlink.createInternal(anchor, text, format?)` | Internal link | `Hyperlink.createInternal('Section1', 'Go to')` |
574
+ | `para.addHyperlink(hyperlink)` | Add to paragraph | `para.addHyperlink(link)` |
575
+
576
+ ### Headers & Footers
577
+
578
+ | Method | Description | Example |
579
+ | ---------------------------- | ------------------------ | -------------------------------- |
580
+ | `setHeader(header)` | Set default header | `doc.setHeader(myHeader)` |
581
+ | `setFooter(footer)` | Set default footer | `doc.setFooter(myFooter)` |
582
+ | `setFirstPageHeader(header)` | First page header | `doc.setFirstPageHeader(header)` |
583
+ | `setFirstPageFooter(footer)` | First page footer | `doc.setFirstPageFooter(footer)` |
584
+ | `setEvenPageHeader(header)` | Even page header | `doc.setEvenPageHeader(header)` |
585
+ | `setEvenPageFooter(footer)` | Even page footer | `doc.setEvenPageFooter(footer)` |
586
+ | `removeHeader(type)` | Remove specific header | `doc.removeHeader('default')` |
587
+ | `removeFooter(type)` | Remove specific footer | `doc.removeFooter('first')` |
588
+ | `clearHeaders()` | Remove all headers | `doc.clearHeaders()` |
589
+ | `clearFooters()` | Remove all footers | `doc.clearFooters()` |
590
+
591
+ ### Page Setup
592
+
593
+ | Method | Description | Example |
594
+ | ------------------------------------- | ----------------- | ------------------------------------- |
595
+ | `setPageSize(width, height, orient?)` | Page dimensions | `doc.setPageSize(12240, 15840)` |
596
+ | `setPageOrientation(orientation)` | Page orientation | `doc.setPageOrientation('landscape')` |
597
+ | `setMargins(margins)` | Page margins | `doc.setMargins({top: 1440, ...})` |
598
+ | `setLanguage(language)` | Document language | `doc.setLanguage('en-US')` |
599
+
600
+ ### Document Properties
601
+
602
+ | Method | Description | Properties |
603
+ | ---------------------- | ------------ | ------------------------------------- |
604
+ | `setProperties(props)` | Set metadata | `{title, subject, creator, keywords}` |
605
+ | `getProperties()` | Get metadata | Returns all properties |
606
+
607
+ ### Advanced Features
608
+
609
+ #### Bookmarks
610
+
611
+ | Method | Description |
612
+ | ---------------------------------------- | ------------------- |
613
+ | `createBookmark(name)` | Create bookmark |
614
+ | `createHeadingBookmark(text)` | Auto-named bookmark |
615
+ | `getBookmark(name)` | Get by name |
616
+ | `hasBookmark(name)` | Check existence |
617
+ | `addBookmarkToParagraph(para, bookmark)` | Add to paragraph |
618
+
619
+ #### Comments
620
+
621
+ | Method | Description |
622
+ | ------------------------------------------- | ----------------- |
623
+ | `createComment(author, content, initials?)` | Add comment |
624
+ | `createReply(parentId, author, content)` | Reply to comment |
625
+ | `getComment(id)` | Get by ID |
626
+ | `getAllComments()` | Get all top-level |
627
+ | `addCommentToParagraph(para, comment)` | Add to paragraph |
628
+
629
+ #### Track Changes
630
+
631
+ | Method | Description |
632
+ | ------------------------------------ | ----------------- |
633
+ | `trackInsertion(para, author, text)` | Track insertion |
634
+ | `trackDeletion(para, author, text)` | Track deletion |
635
+ | `isTrackingChanges()` | Check if tracking |
636
+ | `getRevisionStats()` | Get statistics |
637
+
638
+ #### Footnotes & Endnotes
639
+
640
+ | Method | Description |
641
+ | -------------------------- | ---------------- |
642
+ | `FootnoteManager.create()` | Manage footnotes |
643
+ | `EndnoteManager.create()` | Manage endnotes |
644
+
645
+ #### Document Helper Functions
646
+
647
+ High-level helper methods for common document formatting tasks:
648
+
649
+ | Method | Description |
650
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
651
+ | `applyCustomFormattingToExistingStyles()`| Modify Heading1, Heading2, Heading3, Normal, and List Paragraph styles with Verdana font, specific spacing, single line spacing, wrap Heading2 in tables, right-align "Top of Document" hyperlinks, set all hyperlinks to blue, and hide TOC page numbers |
652
+ | `wrapParagraphInTable(para, options?)` | Wrap a paragraph in a 1x1 table with optional shading, margins, and width settings |
653
+ | `isParagraphInTable(para)` | Check if a paragraph is inside a table; returns `{inTable: boolean, cell?: TableCell}` |
654
+ | `updateAllHyperlinkColors(color)` | Set all hyperlinks in the document to a specific color (e.g., '0000FF' for blue) |
655
+ | `removeAllHeadersFooters()` | Remove all headers and footers from the document; returns count of headers/footers removed |
656
+
657
+ **Example - Using Helper Functions:**
658
+
659
+ ```typescript
660
+ import { Document } from 'docxmlater';
661
+
662
+ const doc = await Document.load('document.docx');
663
+
664
+ // Apply comprehensive formatting to standard styles
665
+ const results = doc.applyCustomFormattingToExistingStyles();
666
+ console.log(`Modified styles:`, results);
667
+ // Output: { heading1: true, heading2: true, heading3: true, normal: true, listParagraph: true }
668
+
669
+ // Wrap a specific paragraph in a table
670
+ const para = doc.getParagraphs()[0];
671
+ doc.wrapParagraphInTable(para, {
672
+ shading: 'BFBFBF', // Gray background
673
+ marginLeft: 101, // 5pt margins
674
+ marginRight: 101,
675
+ tableWidthPercent: 5000 // 100% width
676
+ });
677
+
678
+ // Check if a paragraph is in a table
679
+ const { inTable, cell } = doc.isParagraphInTable(para);
680
+ if (inTable && cell) {
681
+ console.log('Paragraph is in a table cell');
682
+ cell.setShading({ fill: 'FFFF00' }); // Change to yellow
683
+ }
684
+
685
+ // Set all hyperlinks to blue
686
+ doc.updateAllHyperlinkColors('0000FF');
687
+
688
+ // Remove all headers and footers
689
+ const removedCount = doc.removeAllHeadersFooters();
690
+ console.log(`Removed ${removedCount} headers and footers`);
691
+
692
+ await doc.save('formatted.docx');
693
+ ```
694
+
695
+ **Note on `applyCustomFormattingToExistingStyles()`:**
696
+
697
+ This helper function applies a comprehensive set of formatting rules:
698
+ - **Heading1**: 18pt black bold Verdana, left aligned, 0pt before/12pt after, single line spacing
699
+ - **Heading2**: 14pt black bold Verdana, left aligned, 6pt before/after, single line spacing, wrapped in gray tables (100% width, 0.08" margins)
700
+ - **Heading3**: 12pt black bold Verdana, left aligned, 3pt before/after, single line spacing (no table wrapping)
701
+ - **Normal**: 12pt Verdana, left aligned, 3pt before/after, single line spacing
702
+ - **List Paragraph**: 12pt Verdana, left aligned, 0pt before/3pt after, single line spacing, 0.25" bullet indent/0.50" text indent, contextual spacing enabled
703
+ - **All Styles**: Removes italic and underline formatting
704
+ - **Heading2 Tables**: Existing tables are updated (cell shaded gray, margins set to 0.08" left/right); new tables created for paragraphs not in tables
705
+ - **Hyperlinks**: "Top of the Document" links are right-aligned with 0pt spacing; all hyperlinks set to blue (#0000FF)
706
+ - **Empty Paragraphs**: Empty Heading2 paragraphs are skipped (not wrapped in tables)
707
+ - **TOC Elements**: All Table of Contents have page numbers hidden (showPageNumbers=false, hideInWebLayout=true with \n and \z switches)
708
+
709
+ Per ECMA-376 §17.7.2, direct formatting in document.xml overrides style definitions. This method automatically clears conflicting direct formatting to ensure style changes take effect.
710
+
711
+ ### Low-Level Document Parts
712
+
713
+ | Method | Description | Example |
714
+ | ---------------------------- | --------------------- | ------------------------------------------------- |
715
+ | `getPart(partName)` | Get document part | `doc.getPart('word/document.xml')` |
716
+ | `setPart(partName, content)` | Set document part | `doc.setPart('custom.xml', data)` |
717
+ | `removePart(partName)` | Remove part | `doc.removePart('custom.xml')` |
718
+ | `listParts()` | List all parts | `const parts = await doc.listParts()` |
719
+ | `partExists(partName)` | Check part exists | `if (await doc.partExists('...'))` |
720
+ | `getContentTypes()` | Get content types | `const types = await doc.getContentTypes()` |
721
+ | `addContentType(part, type)` | Register content type | `doc.addContentType('.json', 'application/json')` |
722
+
723
+ ### Unit Conversion Utilities
724
+
725
+ #### Twips Conversions
726
+ | Function | Description | Example |
727
+ | ------------------------- | ------------------- | --------------------------- |
728
+ | `twipsToPoints(twips)` | Twips to points | `twipsToPoints(240)` // 12 |
729
+ | `twipsToInches(twips)` | Twips to inches | `twipsToInches(1440)` // 1 |
730
+ | `twipsToCm(twips)` | Twips to cm | `twipsToCm(1440)` // 2.54 |
731
+ | `twipsToEmus(twips)` | Twips to EMUs | `twipsToEmus(1440)` |
732
+
733
+ #### EMUs (English Metric Units) Conversions
734
+ | Function | Description | Example |
735
+ | --------------------------- | -------------------- | ----------------------------- |
736
+ | `emusToTwips(emus)` | EMUs to twips | `emusToTwips(914400)` // 1440 |
737
+ | `emusToInches(emus)` | EMUs to inches | `emusToInches(914400)` // 1 |
738
+ | `emusToCm(emus)` | EMUs to cm | `emusToCm(914400)` // 2.54 |
739
+ | `emusToPoints(emus)` | EMUs to points | `emusToPoints(914400)` // 72 |
740
+ | `emusToPixels(emus, dpi?)` | EMUs to pixels | `emusToPixels(914400)` // 96 |
741
+
742
+ #### Points Conversions
743
+ | Function | Description | Example |
744
+ | ------------------------ | ------------------ | -------------------------- |
745
+ | `pointsToTwips(points)` | Points to twips | `pointsToTwips(12)` // 240 |
746
+ | `pointsToEmus(points)` | Points to EMUs | `pointsToEmus(72)` |
747
+ | `pointsToInches(points)` | Points to inches | `pointsToInches(72)` // 1 |
748
+ | `pointsToCm(points)` | Points to cm | `pointsToCm(72)` // 2.54 |
749
+
750
+ #### Inches Conversions
751
+ | Function | Description | Example |
752
+ | ----------------------------- | ------------------- | ----------------------------- |
753
+ | `inchesToTwips(inches)` | Inches to twips | `inchesToTwips(1)` // 1440 |
754
+ | `inchesToEmus(inches)` | Inches to EMUs | `inchesToEmus(1)` // 914400 |
755
+ | `inchesToPoints(inches)` | Inches to points | `inchesToPoints(1)` // 72 |
756
+ | `inchesToCm(inches)` | Inches to cm | `inchesToCm(1)` // 2.54 |
757
+ | `inchesToPixels(inches, dpi)` | Inches to pixels | `inchesToPixels(1, 96)` // 96 |
758
+
759
+ #### Centimeters Conversions
760
+ | Function | Description | Example |
761
+ | ----------------------- | ---------------- | --------------------------- |
762
+ | `cmToTwips(cm)` | cm to twips | `cmToTwips(2.54)` // 1440 |
763
+ | `cmToEmus(cm)` | cm to EMUs | `cmToEmus(2.54)` // 914400 |
764
+ | `cmToInches(cm)` | cm to inches | `cmToInches(2.54)` // 1 |
765
+ | `cmToPoints(cm)` | cm to points | `cmToPoints(2.54)` // 72 |
766
+ | `cmToPixels(cm, dpi?)` | cm to pixels | `cmToPixels(2.54, 96)` // 96|
767
+
768
+ #### Pixels Conversions
769
+ | Function | Description | Example |
770
+ | ---------------------------- | ------------------- | ------------------------------ |
771
+ | `pixelsToEmus(pixels, dpi?)` | Pixels to EMUs | `pixelsToEmus(96)` // 914400 |
772
+ | `pixelsToInches(pixels, dpi?)`| Pixels to inches | `pixelsToInches(96, 96)` // 1 |
773
+ | `pixelsToTwips(pixels, dpi?)`| Pixels to twips | `pixelsToTwips(96, 96)` // 1440|
774
+ | `pixelsToCm(pixels, dpi?)` | Pixels to cm | `pixelsToCm(96, 96)` // 2.54 |
775
+ | `pixelsToPoints(pixels, dpi?)`| Pixels to points | `pixelsToPoints(96, 96)` // 72 |
776
+
777
+ **Note:** Default DPI is 96 for pixel conversions
778
+
779
+ ### ZIP Archive Helper Methods
780
+
781
+ #### File Operations
782
+ | Method | Description | Example |
783
+ | ------------------------------- | ------------------------- | -------------------------------------------- |
784
+ | `addFile(path, content)` | Add file to archive | `handler.addFile('doc.xml', xmlContent)` |
785
+ | `updateFile(path, content)` | Update existing file | `handler.updateFile('doc.xml', newContent)` |
786
+ | `removeFile(path)` | Remove file from archive | `handler.removeFile('old.xml')` |
787
+ | `renameFile(oldPath, newPath)` | Rename file | `handler.renameFile('a.xml', 'b.xml')` |
788
+ | `copyFile(srcPath, destPath)` | Copy file | `handler.copyFile('a.xml', 'copy-a.xml')` |
789
+ | `moveFile(srcPath, destPath)` | Move file | `handler.moveFile('a.xml', 'folder/a.xml')` |
790
+
791
+ #### File Retrieval
792
+ | Method | Description | Returns |
793
+ | ------------------------- | ---------------------- | --------------- |
794
+ | `getFile(path)` | Get file object | `ZipFile` |
795
+ | `getFileAsString(path)` | Get file as string | `string` |
796
+ | `getFileAsBuffer(path)` | Get file as buffer | `Buffer` |
797
+ | `hasFile(path)` | Check if file exists | `boolean` |
798
+ | `getFilePaths()` | Get all file paths | `string[]` |
799
+ | `getAllFiles()` | Get all files | `FileMap` |
800
+
801
+ #### Batch Operations
802
+ | Method | Description | Returns |
803
+ | ------------------------------- | ---------------------------- | -------------- |
804
+ | `removeFiles(paths[])` | Remove multiple files | `number` |
805
+ | `getFilesByExtension(ext)` | Get files by extension | `ZipFile[]` |
806
+ | `getTextFiles()` | Get all text files | `ZipFile[]` |
807
+ | `getBinaryFiles()` | Get all binary files | `ZipFile[]` |
808
+ | `getMediaFiles()` | Get media files | `ZipFile[]` |
809
+
810
+ #### Archive Information
811
+ | Method | Description | Returns |
812
+ | ------------------ | ------------------------- | ------------------------ |
813
+ | `getFileCount()` | Count files in archive | `number` |
814
+ | `getTotalSize()` | Get total size in bytes | `number` |
815
+ | `getStats()` | Get detailed statistics | `{fileCount, size, ...}` |
816
+ | `isEmpty()` | Check if archive is empty | `boolean` |
817
+
818
+ #### Import/Export
819
+ | Method | Description | Returns |
820
+ | -------------------------------- | ------------------------ | -------------------- |
821
+ | `exportFile(internal, external)` | Export file from archive | `Promise<void>` |
822
+ | `importFile(external, internal)` | Import file to archive | `Promise<void>` |
823
+
824
+ ## Common Recipes
825
+
826
+ ### Create a Simple Document
827
+
828
+ ```typescript
829
+ const doc = Document.create();
830
+ doc.createParagraph("Title").setStyle("Title");
831
+ doc.createParagraph("This is a simple document.");
832
+ await doc.save("simple.docx");
833
+ ```
834
+
835
+ ### Add Formatted Text
836
+
837
+ ```typescript
838
+ const para = doc.createParagraph();
839
+ para.addText("Bold", { bold: true });
840
+ para.addText(" and ");
841
+ para.addText("Colored", { color: "FF0000" });
842
+ ```
843
+
844
+ ### Create a Table with Borders
845
+
846
+ ```typescript
847
+ const table = doc.createTable(3, 3);
848
+ table.setAllBorders({ style: "single", size: 8, color: "000000" });
849
+ table.getCell(0, 0)?.createParagraph("Header 1");
850
+ table.getRow(0)?.getCell(0)?.setShading({ fill: "4472C4" });
851
+ ```
852
+
853
+ ### Insert an Image
854
+
855
+ ```typescript
856
+ import { Image, inchesToEmus } from "docxmlater";
857
+
858
+ const image = Image.fromFile("./photo.jpg");
859
+ image.setWidth(inchesToEmus(4), true); // 4 inches, maintain ratio
860
+ doc.addImage(image);
861
+ ```
862
+
863
+ ### Add a Hyperlink
864
+
865
+ ```typescript
866
+ const para = doc.createParagraph();
867
+ para.addText("Visit ");
868
+ para.addHyperlink(
869
+ Hyperlink.createExternal("https://example.com", "our website")
870
+ );
871
+ ```
872
+
873
+ ### Search and Replace Text
874
+
875
+ ```typescript
876
+ // Find all occurrences
877
+ const results = doc.findText("old text", { caseSensitive: true });
878
+ console.log(`Found ${results.length} occurrences`);
879
+
880
+ // Replace all
881
+ const count = doc.replaceText("old text", "new text", { wholeWord: true });
882
+ console.log(`Replaced ${count} occurrences`);
883
+ ```
884
+
885
+ ### Load and Modify Existing Document
886
+
887
+ ```typescript
888
+ const doc = await Document.load("existing.docx");
889
+ doc.createParagraph("Added paragraph");
890
+
891
+ // Update all hyperlinks
892
+ const urlMap = new Map([["https://old-site.com", "https://new-site.com"]]);
893
+ doc.updateHyperlinkUrls(urlMap);
894
+
895
+ await doc.save("modified.docx");
896
+ ```
897
+
898
+ ### Create Lists
899
+
900
+ ```typescript
901
+ // Bullet list
902
+ const bulletId = doc.createBulletList(3);
903
+ doc.createParagraph("First item").setNumbering(bulletId, 0);
904
+ doc.createParagraph("Second item").setNumbering(bulletId, 0);
905
+
906
+ // Numbered list
907
+ const numberId = doc.createNumberedList(3);
908
+ doc.createParagraph("Step 1").setNumbering(numberId, 0);
909
+ doc.createParagraph("Step 2").setNumbering(numberId, 0);
910
+ ```
911
+
912
+ ### Apply Custom Styles
913
+
914
+ ```typescript
915
+ import { Style } from "docxmlater";
916
+
917
+ const customStyle = Style.create({
918
+ styleId: "CustomHeading",
919
+ name: "Custom Heading",
920
+ basedOn: "Normal",
921
+ runFormatting: { bold: true, size: 14, color: "2E74B5" },
922
+ paragraphFormatting: { alignment: "center", spaceAfter: 240 },
923
+ });
924
+
925
+ doc.addStyle(customStyle);
926
+ doc.createParagraph("Custom Styled Text").setStyle("CustomHeading");
927
+ ```
928
+
929
+ ### Build Content with Detached Paragraphs
930
+
931
+ Create paragraphs independently and add them conditionally:
932
+
933
+ ```typescript
934
+ import { Paragraph } from "docxmlater";
935
+
936
+ // Create reusable paragraph templates
937
+ const warningTemplate = Paragraph.createFormatted(
938
+ "WARNING: ",
939
+ { bold: true, color: "FF6600" },
940
+ { spacing: { before: 120, after: 120 } }
941
+ );
942
+
943
+ // Clone and customize
944
+ const warning1 = warningTemplate.clone();
945
+ warning1.addText("Please read the documentation before proceeding.");
946
+
947
+ // Build content from data
948
+ const items = [
949
+ { title: "First Item", description: "Description here" },
950
+ { title: "Second Item", description: "Another description" },
951
+ ];
952
+
953
+ items.forEach((item, index) => {
954
+ const titlePara = Paragraph.create(`${index + 1}. `);
955
+ titlePara.addText(item.title, { bold: true });
956
+
957
+ const descPara = Paragraph.create(item.description, {
958
+ indentation: { left: 360 },
959
+ });
960
+
961
+ doc.addParagraph(titlePara);
962
+ doc.addParagraph(descPara);
963
+ });
964
+
965
+ // See examples/advanced/detached-paragraphs.ts for more patterns
966
+ ```
967
+
968
+ ### Add Headers and Footers
969
+
970
+ ```typescript
971
+ import { Header, Footer, Field } from "docxmlater";
972
+
973
+ // Header with page numbers
974
+ const header = Header.create();
975
+ header.addParagraph("Document Title").setAlignment("center");
976
+
977
+ // Footer with page numbers
978
+ const footer = Footer.create();
979
+ const footerPara = footer.addParagraph();
980
+ footerPara.addText("Page ");
981
+ footerPara.addField(Field.create({ type: "PAGE" }));
982
+ footerPara.addText(" of ");
983
+ footerPara.addField(Field.create({ type: "NUMPAGES" }));
984
+
985
+ doc.setHeader(header);
986
+ doc.setFooter(footer);
987
+ ```
988
+
989
+ ### Work with Document Statistics
990
+
991
+ ```typescript
992
+ // Get word and character counts
993
+ console.log("Words:", doc.getWordCount());
994
+ console.log("Characters:", doc.getCharacterCount());
995
+ console.log("Characters (no spaces):", doc.getCharacterCount(false));
996
+
997
+ // Check document size
998
+ const size = doc.estimateSize();
999
+ if (size.warning) {
1000
+ console.warn(size.warning);
1001
+ }
1002
+ console.log(`Estimated size: ${size.totalEstimatedMB} MB`);
1003
+ ```
1004
+
1005
+ ### Handle Large Documents Efficiently
1006
+
1007
+ ```typescript
1008
+ const doc = Document.create({
1009
+ maxMemoryUsagePercent: 80,
1010
+ maxRssMB: 2048,
1011
+ maxImageCount: 50,
1012
+ maxTotalImageSizeMB: 100,
1013
+ });
1014
+
1015
+ // Process document...
1016
+
1017
+ // Clean up resources after saving
1018
+ await doc.save("large-document.docx");
1019
+ doc.dispose(); // Free memory
1020
+ ```
1021
+
1022
+ ### Direct XML Access (Advanced)
1023
+
1024
+ ```typescript
1025
+ // Get raw XML
1026
+ const documentXml = await doc.getPart("word/document.xml");
1027
+ console.log(documentXml?.content);
1028
+
1029
+ // Modify raw XML (use with caution)
1030
+ await doc.setPart("word/custom.xml", "<custom>data</custom>");
1031
+ await doc.addContentType("/word/custom.xml", "application/xml");
1032
+
1033
+ // List all parts
1034
+ const parts = await doc.listParts();
1035
+ console.log("Document contains:", parts.length, "parts");
1036
+ ```
1037
+
1038
+ ## Features
1039
+
1040
+ - **Full OpenXML Compliance** - Follows ECMA-376 standard
1041
+ - **TypeScript First** - Complete type definitions
1042
+ - **Memory Efficient** - Handles large documents with streaming
1043
+ - **Atomic Saves** - Prevents corruption with temp file pattern
1044
+ - **Rich Formatting** - Complete text and paragraph formatting
1045
+ - **Tables** - Full support with borders, shading, merging
1046
+ - **Images** - PNG, JPEG, GIF with sizing and positioning
1047
+ - **Hyperlinks** - External, internal, and email links
1048
+ - **Styles** - 13 built-in styles + custom style creation
1049
+ - **Lists** - Bullets, numbering, multi-level
1050
+ - **Headers/Footers** - Different first/even/odd pages
1051
+ - **Search & Replace** - With case and whole word options
1052
+ - **Document Stats** - Word count, character count, size estimation
1053
+ - **Track Changes** - Insertions and deletions with authors
1054
+ - **Comments** - With replies and threading
1055
+ - **Bookmarks** - For internal navigation
1056
+ - **Low-level Access** - Direct ZIP and XML manipulation
1057
+
1058
+ ## Performance
1059
+
1060
+ - Process 100+ page documents efficiently
1061
+ - Atomic save pattern prevents corruption
1062
+ - Memory management for large files
1063
+ - Lazy loading of document parts
1064
+ - Resource cleanup with `dispose()`
1065
+
1066
+ ## Testing
1067
+
1068
+ ```bash
1069
+ npm test # Run all tests
1070
+ npm run test:watch # Watch mode
1071
+ npm run test:coverage # Coverage report
1072
+ ```
1073
+
1074
+ **Current:** 1,119 tests passing (97.3% pass rate) | 100% core functionality covered
1075
+
1076
+ ## Development
1077
+
1078
+ ```bash
1079
+ # Install dependencies
1080
+ npm install
1081
+
1082
+ # Build TypeScript
1083
+ npm run build
1084
+
1085
+ # Run examples
1086
+ npx ts-node examples/simple-document.ts
1087
+ ```
1088
+
1089
+ ## Project Structure
1090
+
1091
+ ```text
1092
+ src/
1093
+ ├── core/ # Document, Parser, Generator, Validator
1094
+ ├── elements/ # Paragraph, Run, Table, Image, Hyperlink
1095
+ ├── formatting/ # Style, NumberingManager
1096
+ ├── xml/ # XMLBuilder, XMLParser
1097
+ ├── zip/ # ZipHandler for DOCX manipulation
1098
+ └── utils/ # Validation, Units conversion
1099
+
1100
+ examples/
1101
+ ├── 01-basic/ # Simple document creation
1102
+ ├── 02-text/ # Text formatting examples
1103
+ ├── 03-tables/ # Table examples
1104
+ ├── 04-styles/ # Style examples
1105
+ ├── 05-images/ # Image handling
1106
+ ├── 06-complete/ # Full document examples
1107
+ └── 07-hyperlinks/ # Link examples
1108
+ ```
1109
+
1110
+ ## Hierarchy
1111
+
1112
+ ```text
1113
+ w:document (root)
1114
+ └── w:body (body container)
1115
+ ├── w:p (paragraph) [1..n]
1116
+ ├── w:pPr (paragraph properties) [0..1]
1117
+ │ ├── w:pStyle (style reference)
1118
+ │ ├── w:jc (justification/alignment)
1119
+ │ │ ├── w:ind (indentation)
1120
+ │ │ └── w:spacing (spacing before/after)
1121
+ │ ├── w:r (run) [1..n]
1122
+ │ │ ├── w:rPr (run properties) [0..1]
1123
+ │ │ │ ├── w:b (bold)
1124
+ │ │ │ ├── w:i (italic)
1125
+ │ │ │ ├── w:u (underline)
1126
+ │ │ │ ├── w:sz (font size)
1127
+ │ │ │ └── w:color (text color)
1128
+ │ │ └── w:t (text content) [1]
1129
+ │ ├── w:hyperlink (hyperlink) [0..n]
1130
+ │ │ └── w:r (run with hyperlink text)
1131
+ │ └── w:drawing (embedded image/shape) [0..n]
1132
+ ├── w:tbl (table) [1..n]
1133
+ │ ├── w:tblPr (table properties)
1134
+ │ └── w:tr (table row) [1..n]
1135
+ │ └── w:tc (table cell) [1..n]
1136
+ │ └── w:p (paragraph in cell)
1137
+ └── w:sectPr (section properties) [1] (must be last child of w:body)
1138
+ ```
1139
+
1140
+ ## Requirements
1141
+
1142
+ - Node.js 16+
1143
+ - TypeScript 5.0+ (for development)
1144
+
1145
+ ## Installation Options
1146
+
1147
+ ```bash
1148
+ # NPM
1149
+ npm install docxmlater
1150
+
1151
+ # Yarn
1152
+ yarn add docxmlater
1153
+
1154
+ # PNPM
1155
+ pnpm add docxmlater
1156
+ ```
1157
+
1158
+ ## Troubleshooting
1159
+
1160
+ ### XML Corruption in Text
1161
+
1162
+ **Problem**: Text displays with XML tags like `Important Information<w:t xml:space="preserve">1` in Word.
1163
+
1164
+ **Cause**: Passing XML-like strings to text methods instead of using the API properly.
1165
+
1166
+ ```typescript
1167
+ // WRONG - Will display escaped XML as literal text
1168
+ paragraph.addText("Important Information<w:t>1</w:t>");
1169
+ // Displays as: "Important Information<w:t>1</w:t>"
1170
+
1171
+ // CORRECT - Use separate text runs
1172
+ paragraph.addText("Important Information");
1173
+ paragraph.addText("1");
1174
+ // Displays as: "Important Information1"
1175
+
1176
+ // Or combine in one call
1177
+ paragraph.addText("Important Information 1");
1178
+ ```
1179
+
1180
+ **Detection**: Use the corruption detection utility to find issues:
1181
+
1182
+ ```typescript
1183
+ import { detectCorruptionInDocument } from "docxmlater";
1184
+
1185
+ const doc = await Document.load("file.docx");
1186
+ const report = detectCorruptionInDocument(doc);
1187
+
1188
+ if (report.isCorrupted) {
1189
+ console.log(report.summary);
1190
+ report.locations.forEach((loc) => {
1191
+ console.log(`Paragraph ${loc.paragraphIndex}, Run ${loc.runIndex}:`);
1192
+ console.log(` Original: ${loc.text}`);
1193
+ console.log(` Fixed: ${loc.suggestedFix}`);
1194
+ });
1195
+ }
1196
+ ```
1197
+
1198
+ **Auto-Cleaning**: XML patterns are automatically removed by default for defensive data handling:
1199
+
1200
+ ```typescript
1201
+ // Default behavior - auto-clean enabled
1202
+ const run = new Run("Text<w:t>value</w:t>");
1203
+ // Result: "Textvalue" (XML tags removed automatically)
1204
+
1205
+ // Disable auto-cleaning (for debugging)
1206
+ const run = new Run("Text<w:t>value</w:t>", { cleanXmlFromText: false });
1207
+ // Result: "Text<w:t>value</w:t>" (XML tags preserved, will display in Word)
1208
+ ```
1209
+
1210
+ **Why This Happens**: The framework correctly escapes XML special characters per the XML specification. When you pass XML tags as text, they are properly escaped (`<` becomes `&lt;`) and Word displays them as literal text, not as markup.
1211
+
1212
+ **The Right Approach**: Use the framework's API methods instead of embedding XML:
1213
+
1214
+ - Use `paragraph.addText()` multiple times for separate text runs
1215
+ - Use formatting options: `{bold: true}`, `{italic: true}`, etc.
1216
+ - Use `paragraph.addHyperlink()` for links
1217
+ - Don't pass XML strings to text methods
1218
+ - Don't try to embed `<w:t>` or other XML tags in your text
1219
+
1220
+ For more details, see the [corruption detection examples](examples/troubleshooting/).
1221
+
1222
+ ### Layout Conflicts (Massive Whitespace)
1223
+
1224
+ **Problem**: Documents show massive whitespace between paragraphs when opened in Word, even though the XML looks correct.
1225
+
1226
+ **Cause**: The `pageBreakBefore` property conflicting with `keepNext`/`keepLines` properties. When a paragraph has both `pageBreakBefore` and keep properties set to true, Word's layout engine tries to satisfy contradictory constraints (insert break vs. keep together), resulting in massive whitespace as it struggles to resolve the conflict.
1227
+
1228
+ **Why This Causes Problems**:
1229
+
1230
+ - `pageBreakBefore` tells Word to insert a page break before the paragraph
1231
+ - `keepNext` tells Word to keep the paragraph with the next one (no break)
1232
+ - `keepLines` tells Word to keep all lines together (no break)
1233
+ - The combination creates layout conflicts that manifest as massive whitespace
1234
+
1235
+ **Automatic Conflict Resolution** (v0.28.2+):
1236
+
1237
+ The framework now automatically prevents these conflicts by **prioritizing keep properties over page breaks**:
1238
+
1239
+ ```typescript
1240
+ // When setting keepNext or keepLines, pageBreakBefore is automatically cleared
1241
+ const para = new Paragraph()
1242
+ .addText("Content")
1243
+ .setPageBreakBefore(true) // Set to true
1244
+ .setKeepNext(true); // Automatically clears pageBreakBefore
1245
+
1246
+ // Result: keepNext=true, pageBreakBefore=false (conflict resolved)
1247
+ ```
1248
+
1249
+ **Why This Priority?**
1250
+
1251
+ - Keep properties (`keepNext`/`keepLines`) represent explicit user intent to keep content together
1252
+ - Page breaks are often layout hints that may conflict with document flow
1253
+ - Removing `pageBreakBefore` eliminates whitespace while preserving the user's intention
1254
+
1255
+ **Parsing Documents**:
1256
+
1257
+ When loading existing DOCX files with conflicts, they are automatically resolved:
1258
+
1259
+ ```typescript
1260
+ // Load document with conflicts
1261
+ const doc = await Document.load("document-with-conflicts.docx");
1262
+
1263
+ // Conflicts are automatically resolved during parsing
1264
+ // keepNext/keepLines take priority, pageBreakBefore is removed
1265
+ ```
1266
+
1267
+ **How It Works**:
1268
+
1269
+ 1. When `setKeepNext(true)` is called, `pageBreakBefore` is automatically set to `false`
1270
+ 2. When `setKeepLines(true)` is called, `pageBreakBefore` is automatically set to `false`
1271
+ 3. When parsing documents, if both properties exist, `pageBreakBefore` is cleared
1272
+ 4. Keep properties win because they represent explicit user intent
1273
+
1274
+ **Manual Override**:
1275
+
1276
+ If you need a page break despite keep properties, set it after:
1277
+
1278
+ ```typescript
1279
+ const para = new Paragraph()
1280
+ .setKeepNext(true) // Set first
1281
+ .setPageBreakBefore(true); // Override - you explicitly want this conflict
1282
+
1283
+ // But note: This will cause layout issues (whitespace) in Word
1284
+ ```
1285
+
1286
+ ## Known Limitations
1287
+
1288
+ While docXMLater provides comprehensive DOCX manipulation capabilities, there are some features that are not yet fully implemented:
1289
+
1290
+ ### 1. Table Row Spanning with vMerge
1291
+
1292
+ **Status:** FULLY IMPLEMENTED ✅
1293
+
1294
+ **What Works:**
1295
+ - Column spanning (horizontal cell merging) is fully supported
1296
+ - Row spanning (vertical cell merging) is now fully implemented
1297
+ - Both horizontal and vertical merging can be combined
1298
+ - Uses Word's proper `vMerge` attribute ('restart' and 'continue')
1299
+
1300
+ **Usage:**
1301
+ ```typescript
1302
+ // Merge cells horizontally (column spanning)
1303
+ table.mergeCells(0, 0, 0, 2); // Merge columns 0-2 in row 0
1304
+
1305
+ // Merge cells vertically (row spanning)
1306
+ table.mergeCells(0, 0, 2, 0); // Merge rows 0-2 in column 0
1307
+
1308
+ // Merge both horizontally and vertically (2x2 block)
1309
+ table.mergeCells(0, 0, 1, 1); // Merge 2x2 block starting at (0,0)
1310
+ ```
1311
+
1312
+ ### 2. Structured Document Tags (SDT) Parsing
1313
+
1314
+ **Status:** FULLY IMPLEMENTED
1315
+
1316
+ **What Works:**
1317
+ - Complete SDT parsing from existing documents
1318
+ - All 9 control types supported (richText, plainText, comboBox, dropDownList, datePicker, checkbox, picture, buildingBlock, group)
1319
+ - SDT properties fully extracted (id, tag, lock, alias, controlType)
1320
+ - Nested content parsing (paragraphs, tables, nested SDTs)
1321
+ - Preserves element order using XMLParser's `_orderedChildren` metadata
1322
+ - Round-trip operations fully supported
1323
+
1324
+ **Control Types Supported:**
1325
+ - **Rich Text** - Multi-formatted text content
1326
+ - **Plain Text** - Simple text with optional multiLine support
1327
+ - **Combo Box** - User-editable dropdown with list items
1328
+ - **Dropdown List** - Fixed selection from list items
1329
+ - **Date Picker** - Date selection with format and calendar type
1330
+ - **Checkbox** - Boolean selection with custom checked/unchecked states
1331
+ - **Picture** - Image content control
1332
+ - **Building Block** - Gallery and category-based content
1333
+ - **Group** - Grouping of other controls
1334
+
1335
+ **Usage:**
1336
+ ```typescript
1337
+ // Load documents with SDTs - fully parsed
1338
+ const doc = await Document.load('document-with-sdts.docx');
1339
+
1340
+ // Access parsed SDT content
1341
+ const sdts = doc.getBodyElements().filter(el => el instanceof StructuredDocumentTag);
1342
+ for (const sdt of sdts) {
1343
+ console.log('ID:', sdt.getId());
1344
+ console.log('Tag:', sdt.getTag());
1345
+ console.log('Type:', sdt.getControlType());
1346
+ console.log('Content:', sdt.getContent());
1347
+ }
1348
+
1349
+ // Create new SDTs programmatically
1350
+ const sdt = new StructuredDocumentTag({
1351
+ id: 123456,
1352
+ tag: 'MyControl',
1353
+ controlType: 'richText',
1354
+ alias: 'Rich Text Control'
1355
+ });
1356
+ sdt.addContent(paragraph);
1357
+ ```
1358
+
1359
+ All known limitations have been resolved! For feature requests or bug reports, please visit our [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues).
1360
+
1361
+ ## Contributing
1362
+
1363
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md).
1364
+
1365
+ 1. Fork the repository
1366
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1367
+ 3. Commit changes (`git commit -m 'Add amazing feature'`)
1368
+ 4. Push to branch (`git push origin feature/amazing-feature`)
1369
+ 5. Open a Pull Request
1370
+
1371
+ ## Recent Updates (v1.3.0)
1372
+
1373
+ **Enhanced Document Parsing & Helper Functions:**
1374
+
1375
+ - **TOC Parsing** - Parse Table of Contents from existing DOCX files
1376
+ - Extract TOC field instructions with all switches (\h, \u, \z, \n, \o, \t)
1377
+ - Detect SDT wrappers with `docPartGallery="Table of Contents"`
1378
+ - Create `TableOfContentsElement` objects from parsed TOCs
1379
+ - Support for modifying TOC field instructions in loaded documents
1380
+ - **removeAllHeadersFooters() Helper** - New document helper function
1381
+ - Removes all headers and footers from the document
1382
+ - Deletes header/footer XML files and relationships
1383
+ - Returns count of removed headers/footers
1384
+ - **Enhanced Test Suite** - 1,119/1,150 tests passing (97.3% pass rate)
1385
+ - **Documentation Updates** - Complete API reference for new helper functions
1386
+
1387
+ **Previous Enhancements (v1.2.0):**
1388
+ - 5 advanced document helper functions
1389
+ - Enhanced document modification capabilities
1390
+ - Improved paragraph and table wrapping utilities
1391
+
1392
+ ## License
1393
+
1394
+ MIT © DiaTech
1395
+
1396
+ ## Acknowledgments
1397
+
1398
+ - Built with [JSZip](https://stuk.github.io/jszip/) for ZIP handling
1399
+ - Follows [ECMA-376](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/) Office Open XML standard
1400
+ - Inspired by [python-docx](https://python-docx.readthedocs.io/) and [docx](https://github.com/dolanmiu/docx)
1401
+
1402
+ ## Support
1403
+
1404
+ - **Documentation**: [Full Docs](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs)
1405
+ - **Examples**: [Example Code](https://github.com/ItMeDiaTech/docXMLater/tree/main/examples)
1406
+ - **Issues**: [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues)
1407
+ - **Discussions**: [GitHub Discussions](https://github.com/ItMeDiaTech/docXMLater/discussions)
1408
+
1409
+ ## Quick Links
1410
+
1411
+ - [NPM Package](https://www.npmjs.com/package/docxmlater)
1412
+ - [GitHub Repository](https://github.com/ItMeDiaTech/docXMLater)
1413
+ - [API Reference](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs/api)
1414
+ - [Change Log](https://github.com/ItMeDiaTech/docXMLater/blob/main/CHANGELOG.md)
1415
+
1416
+ ---
1417
+
1418
+ **Ready to create amazing Word documents?** Start with our [examples](https://github.com/ItMeDiaTech/docXMLater/tree/main/examples) or dive into the [API Reference](#complete-api-reference) above!