docxmlater 1.4.3 → 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,1415 +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.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, 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 |
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, normal: 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)
700
- - **Normal**: 12pt Verdana, left aligned, 3pt before/after, single line spacing
701
- - **All Styles**: Removes italic and underline formatting
702
- - **Hyperlinks**: "Top of the Document" links are right-aligned with 0pt spacing; all hyperlinks set to blue (#0000FF)
703
- - **Empty Paragraphs**: Empty Heading2 paragraphs are skipped (not wrapped in tables)
704
- - **TOC Elements**: All Table of Contents have page numbers hidden (showPageNumbers=false, hideInWebLayout=true with \n and \z switches)
705
-
706
- 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.
707
-
708
- ### Low-Level Document Parts
709
-
710
- | Method | Description | Example |
711
- | ---------------------------- | --------------------- | ------------------------------------------------- |
712
- | `getPart(partName)` | Get document part | `doc.getPart('word/document.xml')` |
713
- | `setPart(partName, content)` | Set document part | `doc.setPart('custom.xml', data)` |
714
- | `removePart(partName)` | Remove part | `doc.removePart('custom.xml')` |
715
- | `listParts()` | List all parts | `const parts = await doc.listParts()` |
716
- | `partExists(partName)` | Check part exists | `if (await doc.partExists('...'))` |
717
- | `getContentTypes()` | Get content types | `const types = await doc.getContentTypes()` |
718
- | `addContentType(part, type)` | Register content type | `doc.addContentType('.json', 'application/json')` |
719
-
720
- ### Unit Conversion Utilities
721
-
722
- #### Twips Conversions
723
- | Function | Description | Example |
724
- | ------------------------- | ------------------- | --------------------------- |
725
- | `twipsToPoints(twips)` | Twips to points | `twipsToPoints(240)` // 12 |
726
- | `twipsToInches(twips)` | Twips to inches | `twipsToInches(1440)` // 1 |
727
- | `twipsToCm(twips)` | Twips to cm | `twipsToCm(1440)` // 2.54 |
728
- | `twipsToEmus(twips)` | Twips to EMUs | `twipsToEmus(1440)` |
729
-
730
- #### EMUs (English Metric Units) Conversions
731
- | Function | Description | Example |
732
- | --------------------------- | -------------------- | ----------------------------- |
733
- | `emusToTwips(emus)` | EMUs to twips | `emusToTwips(914400)` // 1440 |
734
- | `emusToInches(emus)` | EMUs to inches | `emusToInches(914400)` // 1 |
735
- | `emusToCm(emus)` | EMUs to cm | `emusToCm(914400)` // 2.54 |
736
- | `emusToPoints(emus)` | EMUs to points | `emusToPoints(914400)` // 72 |
737
- | `emusToPixels(emus, dpi?)` | EMUs to pixels | `emusToPixels(914400)` // 96 |
738
-
739
- #### Points Conversions
740
- | Function | Description | Example |
741
- | ------------------------ | ------------------ | -------------------------- |
742
- | `pointsToTwips(points)` | Points to twips | `pointsToTwips(12)` // 240 |
743
- | `pointsToEmus(points)` | Points to EMUs | `pointsToEmus(72)` |
744
- | `pointsToInches(points)` | Points to inches | `pointsToInches(72)` // 1 |
745
- | `pointsToCm(points)` | Points to cm | `pointsToCm(72)` // 2.54 |
746
-
747
- #### Inches Conversions
748
- | Function | Description | Example |
749
- | ----------------------------- | ------------------- | ----------------------------- |
750
- | `inchesToTwips(inches)` | Inches to twips | `inchesToTwips(1)` // 1440 |
751
- | `inchesToEmus(inches)` | Inches to EMUs | `inchesToEmus(1)` // 914400 |
752
- | `inchesToPoints(inches)` | Inches to points | `inchesToPoints(1)` // 72 |
753
- | `inchesToCm(inches)` | Inches to cm | `inchesToCm(1)` // 2.54 |
754
- | `inchesToPixels(inches, dpi)` | Inches to pixels | `inchesToPixels(1, 96)` // 96 |
755
-
756
- #### Centimeters Conversions
757
- | Function | Description | Example |
758
- | ----------------------- | ---------------- | --------------------------- |
759
- | `cmToTwips(cm)` | cm to twips | `cmToTwips(2.54)` // 1440 |
760
- | `cmToEmus(cm)` | cm to EMUs | `cmToEmus(2.54)` // 914400 |
761
- | `cmToInches(cm)` | cm to inches | `cmToInches(2.54)` // 1 |
762
- | `cmToPoints(cm)` | cm to points | `cmToPoints(2.54)` // 72 |
763
- | `cmToPixels(cm, dpi?)` | cm to pixels | `cmToPixels(2.54, 96)` // 96|
764
-
765
- #### Pixels Conversions
766
- | Function | Description | Example |
767
- | ---------------------------- | ------------------- | ------------------------------ |
768
- | `pixelsToEmus(pixels, dpi?)` | Pixels to EMUs | `pixelsToEmus(96)` // 914400 |
769
- | `pixelsToInches(pixels, dpi?)`| Pixels to inches | `pixelsToInches(96, 96)` // 1 |
770
- | `pixelsToTwips(pixels, dpi?)`| Pixels to twips | `pixelsToTwips(96, 96)` // 1440|
771
- | `pixelsToCm(pixels, dpi?)` | Pixels to cm | `pixelsToCm(96, 96)` // 2.54 |
772
- | `pixelsToPoints(pixels, dpi?)`| Pixels to points | `pixelsToPoints(96, 96)` // 72 |
773
-
774
- **Note:** Default DPI is 96 for pixel conversions
775
-
776
- ### ZIP Archive Helper Methods
777
-
778
- #### File Operations
779
- | Method | Description | Example |
780
- | ------------------------------- | ------------------------- | -------------------------------------------- |
781
- | `addFile(path, content)` | Add file to archive | `handler.addFile('doc.xml', xmlContent)` |
782
- | `updateFile(path, content)` | Update existing file | `handler.updateFile('doc.xml', newContent)` |
783
- | `removeFile(path)` | Remove file from archive | `handler.removeFile('old.xml')` |
784
- | `renameFile(oldPath, newPath)` | Rename file | `handler.renameFile('a.xml', 'b.xml')` |
785
- | `copyFile(srcPath, destPath)` | Copy file | `handler.copyFile('a.xml', 'copy-a.xml')` |
786
- | `moveFile(srcPath, destPath)` | Move file | `handler.moveFile('a.xml', 'folder/a.xml')` |
787
-
788
- #### File Retrieval
789
- | Method | Description | Returns |
790
- | ------------------------- | ---------------------- | --------------- |
791
- | `getFile(path)` | Get file object | `ZipFile` |
792
- | `getFileAsString(path)` | Get file as string | `string` |
793
- | `getFileAsBuffer(path)` | Get file as buffer | `Buffer` |
794
- | `hasFile(path)` | Check if file exists | `boolean` |
795
- | `getFilePaths()` | Get all file paths | `string[]` |
796
- | `getAllFiles()` | Get all files | `FileMap` |
797
-
798
- #### Batch Operations
799
- | Method | Description | Returns |
800
- | ------------------------------- | ---------------------------- | -------------- |
801
- | `removeFiles(paths[])` | Remove multiple files | `number` |
802
- | `getFilesByExtension(ext)` | Get files by extension | `ZipFile[]` |
803
- | `getTextFiles()` | Get all text files | `ZipFile[]` |
804
- | `getBinaryFiles()` | Get all binary files | `ZipFile[]` |
805
- | `getMediaFiles()` | Get media files | `ZipFile[]` |
806
-
807
- #### Archive Information
808
- | Method | Description | Returns |
809
- | ------------------ | ------------------------- | ------------------------ |
810
- | `getFileCount()` | Count files in archive | `number` |
811
- | `getTotalSize()` | Get total size in bytes | `number` |
812
- | `getStats()` | Get detailed statistics | `{fileCount, size, ...}` |
813
- | `isEmpty()` | Check if archive is empty | `boolean` |
814
-
815
- #### Import/Export
816
- | Method | Description | Returns |
817
- | -------------------------------- | ------------------------ | -------------------- |
818
- | `exportFile(internal, external)` | Export file from archive | `Promise<void>` |
819
- | `importFile(external, internal)` | Import file to archive | `Promise<void>` |
820
-
821
- ## Common Recipes
822
-
823
- ### Create a Simple Document
824
-
825
- ```typescript
826
- const doc = Document.create();
827
- doc.createParagraph("Title").setStyle("Title");
828
- doc.createParagraph("This is a simple document.");
829
- await doc.save("simple.docx");
830
- ```
831
-
832
- ### Add Formatted Text
833
-
834
- ```typescript
835
- const para = doc.createParagraph();
836
- para.addText("Bold", { bold: true });
837
- para.addText(" and ");
838
- para.addText("Colored", { color: "FF0000" });
839
- ```
840
-
841
- ### Create a Table with Borders
842
-
843
- ```typescript
844
- const table = doc.createTable(3, 3);
845
- table.setAllBorders({ style: "single", size: 8, color: "000000" });
846
- table.getCell(0, 0)?.createParagraph("Header 1");
847
- table.getRow(0)?.getCell(0)?.setShading({ fill: "4472C4" });
848
- ```
849
-
850
- ### Insert an Image
851
-
852
- ```typescript
853
- import { Image, inchesToEmus } from "docxmlater";
854
-
855
- const image = Image.fromFile("./photo.jpg");
856
- image.setWidth(inchesToEmus(4), true); // 4 inches, maintain ratio
857
- doc.addImage(image);
858
- ```
859
-
860
- ### Add a Hyperlink
861
-
862
- ```typescript
863
- const para = doc.createParagraph();
864
- para.addText("Visit ");
865
- para.addHyperlink(
866
- Hyperlink.createExternal("https://example.com", "our website")
867
- );
868
- ```
869
-
870
- ### Search and Replace Text
871
-
872
- ```typescript
873
- // Find all occurrences
874
- const results = doc.findText("old text", { caseSensitive: true });
875
- console.log(`Found ${results.length} occurrences`);
876
-
877
- // Replace all
878
- const count = doc.replaceText("old text", "new text", { wholeWord: true });
879
- console.log(`Replaced ${count} occurrences`);
880
- ```
881
-
882
- ### Load and Modify Existing Document
883
-
884
- ```typescript
885
- const doc = await Document.load("existing.docx");
886
- doc.createParagraph("Added paragraph");
887
-
888
- // Update all hyperlinks
889
- const urlMap = new Map([["https://old-site.com", "https://new-site.com"]]);
890
- doc.updateHyperlinkUrls(urlMap);
891
-
892
- await doc.save("modified.docx");
893
- ```
894
-
895
- ### Create Lists
896
-
897
- ```typescript
898
- // Bullet list
899
- const bulletId = doc.createBulletList(3);
900
- doc.createParagraph("First item").setNumbering(bulletId, 0);
901
- doc.createParagraph("Second item").setNumbering(bulletId, 0);
902
-
903
- // Numbered list
904
- const numberId = doc.createNumberedList(3);
905
- doc.createParagraph("Step 1").setNumbering(numberId, 0);
906
- doc.createParagraph("Step 2").setNumbering(numberId, 0);
907
- ```
908
-
909
- ### Apply Custom Styles
910
-
911
- ```typescript
912
- import { Style } from "docxmlater";
913
-
914
- const customStyle = Style.create({
915
- styleId: "CustomHeading",
916
- name: "Custom Heading",
917
- basedOn: "Normal",
918
- runFormatting: { bold: true, size: 14, color: "2E74B5" },
919
- paragraphFormatting: { alignment: "center", spaceAfter: 240 },
920
- });
921
-
922
- doc.addStyle(customStyle);
923
- doc.createParagraph("Custom Styled Text").setStyle("CustomHeading");
924
- ```
925
-
926
- ### Build Content with Detached Paragraphs
927
-
928
- Create paragraphs independently and add them conditionally:
929
-
930
- ```typescript
931
- import { Paragraph } from "docxmlater";
932
-
933
- // Create reusable paragraph templates
934
- const warningTemplate = Paragraph.createFormatted(
935
- "WARNING: ",
936
- { bold: true, color: "FF6600" },
937
- { spacing: { before: 120, after: 120 } }
938
- );
939
-
940
- // Clone and customize
941
- const warning1 = warningTemplate.clone();
942
- warning1.addText("Please read the documentation before proceeding.");
943
-
944
- // Build content from data
945
- const items = [
946
- { title: "First Item", description: "Description here" },
947
- { title: "Second Item", description: "Another description" },
948
- ];
949
-
950
- items.forEach((item, index) => {
951
- const titlePara = Paragraph.create(`${index + 1}. `);
952
- titlePara.addText(item.title, { bold: true });
953
-
954
- const descPara = Paragraph.create(item.description, {
955
- indentation: { left: 360 },
956
- });
957
-
958
- doc.addParagraph(titlePara);
959
- doc.addParagraph(descPara);
960
- });
961
-
962
- // See examples/advanced/detached-paragraphs.ts for more patterns
963
- ```
964
-
965
- ### Add Headers and Footers
966
-
967
- ```typescript
968
- import { Header, Footer, Field } from "docxmlater";
969
-
970
- // Header with page numbers
971
- const header = Header.create();
972
- header.addParagraph("Document Title").setAlignment("center");
973
-
974
- // Footer with page numbers
975
- const footer = Footer.create();
976
- const footerPara = footer.addParagraph();
977
- footerPara.addText("Page ");
978
- footerPara.addField(Field.create({ type: "PAGE" }));
979
- footerPara.addText(" of ");
980
- footerPara.addField(Field.create({ type: "NUMPAGES" }));
981
-
982
- doc.setHeader(header);
983
- doc.setFooter(footer);
984
- ```
985
-
986
- ### Work with Document Statistics
987
-
988
- ```typescript
989
- // Get word and character counts
990
- console.log("Words:", doc.getWordCount());
991
- console.log("Characters:", doc.getCharacterCount());
992
- console.log("Characters (no spaces):", doc.getCharacterCount(false));
993
-
994
- // Check document size
995
- const size = doc.estimateSize();
996
- if (size.warning) {
997
- console.warn(size.warning);
998
- }
999
- console.log(`Estimated size: ${size.totalEstimatedMB} MB`);
1000
- ```
1001
-
1002
- ### Handle Large Documents Efficiently
1003
-
1004
- ```typescript
1005
- const doc = Document.create({
1006
- maxMemoryUsagePercent: 80,
1007
- maxRssMB: 2048,
1008
- maxImageCount: 50,
1009
- maxTotalImageSizeMB: 100,
1010
- });
1011
-
1012
- // Process document...
1013
-
1014
- // Clean up resources after saving
1015
- await doc.save("large-document.docx");
1016
- doc.dispose(); // Free memory
1017
- ```
1018
-
1019
- ### Direct XML Access (Advanced)
1020
-
1021
- ```typescript
1022
- // Get raw XML
1023
- const documentXml = await doc.getPart("word/document.xml");
1024
- console.log(documentXml?.content);
1025
-
1026
- // Modify raw XML (use with caution)
1027
- await doc.setPart("word/custom.xml", "<custom>data</custom>");
1028
- await doc.addContentType("/word/custom.xml", "application/xml");
1029
-
1030
- // List all parts
1031
- const parts = await doc.listParts();
1032
- console.log("Document contains:", parts.length, "parts");
1033
- ```
1034
-
1035
- ## Features
1036
-
1037
- - **Full OpenXML Compliance** - Follows ECMA-376 standard
1038
- - **TypeScript First** - Complete type definitions
1039
- - **Memory Efficient** - Handles large documents with streaming
1040
- - **Atomic Saves** - Prevents corruption with temp file pattern
1041
- - **Rich Formatting** - Complete text and paragraph formatting
1042
- - **Tables** - Full support with borders, shading, merging
1043
- - **Images** - PNG, JPEG, GIF with sizing and positioning
1044
- - **Hyperlinks** - External, internal, and email links
1045
- - **Styles** - 13 built-in styles + custom style creation
1046
- - **Lists** - Bullets, numbering, multi-level
1047
- - **Headers/Footers** - Different first/even/odd pages
1048
- - **Search & Replace** - With case and whole word options
1049
- - **Document Stats** - Word count, character count, size estimation
1050
- - **Track Changes** - Insertions and deletions with authors
1051
- - **Comments** - With replies and threading
1052
- - **Bookmarks** - For internal navigation
1053
- - **Low-level Access** - Direct ZIP and XML manipulation
1054
-
1055
- ## Performance
1056
-
1057
- - Process 100+ page documents efficiently
1058
- - Atomic save pattern prevents corruption
1059
- - Memory management for large files
1060
- - Lazy loading of document parts
1061
- - Resource cleanup with `dispose()`
1062
-
1063
- ## Testing
1064
-
1065
- ```bash
1066
- npm test # Run all tests
1067
- npm run test:watch # Watch mode
1068
- npm run test:coverage # Coverage report
1069
- ```
1070
-
1071
- **Current:** 1,119 tests passing (97.3% pass rate) | 100% core functionality covered
1072
-
1073
- ## Development
1074
-
1075
- ```bash
1076
- # Install dependencies
1077
- npm install
1078
-
1079
- # Build TypeScript
1080
- npm run build
1081
-
1082
- # Run examples
1083
- npx ts-node examples/simple-document.ts
1084
- ```
1085
-
1086
- ## Project Structure
1087
-
1088
- ```text
1089
- src/
1090
- ├── core/ # Document, Parser, Generator, Validator
1091
- ├── elements/ # Paragraph, Run, Table, Image, Hyperlink
1092
- ├── formatting/ # Style, NumberingManager
1093
- ├── xml/ # XMLBuilder, XMLParser
1094
- ├── zip/ # ZipHandler for DOCX manipulation
1095
- └── utils/ # Validation, Units conversion
1096
-
1097
- examples/
1098
- ├── 01-basic/ # Simple document creation
1099
- ├── 02-text/ # Text formatting examples
1100
- ├── 03-tables/ # Table examples
1101
- ├── 04-styles/ # Style examples
1102
- ├── 05-images/ # Image handling
1103
- ├── 06-complete/ # Full document examples
1104
- └── 07-hyperlinks/ # Link examples
1105
- ```
1106
-
1107
- ## Hierarchy
1108
-
1109
- ```text
1110
- w:document (root)
1111
- └── w:body (body container)
1112
- ├── w:p (paragraph) [1..n]
1113
- │ ├── w:pPr (paragraph properties) [0..1]
1114
- │ │ ├── w:pStyle (style reference)
1115
- │ │ ├── w:jc (justification/alignment)
1116
- ├── w:ind (indentation)
1117
- │ │ └── w:spacing (spacing before/after)
1118
- │ ├── w:r (run) [1..n]
1119
- │ │ ├── w:rPr (run properties) [0..1]
1120
- │ │ │ ├── w:b (bold)
1121
- │ │ ├── w:i (italic)
1122
- │ │ ├── w:u (underline)
1123
- │ │ │ ├── w:sz (font size)
1124
- │ │ │ └── w:color (text color)
1125
- │ │ └── w:t (text content) [1]
1126
- │ ├── w:hyperlink (hyperlink) [0..n]
1127
- │ │ └── w:r (run with hyperlink text)
1128
- │ └── w:drawing (embedded image/shape) [0..n]
1129
- ├── w:tbl (table) [1..n]
1130
- ├── w:tblPr (table properties)
1131
- │ └── w:tr (table row) [1..n]
1132
- │ └── w:tc (table cell) [1..n]
1133
- └── w:p (paragraph in cell)
1134
- └── w:sectPr (section properties) [1] (must be last child of w:body)
1135
- ```
1136
-
1137
- ## Requirements
1138
-
1139
- - Node.js 16+
1140
- - TypeScript 5.0+ (for development)
1141
-
1142
- ## Installation Options
1143
-
1144
- ```bash
1145
- # NPM
1146
- npm install docxmlater
1147
-
1148
- # Yarn
1149
- yarn add docxmlater
1150
-
1151
- # PNPM
1152
- pnpm add docxmlater
1153
- ```
1154
-
1155
- ## Troubleshooting
1156
-
1157
- ### XML Corruption in Text
1158
-
1159
- **Problem**: Text displays with XML tags like `Important Information<w:t xml:space="preserve">1` in Word.
1160
-
1161
- **Cause**: Passing XML-like strings to text methods instead of using the API properly.
1162
-
1163
- ```typescript
1164
- // WRONG - Will display escaped XML as literal text
1165
- paragraph.addText("Important Information<w:t>1</w:t>");
1166
- // Displays as: "Important Information<w:t>1</w:t>"
1167
-
1168
- // CORRECT - Use separate text runs
1169
- paragraph.addText("Important Information");
1170
- paragraph.addText("1");
1171
- // Displays as: "Important Information1"
1172
-
1173
- // Or combine in one call
1174
- paragraph.addText("Important Information 1");
1175
- ```
1176
-
1177
- **Detection**: Use the corruption detection utility to find issues:
1178
-
1179
- ```typescript
1180
- import { detectCorruptionInDocument } from "docxmlater";
1181
-
1182
- const doc = await Document.load("file.docx");
1183
- const report = detectCorruptionInDocument(doc);
1184
-
1185
- if (report.isCorrupted) {
1186
- console.log(report.summary);
1187
- report.locations.forEach((loc) => {
1188
- console.log(`Paragraph ${loc.paragraphIndex}, Run ${loc.runIndex}:`);
1189
- console.log(` Original: ${loc.text}`);
1190
- console.log(` Fixed: ${loc.suggestedFix}`);
1191
- });
1192
- }
1193
- ```
1194
-
1195
- **Auto-Cleaning**: XML patterns are automatically removed by default for defensive data handling:
1196
-
1197
- ```typescript
1198
- // Default behavior - auto-clean enabled
1199
- const run = new Run("Text<w:t>value</w:t>");
1200
- // Result: "Textvalue" (XML tags removed automatically)
1201
-
1202
- // Disable auto-cleaning (for debugging)
1203
- const run = new Run("Text<w:t>value</w:t>", { cleanXmlFromText: false });
1204
- // Result: "Text<w:t>value</w:t>" (XML tags preserved, will display in Word)
1205
- ```
1206
-
1207
- **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.
1208
-
1209
- **The Right Approach**: Use the framework's API methods instead of embedding XML:
1210
-
1211
- - Use `paragraph.addText()` multiple times for separate text runs
1212
- - Use formatting options: `{bold: true}`, `{italic: true}`, etc.
1213
- - Use `paragraph.addHyperlink()` for links
1214
- - Don't pass XML strings to text methods
1215
- - Don't try to embed `<w:t>` or other XML tags in your text
1216
-
1217
- For more details, see the [corruption detection examples](examples/troubleshooting/).
1218
-
1219
- ### Layout Conflicts (Massive Whitespace)
1220
-
1221
- **Problem**: Documents show massive whitespace between paragraphs when opened in Word, even though the XML looks correct.
1222
-
1223
- **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.
1224
-
1225
- **Why This Causes Problems**:
1226
-
1227
- - `pageBreakBefore` tells Word to insert a page break before the paragraph
1228
- - `keepNext` tells Word to keep the paragraph with the next one (no break)
1229
- - `keepLines` tells Word to keep all lines together (no break)
1230
- - The combination creates layout conflicts that manifest as massive whitespace
1231
-
1232
- **Automatic Conflict Resolution** (v0.28.2+):
1233
-
1234
- The framework now automatically prevents these conflicts by **prioritizing keep properties over page breaks**:
1235
-
1236
- ```typescript
1237
- // When setting keepNext or keepLines, pageBreakBefore is automatically cleared
1238
- const para = new Paragraph()
1239
- .addText("Content")
1240
- .setPageBreakBefore(true) // Set to true
1241
- .setKeepNext(true); // Automatically clears pageBreakBefore
1242
-
1243
- // Result: keepNext=true, pageBreakBefore=false (conflict resolved)
1244
- ```
1245
-
1246
- **Why This Priority?**
1247
-
1248
- - Keep properties (`keepNext`/`keepLines`) represent explicit user intent to keep content together
1249
- - Page breaks are often layout hints that may conflict with document flow
1250
- - Removing `pageBreakBefore` eliminates whitespace while preserving the user's intention
1251
-
1252
- **Parsing Documents**:
1253
-
1254
- When loading existing DOCX files with conflicts, they are automatically resolved:
1255
-
1256
- ```typescript
1257
- // Load document with conflicts
1258
- const doc = await Document.load("document-with-conflicts.docx");
1259
-
1260
- // Conflicts are automatically resolved during parsing
1261
- // keepNext/keepLines take priority, pageBreakBefore is removed
1262
- ```
1263
-
1264
- **How It Works**:
1265
-
1266
- 1. When `setKeepNext(true)` is called, `pageBreakBefore` is automatically set to `false`
1267
- 2. When `setKeepLines(true)` is called, `pageBreakBefore` is automatically set to `false`
1268
- 3. When parsing documents, if both properties exist, `pageBreakBefore` is cleared
1269
- 4. Keep properties win because they represent explicit user intent
1270
-
1271
- **Manual Override**:
1272
-
1273
- If you need a page break despite keep properties, set it after:
1274
-
1275
- ```typescript
1276
- const para = new Paragraph()
1277
- .setKeepNext(true) // Set first
1278
- .setPageBreakBefore(true); // Override - you explicitly want this conflict
1279
-
1280
- // But note: This will cause layout issues (whitespace) in Word
1281
- ```
1282
-
1283
- ## Known Limitations
1284
-
1285
- While docXMLater provides comprehensive DOCX manipulation capabilities, there are some features that are not yet fully implemented:
1286
-
1287
- ### 1. Table Row Spanning with vMerge
1288
-
1289
- **Status:** FULLY IMPLEMENTED ✅
1290
-
1291
- **What Works:**
1292
- - Column spanning (horizontal cell merging) is fully supported
1293
- - Row spanning (vertical cell merging) is now fully implemented
1294
- - Both horizontal and vertical merging can be combined
1295
- - Uses Word's proper `vMerge` attribute ('restart' and 'continue')
1296
-
1297
- **Usage:**
1298
- ```typescript
1299
- // Merge cells horizontally (column spanning)
1300
- table.mergeCells(0, 0, 0, 2); // Merge columns 0-2 in row 0
1301
-
1302
- // Merge cells vertically (row spanning)
1303
- table.mergeCells(0, 0, 2, 0); // Merge rows 0-2 in column 0
1304
-
1305
- // Merge both horizontally and vertically (2x2 block)
1306
- table.mergeCells(0, 0, 1, 1); // Merge 2x2 block starting at (0,0)
1307
- ```
1308
-
1309
- ### 2. Structured Document Tags (SDT) Parsing
1310
-
1311
- **Status:** FULLY IMPLEMENTED ✅
1312
-
1313
- **What Works:**
1314
- - Complete SDT parsing from existing documents
1315
- - All 9 control types supported (richText, plainText, comboBox, dropDownList, datePicker, checkbox, picture, buildingBlock, group)
1316
- - SDT properties fully extracted (id, tag, lock, alias, controlType)
1317
- - Nested content parsing (paragraphs, tables, nested SDTs)
1318
- - Preserves element order using XMLParser's `_orderedChildren` metadata
1319
- - Round-trip operations fully supported
1320
-
1321
- **Control Types Supported:**
1322
- - **Rich Text** - Multi-formatted text content
1323
- - **Plain Text** - Simple text with optional multiLine support
1324
- - **Combo Box** - User-editable dropdown with list items
1325
- - **Dropdown List** - Fixed selection from list items
1326
- - **Date Picker** - Date selection with format and calendar type
1327
- - **Checkbox** - Boolean selection with custom checked/unchecked states
1328
- - **Picture** - Image content control
1329
- - **Building Block** - Gallery and category-based content
1330
- - **Group** - Grouping of other controls
1331
-
1332
- **Usage:**
1333
- ```typescript
1334
- // Load documents with SDTs - fully parsed
1335
- const doc = await Document.load('document-with-sdts.docx');
1336
-
1337
- // Access parsed SDT content
1338
- const sdts = doc.getBodyElements().filter(el => el instanceof StructuredDocumentTag);
1339
- for (const sdt of sdts) {
1340
- console.log('ID:', sdt.getId());
1341
- console.log('Tag:', sdt.getTag());
1342
- console.log('Type:', sdt.getControlType());
1343
- console.log('Content:', sdt.getContent());
1344
- }
1345
-
1346
- // Create new SDTs programmatically
1347
- const sdt = new StructuredDocumentTag({
1348
- id: 123456,
1349
- tag: 'MyControl',
1350
- controlType: 'richText',
1351
- alias: 'Rich Text Control'
1352
- });
1353
- sdt.addContent(paragraph);
1354
- ```
1355
-
1356
- All known limitations have been resolved! For feature requests or bug reports, please visit our [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues).
1357
-
1358
- ## Contributing
1359
-
1360
- Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md).
1361
-
1362
- 1. Fork the repository
1363
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1364
- 3. Commit changes (`git commit -m 'Add amazing feature'`)
1365
- 4. Push to branch (`git push origin feature/amazing-feature`)
1366
- 5. Open a Pull Request
1367
-
1368
- ## Recent Updates (v1.3.0)
1369
-
1370
- **Enhanced Document Parsing & Helper Functions:**
1371
-
1372
- - **TOC Parsing** - Parse Table of Contents from existing DOCX files
1373
- - Extract TOC field instructions with all switches (\h, \u, \z, \n, \o, \t)
1374
- - Detect SDT wrappers with `docPartGallery="Table of Contents"`
1375
- - Create `TableOfContentsElement` objects from parsed TOCs
1376
- - Support for modifying TOC field instructions in loaded documents
1377
- - **removeAllHeadersFooters() Helper** - New document helper function
1378
- - Removes all headers and footers from the document
1379
- - Deletes header/footer XML files and relationships
1380
- - Returns count of removed headers/footers
1381
- - **Enhanced Test Suite** - 1,119/1,150 tests passing (97.3% pass rate)
1382
- - **Documentation Updates** - Complete API reference for new helper functions
1383
-
1384
- **Previous Enhancements (v1.2.0):**
1385
- - 5 advanced document helper functions
1386
- - Enhanced document modification capabilities
1387
- - Improved paragraph and table wrapping utilities
1388
-
1389
- ## License
1390
-
1391
- MIT © DiaTech
1392
-
1393
- ## Acknowledgments
1394
-
1395
- - Built with [JSZip](https://stuk.github.io/jszip/) for ZIP handling
1396
- - Follows [ECMA-376](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/) Office Open XML standard
1397
- - Inspired by [python-docx](https://python-docx.readthedocs.io/) and [docx](https://github.com/dolanmiu/docx)
1398
-
1399
- ## Support
1400
-
1401
- - **Documentation**: [Full Docs](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs)
1402
- - **Examples**: [Example Code](https://github.com/ItMeDiaTech/docXMLater/tree/main/examples)
1403
- - **Issues**: [GitHub Issues](https://github.com/ItMeDiaTech/docXMLater/issues)
1404
- - **Discussions**: [GitHub Discussions](https://github.com/ItMeDiaTech/docXMLater/discussions)
1405
-
1406
- ## Quick Links
1407
-
1408
- - [NPM Package](https://www.npmjs.com/package/docxmlater)
1409
- - [GitHub Repository](https://github.com/ItMeDiaTech/docXMLater)
1410
- - [API Reference](https://github.com/ItMeDiaTech/docXMLater/tree/main/docs/api)
1411
- - [Change Log](https://github.com/ItMeDiaTech/docXMLater/blob/main/CHANGELOG.md)
1412
-
1413
- ---
1414
-
1415
- **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!