docxmlater 0.31.0 → 1.0.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 +277 -193
- package/dist/core/Document.d.ts +26 -0
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +86 -2
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentGenerator.d.ts +3 -2
- package/dist/core/DocumentGenerator.d.ts.map +1 -1
- package/dist/core/DocumentGenerator.js +52 -7
- package/dist/core/DocumentGenerator.js.map +1 -1
- package/dist/core/DocumentParser.d.ts +18 -0
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +1311 -26
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/elements/Field.d.ts +7 -1
- package/dist/elements/Field.d.ts.map +1 -1
- package/dist/elements/Field.js +74 -0
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/Image.d.ts +94 -0
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +311 -25
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/Paragraph.d.ts +109 -31
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +274 -0
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/Run.d.ts +68 -0
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +243 -39
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts +16 -0
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +61 -1
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.d.ts +86 -0
- package/dist/elements/Shape.d.ts.map +1 -0
- package/dist/elements/Shape.js +461 -0
- package/dist/elements/Shape.js.map +1 -0
- package/dist/elements/StructuredDocumentTag.d.ts +61 -0
- package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
- package/dist/elements/StructuredDocumentTag.js +235 -0
- package/dist/elements/StructuredDocumentTag.js.map +1 -1
- package/dist/elements/Table.d.ts +33 -0
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +82 -8
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +17 -1
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +61 -8
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableRow.d.ts +29 -0
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +106 -0
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/elements/TextBox.d.ts +77 -0
- package/dist/elements/TextBox.d.ts.map +1 -0
- package/dist/elements/TextBox.js +440 -0
- package/dist/elements/TextBox.js.map +1 -0
- package/dist/formatting/Style.d.ts +100 -0
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +396 -1
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +6 -0
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +50 -0
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/managers/DrawingManager.d.ts +42 -0
- package/dist/managers/DrawingManager.d.ts.map +1 -0
- package/dist/managers/DrawingManager.js +160 -0
- package/dist/managers/DrawingManager.js.map +1 -0
- package/dist/xml/XMLBuilder.d.ts +2 -0
- package/dist/xml/XMLBuilder.d.ts.map +1 -1
- package/dist/xml/XMLBuilder.js +14 -0
- package/dist/xml/XMLBuilder.js.map +1 -1
- package/dist/xml/XMLParser.d.ts.map +1 -1
- package/dist/xml/XMLParser.js +7 -1
- package/dist/xml/XMLParser.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,51 +1,29 @@
|
|
|
1
1
|
# docXMLater - Professional DOCX Framework
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/docxmlater)
|
|
4
|
-
[](https://github.com/ItMeDiaTech/docXMLater)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
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 **100% test pass rate**.
|
|
9
9
|
|
|
10
|
-
|
|
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
11
|
|
|
12
|
-
##
|
|
12
|
+
## Latest Updates - v1.0.0
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**Production Release!** All major features complete:
|
|
15
15
|
|
|
16
|
-
### New
|
|
17
|
-
- **ComplexField Support:** Full implementation of begin/separate/end field structure per ECMA-376
|
|
18
|
-
- **TOC Field Generator:** `createTOCField()` with all switches (\o, \h, \z, \u, \n, \t)
|
|
19
|
-
- **Advanced Fields:** Foundation for cross-references, indexes, and dynamic content
|
|
20
|
-
- **Style Color Fix:** Fixed critical bug where hex colors were corrupted during load/save
|
|
16
|
+
### What's New in v1.0.0
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
18
|
+
- **Complete Feature Set:** All 102 major features implemented
|
|
19
|
+
- **Table Styles:** Full support with 12 conditional formatting types
|
|
20
|
+
- **Content Controls:** 9 control types (rich text, plain text, combo box, dropdown, date picker, checkbox, picture, building block, group)
|
|
21
|
+
- **Field Types:** 11 field types (PAGE, NUMPAGES, DATE, TIME, FILENAME, AUTHOR, TITLE, REF, HYPERLINK, SEQ, TC/XE)
|
|
22
|
+
- **Drawing Elements:** Shapes and textboxes with full positioning
|
|
23
|
+
- **Document Properties:** Core, extended, and custom properties
|
|
24
|
+
- **Production Ready:** Full ECMA-376 compliance, zero regressions
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { createTOCField, ComplexField } from 'docxmlater';
|
|
32
|
-
|
|
33
|
-
// Create TOC with custom options
|
|
34
|
-
const toc = createTOCField({
|
|
35
|
-
levels: '1-3',
|
|
36
|
-
hyperlinks: true,
|
|
37
|
-
omitPageNumbers: false
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Create custom complex field
|
|
41
|
-
const field = new ComplexField({
|
|
42
|
-
instruction: ' PAGE \\* MERGEFORMAT ',
|
|
43
|
-
result: '1',
|
|
44
|
-
resultFormatting: { bold: true }
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Test Results:** 635/635 tests passing (100% - up from 596)
|
|
26
|
+
**Test Results:** 1,098/1,098 tests passing (100% - exceeding v1.0 goal by 29%)
|
|
49
27
|
|
|
50
28
|
## Quick Start
|
|
51
29
|
|
|
@@ -80,27 +58,27 @@ await doc.save("output.docx");
|
|
|
80
58
|
|
|
81
59
|
### Content Creation
|
|
82
60
|
|
|
83
|
-
| Method | Description
|
|
84
|
-
| -------------------------------- |
|
|
85
|
-
| `createParagraph(text?)` | Add paragraph
|
|
86
|
-
| `createTable(rows, cols)` | Add table
|
|
87
|
-
| `addParagraph(para)` | Add existing paragraph
|
|
88
|
-
| `addTable(table)` | Add existing table
|
|
89
|
-
| `addImage(image)` | Add image
|
|
90
|
-
| `addTableOfContents(toc?)` | Add TOC
|
|
91
|
-
| `insertParagraphAt(index, para)` | Insert at position
|
|
61
|
+
| Method | Description | Example |
|
|
62
|
+
| -------------------------------- | ------------------------ | -------------------------------- |
|
|
63
|
+
| `createParagraph(text?)` | Add paragraph | `doc.createParagraph('Text')` |
|
|
64
|
+
| `createTable(rows, cols)` | Add table | `doc.createTable(3, 4)` |
|
|
65
|
+
| `addParagraph(para)` | Add existing paragraph | `doc.addParagraph(myPara)` |
|
|
66
|
+
| `addTable(table)` | Add existing table | `doc.addTable(myTable)` |
|
|
67
|
+
| `addImage(image)` | Add image | `doc.addImage(myImage)` |
|
|
68
|
+
| `addTableOfContents(toc?)` | Add TOC | `doc.addTableOfContents()` |
|
|
69
|
+
| `insertParagraphAt(index, para)` | Insert at position | `doc.insertParagraphAt(0, para)` |
|
|
92
70
|
| `insertTableAt(index, table)` | Insert table at position | `doc.insertTableAt(5, table)` |
|
|
93
|
-
| `insertTocAt(index, toc)` | Insert TOC at position
|
|
71
|
+
| `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
|
|
94
72
|
|
|
95
73
|
### Content Manipulation
|
|
96
74
|
|
|
97
|
-
| Method
|
|
98
|
-
|
|
|
99
|
-
| `replaceParagraphAt(index, para)`
|
|
100
|
-
| `replaceTableAt(index, table)`
|
|
101
|
-
| `moveElement(fromIndex, toIndex)`
|
|
102
|
-
| `swapElements(index1, index2)`
|
|
103
|
-
| `removeTocAt(index)`
|
|
75
|
+
| Method | Description | Returns |
|
|
76
|
+
| --------------------------------- | ---------------------------- | --------- |
|
|
77
|
+
| `replaceParagraphAt(index, para)` | Replace paragraph | `boolean` |
|
|
78
|
+
| `replaceTableAt(index, table)` | Replace table | `boolean` |
|
|
79
|
+
| `moveElement(fromIndex, toIndex)` | Move element to new position | `boolean` |
|
|
80
|
+
| `swapElements(index1, index2)` | Swap two elements | `boolean` |
|
|
81
|
+
| `removeTocAt(index)` | Remove TOC element | `boolean` |
|
|
104
82
|
|
|
105
83
|
### Content Retrieval
|
|
106
84
|
|
|
@@ -133,20 +111,21 @@ await doc.save("output.docx");
|
|
|
133
111
|
|
|
134
112
|
### Style Application
|
|
135
113
|
|
|
136
|
-
| Method
|
|
137
|
-
|
|
|
138
|
-
| `applyStyleToAll(styleId, predicate)`
|
|
139
|
-
| `findElementsByStyle(styleId)`
|
|
114
|
+
| Method | Description | Returns |
|
|
115
|
+
| ------------------------------------- | -------------------------------- | ------------------- |
|
|
116
|
+
| `applyStyleToAll(styleId, predicate)` | Apply style to matching elements | `number` |
|
|
117
|
+
| `findElementsByStyle(styleId)` | Find all elements using a style | `Array<Para\|Cell>` |
|
|
140
118
|
|
|
141
119
|
**Example:**
|
|
120
|
+
|
|
142
121
|
```typescript
|
|
143
122
|
// Apply Heading1 to all paragraphs containing "Chapter"
|
|
144
|
-
const count = doc.applyStyleToAll(
|
|
145
|
-
return el instanceof Paragraph && el.getText().includes(
|
|
123
|
+
const count = doc.applyStyleToAll("Heading1", (el) => {
|
|
124
|
+
return el instanceof Paragraph && el.getText().includes("Chapter");
|
|
146
125
|
});
|
|
147
126
|
|
|
148
127
|
// Find all Heading1 elements
|
|
149
|
-
const headings = doc.findElementsByStyle(
|
|
128
|
+
const headings = doc.findElementsByStyle("Heading1");
|
|
150
129
|
```
|
|
151
130
|
|
|
152
131
|
### Document Statistics
|
|
@@ -192,7 +171,10 @@ const para2 = Paragraph.create("Hello World");
|
|
|
192
171
|
const para3 = Paragraph.create("Centered text", { alignment: "center" });
|
|
193
172
|
|
|
194
173
|
// Create with just formatting
|
|
195
|
-
const para4 = Paragraph.create({
|
|
174
|
+
const para4 = Paragraph.create({
|
|
175
|
+
alignment: "right",
|
|
176
|
+
spacing: { before: 240 },
|
|
177
|
+
});
|
|
196
178
|
|
|
197
179
|
// Create with style
|
|
198
180
|
const heading = Paragraph.createWithStyle("Chapter 1", "Heading1");
|
|
@@ -211,13 +193,13 @@ doc.addParagraph(heading);
|
|
|
211
193
|
|
|
212
194
|
#### Paragraph Factory Methods
|
|
213
195
|
|
|
214
|
-
| Method
|
|
215
|
-
|
|
|
216
|
-
| `Paragraph.create(text?, formatting?)`
|
|
217
|
-
| `Paragraph.create(formatting?)`
|
|
218
|
-
| `Paragraph.createWithStyle(text, styleId)`
|
|
219
|
-
| `Paragraph.createEmpty()`
|
|
220
|
-
| `Paragraph.createFormatted(text, run?, paragraph?)`
|
|
196
|
+
| Method | Description | Example |
|
|
197
|
+
| --------------------------------------------------- | --------------------------- | --------------------------------------- |
|
|
198
|
+
| `Paragraph.create(text?, formatting?)` | Create detached paragraph | `Paragraph.create('Text')` |
|
|
199
|
+
| `Paragraph.create(formatting?)` | Create with formatting only | `Paragraph.create({alignment: 'left'})` |
|
|
200
|
+
| `Paragraph.createWithStyle(text, styleId)` | Create with style | `Paragraph.createWithStyle('', 'H1')` |
|
|
201
|
+
| `Paragraph.createEmpty()` | Create empty paragraph | `Paragraph.createEmpty()` |
|
|
202
|
+
| `Paragraph.createFormatted(text, run?, paragraph?)` | Create with dual formatting | See example above |
|
|
221
203
|
|
|
222
204
|
#### Paragraph Formatting Methods
|
|
223
205
|
|
|
@@ -237,54 +219,56 @@ doc.addParagraph(heading);
|
|
|
237
219
|
|
|
238
220
|
#### Paragraph Manipulation Methods
|
|
239
221
|
|
|
240
|
-
| Method
|
|
241
|
-
|
|
|
242
|
-
| `insertRunAt(index, run)`
|
|
243
|
-
| `removeRunAt(index)`
|
|
244
|
-
| `replaceRunAt(index, run)`
|
|
245
|
-
| `findText(text, options?)`
|
|
246
|
-
| `replaceText(find, replace, options?)` | Replace text in runs
|
|
247
|
-
| `mergeWith(otherPara)`
|
|
248
|
-
| `clone()`
|
|
222
|
+
| Method | Description | Returns |
|
|
223
|
+
| -------------------------------------- | ----------------------- | ----------- |
|
|
224
|
+
| `insertRunAt(index, run)` | Insert run at position | `this` |
|
|
225
|
+
| `removeRunAt(index)` | Remove run at position | `boolean` |
|
|
226
|
+
| `replaceRunAt(index, run)` | Replace run at position | `boolean` |
|
|
227
|
+
| `findText(text, options?)` | Find text in runs | `number[]` |
|
|
228
|
+
| `replaceText(find, replace, options?)` | Replace text in runs | `number` |
|
|
229
|
+
| `mergeWith(otherPara)` | Merge another paragraph | `this` |
|
|
230
|
+
| `clone()` | Clone paragraph | `Paragraph` |
|
|
249
231
|
|
|
250
232
|
**Example:**
|
|
233
|
+
|
|
251
234
|
```typescript
|
|
252
|
-
const para = doc.createParagraph(
|
|
235
|
+
const para = doc.createParagraph("Hello World");
|
|
253
236
|
|
|
254
237
|
// Find and replace
|
|
255
|
-
const indices = para.findText(
|
|
256
|
-
const count = para.replaceText(
|
|
238
|
+
const indices = para.findText("World"); // [1]
|
|
239
|
+
const count = para.replaceText("World", "Universe", { caseSensitive: true });
|
|
257
240
|
|
|
258
241
|
// Manipulate runs
|
|
259
|
-
para.insertRunAt(0, new Run(
|
|
260
|
-
para.replaceRunAt(1, new Run(
|
|
242
|
+
para.insertRunAt(0, new Run("Start: ", { bold: true }));
|
|
243
|
+
para.replaceRunAt(1, new Run("HELLO", { allCaps: true }));
|
|
261
244
|
|
|
262
245
|
// Merge paragraphs
|
|
263
|
-
const para2 = Paragraph.create(
|
|
264
|
-
para.mergeWith(para2);
|
|
246
|
+
const para2 = Paragraph.create(" More text");
|
|
247
|
+
para.mergeWith(para2); // Combines runs
|
|
265
248
|
```
|
|
266
249
|
|
|
267
250
|
### Run (Text Span) Operations
|
|
268
251
|
|
|
269
|
-
| Method
|
|
270
|
-
|
|
|
271
|
-
| `clone()`
|
|
272
|
-
| `insertText(index, text)`
|
|
273
|
-
| `appendText(text)`
|
|
274
|
-
| `replaceText(start, end, text)` | Replace text range
|
|
252
|
+
| Method | Description | Returns |
|
|
253
|
+
| ------------------------------- | ------------------------- | ------- |
|
|
254
|
+
| `clone()` | Clone run with formatting | `Run` |
|
|
255
|
+
| `insertText(index, text)` | Insert text at position | `this` |
|
|
256
|
+
| `appendText(text)` | Append text to end | `this` |
|
|
257
|
+
| `replaceText(start, end, text)` | Replace text range | `this` |
|
|
275
258
|
|
|
276
259
|
**Example:**
|
|
260
|
+
|
|
277
261
|
```typescript
|
|
278
|
-
const run = new Run(
|
|
262
|
+
const run = new Run("Hello World", { bold: true });
|
|
279
263
|
|
|
280
264
|
// Text manipulation
|
|
281
|
-
run.insertText(6,
|
|
282
|
-
run.appendText(
|
|
283
|
-
run.replaceText(0, 5,
|
|
265
|
+
run.insertText(6, "Beautiful "); // "Hello Beautiful World"
|
|
266
|
+
run.appendText("!"); // "Hello Beautiful World!"
|
|
267
|
+
run.replaceText(0, 5, "Hi"); // "Hi Beautiful World!"
|
|
284
268
|
|
|
285
269
|
// Clone for reuse
|
|
286
270
|
const copy = run.clone();
|
|
287
|
-
copy.setColor(
|
|
271
|
+
copy.setColor("FF0000"); // Original unchanged
|
|
288
272
|
```
|
|
289
273
|
|
|
290
274
|
### Table Operations
|
|
@@ -304,19 +288,20 @@ copy.setColor('FF0000'); // Original unchanged
|
|
|
304
288
|
|
|
305
289
|
#### Advanced Table Operations
|
|
306
290
|
|
|
307
|
-
| Method
|
|
308
|
-
|
|
|
309
|
-
| `mergeCells(startRow, startCol, endRow, endCol)` | Merge cells
|
|
310
|
-
| `splitCell(row, col)`
|
|
311
|
-
| `moveCell(fromRow, fromCol, toRow, toCol)`
|
|
312
|
-
| `swapCells(row1, col1, row2, col2)`
|
|
313
|
-
| `setColumnWidth(index, width)`
|
|
314
|
-
| `setColumnWidths(widths)`
|
|
315
|
-
| `insertRows(startIndex, count)`
|
|
316
|
-
| `removeRows(startIndex, count)`
|
|
317
|
-
| `clone()`
|
|
291
|
+
| Method | Description | Returns |
|
|
292
|
+
| ------------------------------------------------ | ------------------------- | ------------ |
|
|
293
|
+
| `mergeCells(startRow, startCol, endRow, endCol)` | Merge cells | `this` |
|
|
294
|
+
| `splitCell(row, col)` | Remove cell spanning | `this` |
|
|
295
|
+
| `moveCell(fromRow, fromCol, toRow, toCol)` | Move cell contents | `this` |
|
|
296
|
+
| `swapCells(row1, col1, row2, col2)` | Swap two cells | `this` |
|
|
297
|
+
| `setColumnWidth(index, width)` | Set specific column width | `this` |
|
|
298
|
+
| `setColumnWidths(widths)` | Set all column widths | `this` |
|
|
299
|
+
| `insertRows(startIndex, count)` | Insert multiple rows | `TableRow[]` |
|
|
300
|
+
| `removeRows(startIndex, count)` | Remove multiple rows | `boolean` |
|
|
301
|
+
| `clone()` | Clone entire table | `Table` |
|
|
318
302
|
|
|
319
303
|
**Example:**
|
|
304
|
+
|
|
320
305
|
```typescript
|
|
321
306
|
const table = doc.createTable(3, 3);
|
|
322
307
|
|
|
@@ -330,12 +315,12 @@ table.moveCell(1, 1, 2, 2);
|
|
|
330
315
|
table.swapCells(0, 0, 2, 2);
|
|
331
316
|
|
|
332
317
|
// Batch row operations
|
|
333
|
-
table.insertRows(1, 3);
|
|
334
|
-
table.removeRows(4, 2);
|
|
318
|
+
table.insertRows(1, 3); // Insert 3 rows at position 1
|
|
319
|
+
table.removeRows(4, 2); // Remove 2 rows starting at position 4
|
|
335
320
|
|
|
336
321
|
// Set column widths
|
|
337
|
-
table.setColumnWidth(0, 2000);
|
|
338
|
-
table.setColumnWidths([2000, 3000, 2000]);
|
|
322
|
+
table.setColumnWidth(0, 2000); // First column = 2000 twips
|
|
323
|
+
table.setColumnWidths([2000, 3000, 2000]); // All columns
|
|
339
324
|
|
|
340
325
|
// Clone table for reuse
|
|
341
326
|
const tableCopy = table.clone();
|
|
@@ -366,27 +351,28 @@ const tableCopy = table.clone();
|
|
|
366
351
|
|
|
367
352
|
#### Style Manipulation
|
|
368
353
|
|
|
369
|
-
| Method
|
|
370
|
-
|
|
|
371
|
-
| `style.clone()`
|
|
372
|
-
| `style.mergeWith(other)` | Merge properties from another style | `this`
|
|
354
|
+
| Method | Description | Returns |
|
|
355
|
+
| ------------------------ | ----------------------------------- | ------- |
|
|
356
|
+
| `style.clone()` | Clone style | `Style` |
|
|
357
|
+
| `style.mergeWith(other)` | Merge properties from another style | `this` |
|
|
373
358
|
|
|
374
359
|
**Example:**
|
|
360
|
+
|
|
375
361
|
```typescript
|
|
376
362
|
// Clone a style
|
|
377
|
-
const heading1 = doc.getStyle(
|
|
363
|
+
const heading1 = doc.getStyle("Heading1");
|
|
378
364
|
const customHeading = heading1.clone();
|
|
379
|
-
customHeading.setRunFormatting({ color:
|
|
365
|
+
customHeading.setRunFormatting({ color: "FF0000" });
|
|
380
366
|
|
|
381
367
|
// Merge styles
|
|
382
368
|
const baseStyle = Style.createNormalStyle();
|
|
383
369
|
const overrideStyle = Style.create({
|
|
384
|
-
styleId:
|
|
385
|
-
name:
|
|
386
|
-
type:
|
|
387
|
-
runFormatting: { bold: true, color:
|
|
370
|
+
styleId: "Override",
|
|
371
|
+
name: "Override",
|
|
372
|
+
type: "paragraph",
|
|
373
|
+
runFormatting: { bold: true, color: "FF0000" },
|
|
388
374
|
});
|
|
389
|
-
baseStyle.mergeWith(overrideStyle);
|
|
375
|
+
baseStyle.mergeWith(overrideStyle); // baseStyle now has bold red text
|
|
390
376
|
```
|
|
391
377
|
|
|
392
378
|
#### Built-in Styles
|
|
@@ -409,90 +395,94 @@ baseStyle.mergeWith(overrideStyle); // baseStyle now has bold red text
|
|
|
409
395
|
|
|
410
396
|
#### Basic TOC Creation
|
|
411
397
|
|
|
412
|
-
| Method
|
|
413
|
-
|
|
|
414
|
-
| `addTableOfContents(toc?)`
|
|
415
|
-
| `insertTocAt(index, toc)`
|
|
416
|
-
| `removeTocAt(index)`
|
|
398
|
+
| Method | Description | Example |
|
|
399
|
+
| -------------------------- | ---------------------- | -------------------------- |
|
|
400
|
+
| `addTableOfContents(toc?)` | Add TOC to document | `doc.addTableOfContents()` |
|
|
401
|
+
| `insertTocAt(index, toc)` | Insert TOC at position | `doc.insertTocAt(0, toc)` |
|
|
402
|
+
| `removeTocAt(index)` | Remove TOC at position | `doc.removeTocAt(0)` |
|
|
417
403
|
|
|
418
404
|
#### TOC Factory Methods
|
|
419
405
|
|
|
420
|
-
| Method
|
|
421
|
-
|
|
|
422
|
-
| `TableOfContents.createStandard(title?)`
|
|
423
|
-
| `TableOfContents.createSimple(title?)`
|
|
424
|
-
| `TableOfContents.createDetailed(title?)`
|
|
425
|
-
| `TableOfContents.createHyperlinked(title?)`
|
|
426
|
-
| `TableOfContents.createWithStyles(styles, opts?)`
|
|
427
|
-
| `TableOfContents.createFlat(title?, styles?)`
|
|
428
|
-
| `TableOfContents.createNumbered(title?, format?)`
|
|
429
|
-
| `TableOfContents.createWithSpacing(spacing, opts?)`
|
|
430
|
-
| `TableOfContents.createWithHyperlinkColor(color, opts?)` | Custom hyperlink color
|
|
406
|
+
| Method | Description | Example |
|
|
407
|
+
| -------------------------------------------------------- | ------------------------ | ---------------------------------------------------- |
|
|
408
|
+
| `TableOfContents.createStandard(title?)` | Standard TOC (3 levels) | `TableOfContents.createStandard()` |
|
|
409
|
+
| `TableOfContents.createSimple(title?)` | Simple TOC (2 levels) | `TableOfContents.createSimple()` |
|
|
410
|
+
| `TableOfContents.createDetailed(title?)` | Detailed TOC (4 levels) | `TableOfContents.createDetailed()` |
|
|
411
|
+
| `TableOfContents.createHyperlinked(title?)` | Hyperlinked TOC | `TableOfContents.createHyperlinked()` |
|
|
412
|
+
| `TableOfContents.createWithStyles(styles, opts?)` | TOC with specific styles | `TableOfContents.createWithStyles(['H1','H3'])` |
|
|
413
|
+
| `TableOfContents.createFlat(title?, styles?)` | Flat TOC (no indent) | `TableOfContents.createFlat()` |
|
|
414
|
+
| `TableOfContents.createNumbered(title?, format?)` | Numbered TOC | `TableOfContents.createNumbered('TOC', 'roman')` |
|
|
415
|
+
| `TableOfContents.createWithSpacing(spacing, opts?)` | TOC with custom spacing | `TableOfContents.createWithSpacing(120)` |
|
|
416
|
+
| `TableOfContents.createWithHyperlinkColor(color, opts?)` | Custom hyperlink color | `TableOfContents.createWithHyperlinkColor('FF0000')` |
|
|
431
417
|
|
|
432
418
|
#### TOC Configuration Methods
|
|
433
419
|
|
|
434
|
-
| Method
|
|
435
|
-
|
|
|
436
|
-
| `setIncludeStyles(styles)`
|
|
437
|
-
| `setNumbered(numbered, format?)`
|
|
438
|
-
| `setNoIndent(noIndent)`
|
|
439
|
-
| `setCustomIndents(indents)`
|
|
440
|
-
| `setSpaceBetweenEntries(spacing)`
|
|
441
|
-
| `setHyperlinkColor(color)`
|
|
442
|
-
| `configure(options)`
|
|
420
|
+
| Method | Description | Values |
|
|
421
|
+
| --------------------------------- | ------------------------------ | -------------------------- |
|
|
422
|
+
| `setIncludeStyles(styles)` | Select specific heading styles | `['Heading1', 'Heading3']` |
|
|
423
|
+
| `setNumbered(numbered, format?)` | Enable/disable numbering | `(true, 'roman')` |
|
|
424
|
+
| `setNoIndent(noIndent)` | Remove indentation | `true/false` |
|
|
425
|
+
| `setCustomIndents(indents)` | Custom indents per level | `[0, 200, 400]` (twips) |
|
|
426
|
+
| `setSpaceBetweenEntries(spacing)` | Spacing between entries | `120` (twips) |
|
|
427
|
+
| `setHyperlinkColor(color)` | Hyperlink color | `'0000FF'` (default blue) |
|
|
428
|
+
| `configure(options)` | Bulk configuration | See example below |
|
|
443
429
|
|
|
444
430
|
#### TOC Properties
|
|
445
431
|
|
|
446
|
-
| Property
|
|
447
|
-
|
|
|
448
|
-
| `title`
|
|
449
|
-
| `levels`
|
|
450
|
-
| `includeStyles`
|
|
451
|
-
| `showPageNumbers`
|
|
452
|
-
| `useHyperlinks`
|
|
453
|
-
| `numbered`
|
|
454
|
-
| `numberingFormat`
|
|
455
|
-
| `noIndent`
|
|
456
|
-
| `customIndents`
|
|
457
|
-
| `spaceBetweenEntries
|
|
458
|
-
| `hyperlinkColor`
|
|
459
|
-
| `tabLeader`
|
|
432
|
+
| Property | Type | Default | Description |
|
|
433
|
+
| --------------------- | ------------------------------------ | --------------------- | ---------------------------------- |
|
|
434
|
+
| `title` | `string` | `'Table of Contents'` | TOC title |
|
|
435
|
+
| `levels` | `number` (1-9) | `3` | Heading levels to include |
|
|
436
|
+
| `includeStyles` | `string[]` | `undefined` | Specific styles (overrides levels) |
|
|
437
|
+
| `showPageNumbers` | `boolean` | `true` | Show page numbers |
|
|
438
|
+
| `useHyperlinks` | `boolean` | `false` | Use hyperlinks instead of page #s |
|
|
439
|
+
| `numbered` | `boolean` | `false` | Number TOC entries |
|
|
440
|
+
| `numberingFormat` | `'decimal'/'roman'/'alpha'` | `'decimal'` | Numbering format |
|
|
441
|
+
| `noIndent` | `boolean` | `false` | Remove all indentation |
|
|
442
|
+
| `customIndents` | `number[]` | `undefined` | Custom indents in twips |
|
|
443
|
+
| `spaceBetweenEntries` | `number` | `0` | Spacing in twips |
|
|
444
|
+
| `hyperlinkColor` | `string` | `'0000FF'` | Hyperlink color (hex without #) |
|
|
445
|
+
| `tabLeader` | `'dot'/'hyphen'/'underscore'/'none'` | `'dot'` | Tab leader character |
|
|
460
446
|
|
|
461
447
|
**Example:**
|
|
448
|
+
|
|
462
449
|
```typescript
|
|
463
450
|
// Basic TOC
|
|
464
451
|
const simpleToc = TableOfContents.createStandard();
|
|
465
452
|
doc.addTableOfContents(simpleToc);
|
|
466
453
|
|
|
467
454
|
// Select specific styles (e.g., only Heading1 and Heading3)
|
|
468
|
-
const customToc = TableOfContents.createWithStyles([
|
|
455
|
+
const customToc = TableOfContents.createWithStyles(["Heading1", "Heading3"]);
|
|
469
456
|
|
|
470
457
|
// Flat TOC with no indentation
|
|
471
|
-
const flatToc = TableOfContents.createFlat(
|
|
458
|
+
const flatToc = TableOfContents.createFlat("Contents");
|
|
472
459
|
|
|
473
460
|
// Numbered TOC with roman numerals
|
|
474
|
-
const numberedToc = TableOfContents.createNumbered(
|
|
461
|
+
const numberedToc = TableOfContents.createNumbered(
|
|
462
|
+
"Table of Contents",
|
|
463
|
+
"roman"
|
|
464
|
+
);
|
|
475
465
|
|
|
476
466
|
// Custom hyperlink color (red instead of blue)
|
|
477
|
-
const coloredToc = TableOfContents.createWithHyperlinkColor(
|
|
467
|
+
const coloredToc = TableOfContents.createWithHyperlinkColor("FF0000");
|
|
478
468
|
|
|
479
469
|
// Advanced configuration
|
|
480
470
|
const toc = TableOfContents.create()
|
|
481
|
-
.setIncludeStyles([
|
|
482
|
-
.setNumbered(true,
|
|
483
|
-
.setSpaceBetweenEntries(120)
|
|
484
|
-
.setHyperlinkColor(
|
|
471
|
+
.setIncludeStyles(["Heading1", "Heading2", "Heading3"])
|
|
472
|
+
.setNumbered(true, "decimal")
|
|
473
|
+
.setSpaceBetweenEntries(120) // 6pt spacing
|
|
474
|
+
.setHyperlinkColor("0000FF")
|
|
485
475
|
.setNoIndent(false);
|
|
486
476
|
|
|
487
477
|
// Or use configure() for bulk settings
|
|
488
478
|
toc.configure({
|
|
489
|
-
title:
|
|
490
|
-
includeStyles: [
|
|
479
|
+
title: "Table of Contents",
|
|
480
|
+
includeStyles: ["Heading1", "CustomHeader"],
|
|
491
481
|
numbered: true,
|
|
492
|
-
numberingFormat:
|
|
482
|
+
numberingFormat: "alpha",
|
|
493
483
|
spaceBetweenEntries: 100,
|
|
494
|
-
hyperlinkColor:
|
|
495
|
-
noIndent: true
|
|
484
|
+
hyperlinkColor: "FF0000",
|
|
485
|
+
noIndent: true,
|
|
496
486
|
});
|
|
497
487
|
|
|
498
488
|
// Insert at specific position
|
|
@@ -599,13 +589,104 @@ doc.insertTocAt(0, toc);
|
|
|
599
589
|
|
|
600
590
|
### Unit Conversion Utilities
|
|
601
591
|
|
|
602
|
-
|
|
603
|
-
|
|
|
604
|
-
|
|
|
605
|
-
| `
|
|
606
|
-
| `
|
|
607
|
-
| `
|
|
608
|
-
| `
|
|
592
|
+
#### Twips Conversions
|
|
593
|
+
| Function | Description | Example |
|
|
594
|
+
| ------------------------- | ------------------- | --------------------------- |
|
|
595
|
+
| `twipsToPoints(twips)` | Twips to points | `twipsToPoints(240)` // 12 |
|
|
596
|
+
| `twipsToInches(twips)` | Twips to inches | `twipsToInches(1440)` // 1 |
|
|
597
|
+
| `twipsToCm(twips)` | Twips to cm | `twipsToCm(1440)` // 2.54 |
|
|
598
|
+
| `twipsToEmus(twips)` | Twips to EMUs | `twipsToEmus(1440)` |
|
|
599
|
+
|
|
600
|
+
#### EMUs (English Metric Units) Conversions
|
|
601
|
+
| Function | Description | Example |
|
|
602
|
+
| --------------------------- | -------------------- | ----------------------------- |
|
|
603
|
+
| `emusToTwips(emus)` | EMUs to twips | `emusToTwips(914400)` // 1440 |
|
|
604
|
+
| `emusToInches(emus)` | EMUs to inches | `emusToInches(914400)` // 1 |
|
|
605
|
+
| `emusToCm(emus)` | EMUs to cm | `emusToCm(914400)` // 2.54 |
|
|
606
|
+
| `emusToPoints(emus)` | EMUs to points | `emusToPoints(914400)` // 72 |
|
|
607
|
+
| `emusToPixels(emus, dpi?)` | EMUs to pixels | `emusToPixels(914400)` // 96 |
|
|
608
|
+
|
|
609
|
+
#### Points Conversions
|
|
610
|
+
| Function | Description | Example |
|
|
611
|
+
| ------------------------ | ------------------ | -------------------------- |
|
|
612
|
+
| `pointsToTwips(points)` | Points to twips | `pointsToTwips(12)` // 240 |
|
|
613
|
+
| `pointsToEmus(points)` | Points to EMUs | `pointsToEmus(72)` |
|
|
614
|
+
| `pointsToInches(points)` | Points to inches | `pointsToInches(72)` // 1 |
|
|
615
|
+
| `pointsToCm(points)` | Points to cm | `pointsToCm(72)` // 2.54 |
|
|
616
|
+
|
|
617
|
+
#### Inches Conversions
|
|
618
|
+
| Function | Description | Example |
|
|
619
|
+
| ----------------------------- | ------------------- | ----------------------------- |
|
|
620
|
+
| `inchesToTwips(inches)` | Inches to twips | `inchesToTwips(1)` // 1440 |
|
|
621
|
+
| `inchesToEmus(inches)` | Inches to EMUs | `inchesToEmus(1)` // 914400 |
|
|
622
|
+
| `inchesToPoints(inches)` | Inches to points | `inchesToPoints(1)` // 72 |
|
|
623
|
+
| `inchesToCm(inches)` | Inches to cm | `inchesToCm(1)` // 2.54 |
|
|
624
|
+
| `inchesToPixels(inches, dpi)` | Inches to pixels | `inchesToPixels(1, 96)` // 96 |
|
|
625
|
+
|
|
626
|
+
#### Centimeters Conversions
|
|
627
|
+
| Function | Description | Example |
|
|
628
|
+
| ----------------------- | ---------------- | --------------------------- |
|
|
629
|
+
| `cmToTwips(cm)` | cm to twips | `cmToTwips(2.54)` // 1440 |
|
|
630
|
+
| `cmToEmus(cm)` | cm to EMUs | `cmToEmus(2.54)` // 914400 |
|
|
631
|
+
| `cmToInches(cm)` | cm to inches | `cmToInches(2.54)` // 1 |
|
|
632
|
+
| `cmToPoints(cm)` | cm to points | `cmToPoints(2.54)` // 72 |
|
|
633
|
+
| `cmToPixels(cm, dpi?)` | cm to pixels | `cmToPixels(2.54, 96)` // 96|
|
|
634
|
+
|
|
635
|
+
#### Pixels Conversions
|
|
636
|
+
| Function | Description | Example |
|
|
637
|
+
| ---------------------------- | ------------------- | ------------------------------ |
|
|
638
|
+
| `pixelsToEmus(pixels, dpi?)` | Pixels to EMUs | `pixelsToEmus(96)` // 914400 |
|
|
639
|
+
| `pixelsToInches(pixels, dpi?)`| Pixels to inches | `pixelsToInches(96, 96)` // 1 |
|
|
640
|
+
| `pixelsToTwips(pixels, dpi?)`| Pixels to twips | `pixelsToTwips(96, 96)` // 1440|
|
|
641
|
+
| `pixelsToCm(pixels, dpi?)` | Pixels to cm | `pixelsToCm(96, 96)` // 2.54 |
|
|
642
|
+
| `pixelsToPoints(pixels, dpi?)`| Pixels to points | `pixelsToPoints(96, 96)` // 72 |
|
|
643
|
+
|
|
644
|
+
**Note:** Default DPI is 96 for pixel conversions
|
|
645
|
+
|
|
646
|
+
### ZIP Archive Helper Methods
|
|
647
|
+
|
|
648
|
+
#### File Operations
|
|
649
|
+
| Method | Description | Example |
|
|
650
|
+
| ------------------------------- | ------------------------- | -------------------------------------------- |
|
|
651
|
+
| `addFile(path, content)` | Add file to archive | `handler.addFile('doc.xml', xmlContent)` |
|
|
652
|
+
| `updateFile(path, content)` | Update existing file | `handler.updateFile('doc.xml', newContent)` |
|
|
653
|
+
| `removeFile(path)` | Remove file from archive | `handler.removeFile('old.xml')` |
|
|
654
|
+
| `renameFile(oldPath, newPath)` | Rename file | `handler.renameFile('a.xml', 'b.xml')` |
|
|
655
|
+
| `copyFile(srcPath, destPath)` | Copy file | `handler.copyFile('a.xml', 'copy-a.xml')` |
|
|
656
|
+
| `moveFile(srcPath, destPath)` | Move file | `handler.moveFile('a.xml', 'folder/a.xml')` |
|
|
657
|
+
|
|
658
|
+
#### File Retrieval
|
|
659
|
+
| Method | Description | Returns |
|
|
660
|
+
| ------------------------- | ---------------------- | --------------- |
|
|
661
|
+
| `getFile(path)` | Get file object | `ZipFile` |
|
|
662
|
+
| `getFileAsString(path)` | Get file as string | `string` |
|
|
663
|
+
| `getFileAsBuffer(path)` | Get file as buffer | `Buffer` |
|
|
664
|
+
| `hasFile(path)` | Check if file exists | `boolean` |
|
|
665
|
+
| `getFilePaths()` | Get all file paths | `string[]` |
|
|
666
|
+
| `getAllFiles()` | Get all files | `FileMap` |
|
|
667
|
+
|
|
668
|
+
#### Batch Operations
|
|
669
|
+
| Method | Description | Returns |
|
|
670
|
+
| ------------------------------- | ---------------------------- | -------------- |
|
|
671
|
+
| `removeFiles(paths[])` | Remove multiple files | `number` |
|
|
672
|
+
| `getFilesByExtension(ext)` | Get files by extension | `ZipFile[]` |
|
|
673
|
+
| `getTextFiles()` | Get all text files | `ZipFile[]` |
|
|
674
|
+
| `getBinaryFiles()` | Get all binary files | `ZipFile[]` |
|
|
675
|
+
| `getMediaFiles()` | Get media files | `ZipFile[]` |
|
|
676
|
+
|
|
677
|
+
#### Archive Information
|
|
678
|
+
| Method | Description | Returns |
|
|
679
|
+
| ------------------ | ------------------------- | ------------------------ |
|
|
680
|
+
| `getFileCount()` | Count files in archive | `number` |
|
|
681
|
+
| `getTotalSize()` | Get total size in bytes | `number` |
|
|
682
|
+
| `getStats()` | Get detailed statistics | `{fileCount, size, ...}` |
|
|
683
|
+
| `isEmpty()` | Check if archive is empty | `boolean` |
|
|
684
|
+
|
|
685
|
+
#### Import/Export
|
|
686
|
+
| Method | Description | Returns |
|
|
687
|
+
| -------------------------------- | ------------------------ | -------------------- |
|
|
688
|
+
| `exportFile(internal, external)` | Export file from archive | `Promise<void>` |
|
|
689
|
+
| `importFile(external, internal)` | Import file to archive | `Promise<void>` |
|
|
609
690
|
|
|
610
691
|
## Common Recipes
|
|
611
692
|
|
|
@@ -997,11 +1078,11 @@ const run = new Run("Text<w:t>value</w:t>", { cleanXmlFromText: false });
|
|
|
997
1078
|
|
|
998
1079
|
**The Right Approach**: Use the framework's API methods instead of embedding XML:
|
|
999
1080
|
|
|
1000
|
-
-
|
|
1001
|
-
-
|
|
1002
|
-
-
|
|
1003
|
-
-
|
|
1004
|
-
-
|
|
1081
|
+
- Use `paragraph.addText()` multiple times for separate text runs
|
|
1082
|
+
- Use formatting options: `{bold: true}`, `{italic: true}`, etc.
|
|
1083
|
+
- Use `paragraph.addHyperlink()` for links
|
|
1084
|
+
- Don't pass XML strings to text methods
|
|
1085
|
+
- Don't try to embed `<w:t>` or other XML tags in your text
|
|
1005
1086
|
|
|
1006
1087
|
For more details, see the [corruption detection examples](examples/troubleshooting/).
|
|
1007
1088
|
|
|
@@ -1012,6 +1093,7 @@ For more details, see the [corruption detection examples](examples/troubleshooti
|
|
|
1012
1093
|
**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.
|
|
1013
1094
|
|
|
1014
1095
|
**Why This Causes Problems**:
|
|
1096
|
+
|
|
1015
1097
|
- `pageBreakBefore` tells Word to insert a page break before the paragraph
|
|
1016
1098
|
- `keepNext` tells Word to keep the paragraph with the next one (no break)
|
|
1017
1099
|
- `keepLines` tells Word to keep all lines together (no break)
|
|
@@ -1025,13 +1107,14 @@ The framework now automatically prevents these conflicts by **prioritizing keep
|
|
|
1025
1107
|
// When setting keepNext or keepLines, pageBreakBefore is automatically cleared
|
|
1026
1108
|
const para = new Paragraph()
|
|
1027
1109
|
.addText("Content")
|
|
1028
|
-
.setPageBreakBefore(true)
|
|
1029
|
-
.setKeepNext(true);
|
|
1110
|
+
.setPageBreakBefore(true) // Set to true
|
|
1111
|
+
.setKeepNext(true); // Automatically clears pageBreakBefore
|
|
1030
1112
|
|
|
1031
1113
|
// Result: keepNext=true, pageBreakBefore=false (conflict resolved)
|
|
1032
1114
|
```
|
|
1033
1115
|
|
|
1034
1116
|
**Why This Priority?**
|
|
1117
|
+
|
|
1035
1118
|
- Keep properties (`keepNext`/`keepLines`) represent explicit user intent to keep content together
|
|
1036
1119
|
- Page breaks are often layout hints that may conflict with document flow
|
|
1037
1120
|
- Removing `pageBreakBefore` eliminates whitespace while preserving the user's intention
|
|
@@ -1049,6 +1132,7 @@ const doc = await Document.load("document-with-conflicts.docx");
|
|
|
1049
1132
|
```
|
|
1050
1133
|
|
|
1051
1134
|
**How It Works**:
|
|
1135
|
+
|
|
1052
1136
|
1. When `setKeepNext(true)` is called, `pageBreakBefore` is automatically set to `false`
|
|
1053
1137
|
2. When `setKeepLines(true)` is called, `pageBreakBefore` is automatically set to `false`
|
|
1054
1138
|
3. When parsing documents, if both properties exist, `pageBreakBefore` is cleared
|
|
@@ -1060,7 +1144,7 @@ If you need a page break despite keep properties, set it after:
|
|
|
1060
1144
|
|
|
1061
1145
|
```typescript
|
|
1062
1146
|
const para = new Paragraph()
|
|
1063
|
-
.setKeepNext(true)
|
|
1147
|
+
.setKeepNext(true) // Set first
|
|
1064
1148
|
.setPageBreakBefore(true); // Override - you explicitly want this conflict
|
|
1065
1149
|
|
|
1066
1150
|
// But note: This will cause layout issues (whitespace) in Word
|
|
@@ -1080,12 +1164,12 @@ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md)
|
|
|
1080
1164
|
|
|
1081
1165
|
**Critical Bug Fix Release:**
|
|
1082
1166
|
|
|
1083
|
-
-
|
|
1084
|
-
-
|
|
1085
|
-
-
|
|
1086
|
-
-
|
|
1087
|
-
-
|
|
1088
|
-
-
|
|
1167
|
+
- **Fixed Paragraph.getText()** - Now includes hyperlink text content (critical data loss bug)
|
|
1168
|
+
- **Added hyperlink integration tests** - 6 new comprehensive test cases
|
|
1169
|
+
- **Enhanced test suite** - 474/478 tests passing (98.1% pass rate)
|
|
1170
|
+
- **Fixed type safety** - XMLElement handling improvements across test files
|
|
1171
|
+
- **Improved StylesManager** - XML corruption detection moved before parser
|
|
1172
|
+
- **Hyperlink management** - Proper relationship ID clearing on URL updates
|
|
1089
1173
|
|
|
1090
1174
|
**What This Fixes:**
|
|
1091
1175
|
When using `para.addText('foo') + para.addHyperlink(link)`, the hyperlink text is now properly included in `paragraph.getText()`, preventing silent text loss.
|