@wonderwhy-er/desktop-commander 0.2.35 → 0.2.37
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 +3 -0
- package/dist/handlers/filesystem-handlers.js +58 -11
- package/dist/handlers/history-handlers.d.ts +7 -0
- package/dist/handlers/history-handlers.js +33 -1
- package/dist/remote-device/remote-channel.d.ts +8 -3
- package/dist/remote-device/remote-channel.js +68 -21
- package/dist/search-manager.d.ts +13 -0
- package/dist/search-manager.js +146 -0
- package/dist/server.js +56 -4
- package/dist/test-docx.d.ts +1 -0
- package/dist/tools/docx/builders/image.d.ts +14 -0
- package/dist/tools/docx/builders/image.js +84 -0
- package/dist/tools/docx/builders/index.d.ts +9 -3
- package/dist/tools/docx/builders/index.js +9 -3
- package/dist/tools/docx/builders/paragraph.d.ts +12 -0
- package/dist/tools/docx/builders/paragraph.js +29 -0
- package/dist/tools/docx/builders/table.d.ts +10 -0
- package/dist/tools/docx/builders/table.js +138 -0
- package/dist/tools/docx/builders/utils.d.ts +5 -0
- package/dist/tools/docx/builders/utils.js +18 -0
- package/dist/tools/docx/constants.d.ts +28 -32
- package/dist/tools/docx/constants.js +56 -52
- package/dist/tools/docx/create.d.ts +21 -0
- package/dist/tools/docx/create.js +386 -0
- package/dist/tools/docx/dom.d.ts +139 -0
- package/dist/tools/docx/dom.js +448 -0
- package/dist/tools/docx/index.d.ts +8 -12
- package/dist/tools/docx/index.js +8 -14
- package/dist/tools/docx/modify.d.ts +28 -0
- package/dist/tools/docx/modify.js +271 -0
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +11 -0
- package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +23 -0
- package/dist/tools/docx/ops/header-replace-text-exact.d.ts +13 -0
- package/dist/tools/docx/ops/header-replace-text-exact.js +55 -0
- package/dist/tools/docx/ops/index.d.ts +17 -0
- package/dist/tools/docx/ops/index.js +70 -0
- package/dist/tools/docx/ops/insert-image-after-text.d.ts +24 -0
- package/dist/tools/docx/ops/insert-image-after-text.js +128 -0
- package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +12 -0
- package/dist/tools/docx/ops/insert-paragraph-after-text.js +74 -0
- package/dist/tools/docx/ops/insert-table-after-text.d.ts +19 -0
- package/dist/tools/docx/ops/insert-table-after-text.js +57 -0
- package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +12 -0
- package/dist/tools/docx/ops/replace-hyperlink-url.js +37 -0
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +9 -0
- package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +25 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +21 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.js +36 -0
- package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
- package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +9 -0
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +24 -0
- package/dist/tools/docx/ops/set-color-for-style.d.ts +13 -0
- package/dist/tools/docx/ops/set-color-for-style.js +31 -0
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +8 -0
- package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +57 -0
- package/dist/tools/docx/ops/table-set-cell-text.d.ts +9 -0
- package/dist/tools/docx/ops/table-set-cell-text.js +40 -0
- package/dist/tools/docx/read.d.ts +27 -0
- package/dist/tools/docx/read.js +308 -0
- package/dist/tools/docx/relationships.d.ts +22 -0
- package/dist/tools/docx/relationships.js +76 -0
- package/dist/tools/docx/types.d.ts +202 -103
- package/dist/tools/docx/types.js +2 -5
- package/dist/tools/docx/validate.d.ts +33 -0
- package/dist/tools/docx/validate.js +49 -0
- package/dist/tools/docx/write.d.ts +17 -0
- package/dist/tools/docx/write.js +88 -0
- package/dist/tools/docx/xml-view-test.d.ts +1 -0
- package/dist/tools/docx/xml-view-test.js +63 -0
- package/dist/tools/docx/xml-view.d.ts +56 -0
- package/dist/tools/docx/xml-view.js +169 -0
- package/dist/tools/docx/zip.d.ts +21 -0
- package/dist/tools/docx/zip.js +35 -0
- package/dist/tools/edit.js +57 -27
- package/dist/tools/schemas.d.ts +13 -0
- package/dist/tools/schemas.js +6 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/contracts.d.ts +14 -0
- package/dist/ui/contracts.js +18 -0
- package/dist/ui/file-preview/index.html +16 -0
- package/dist/ui/file-preview/preview-runtime.js +13983 -0
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
- package/dist/ui/file-preview/shared/preview-file-types.js +57 -0
- package/dist/ui/file-preview/src/app.d.ts +4 -0
- package/dist/ui/file-preview/src/app.js +800 -0
- package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
- package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
- package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
- package/dist/ui/file-preview/src/components/highlighting.js +54 -0
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +9 -0
- package/dist/ui/file-preview/src/components/html-renderer.js +63 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.js +21 -0
- package/dist/ui/file-preview/src/components/toolbar.d.ts +6 -0
- package/dist/ui/file-preview/src/components/toolbar.js +75 -0
- package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
- package/dist/ui/file-preview/src/image-preview.js +21 -0
- package/dist/ui/file-preview/src/main.d.ts +1 -0
- package/dist/ui/file-preview/src/main.js +5 -0
- package/dist/ui/file-preview/src/types.d.ts +1 -0
- package/dist/ui/file-preview/src/types.js +1 -0
- package/dist/ui/file-preview/styles.css +764 -0
- package/dist/ui/resources.d.ts +21 -0
- package/dist/ui/resources.js +72 -0
- package/dist/ui/shared/escape-html.d.ts +4 -0
- package/dist/ui/shared/escape-html.js +11 -0
- package/dist/ui/shared/host-lifecycle.d.ts +16 -0
- package/dist/ui/shared/host-lifecycle.js +35 -0
- package/dist/ui/shared/rpc-client.d.ts +14 -0
- package/dist/ui/shared/rpc-client.js +72 -0
- package/dist/ui/shared/theme-adaptation.d.ts +10 -0
- package/dist/ui/shared/theme-adaptation.js +118 -0
- package/dist/ui/shared/tool-header.d.ts +9 -0
- package/dist/ui/shared/tool-header.js +25 -0
- package/dist/ui/shared/tool-shell.d.ts +16 -0
- package/dist/ui/shared/tool-shell.js +65 -0
- package/dist/ui/shared/widget-state.d.ts +28 -0
- package/dist/ui/shared/widget-state.js +60 -0
- package/dist/utils/capture.d.ts +1 -0
- package/dist/utils/capture.js +176 -8
- package/dist/utils/files/base.d.ts +3 -1
- package/dist/utils/files/docx.d.ts +28 -22
- package/dist/utils/files/docx.js +630 -196
- package/dist/utils/files/factory.d.ts +6 -5
- package/dist/utils/files/factory.js +18 -6
- package/dist/utils/files/text.js +9 -1
- package/dist/utils/system-info.js +1 -1
- package/dist/utils/usageTracker.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM utilities for DOCX XML manipulation.
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: XML parsing, navigation, and minimal element
|
|
5
|
+
* mutation. No file I/O — every function works on in-memory DOM nodes.
|
|
6
|
+
*
|
|
7
|
+
* Uses @xmldom/xmldom for parsing and serialisation so that the
|
|
8
|
+
* document-order of nodes is always preserved.
|
|
9
|
+
*/
|
|
10
|
+
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
12
|
+
// XML parse / serialize
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
14
|
+
export function parseXml(xmlStr) {
|
|
15
|
+
return new DOMParser().parseFromString(xmlStr, 'application/xml');
|
|
16
|
+
}
|
|
17
|
+
export function serializeXml(doc) {
|
|
18
|
+
return new XMLSerializer().serializeToString(doc);
|
|
19
|
+
}
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
21
|
+
// Generic DOM helpers
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
23
|
+
/**
|
|
24
|
+
* Convert any NodeList / HTMLCollection-like object into a real array.
|
|
25
|
+
*/
|
|
26
|
+
export function nodeListToArray(nl) {
|
|
27
|
+
const arr = [];
|
|
28
|
+
for (let i = 0; i < nl.length; i++) {
|
|
29
|
+
const n = nl.item(i);
|
|
30
|
+
if (n)
|
|
31
|
+
arr.push(n);
|
|
32
|
+
}
|
|
33
|
+
return arr;
|
|
34
|
+
}
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
36
|
+
// Body access
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
38
|
+
/** Return the single <w:body> element from a parsed document.xml DOM. */
|
|
39
|
+
export function getBody(doc) {
|
|
40
|
+
const body = doc.getElementsByTagName('w:body').item(0);
|
|
41
|
+
if (!body)
|
|
42
|
+
throw new Error('Invalid DOCX DOM: missing <w:body>');
|
|
43
|
+
return body;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Return ALL direct element children of w:body **in document order**.
|
|
47
|
+
* Includes w:p, w:tbl, w:sdt, w:sectPr, etc.
|
|
48
|
+
*/
|
|
49
|
+
export function getBodyChildren(body) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const node of nodeListToArray(body.childNodes)) {
|
|
52
|
+
if (node.nodeType === 1)
|
|
53
|
+
out.push(node);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Return ALL top‑level tables that are logically in the body, including those
|
|
59
|
+
* wrapped in structured document tags (w:sdt / w:sdtContent).
|
|
60
|
+
*
|
|
61
|
+
* Previous logic only saw tables that were direct children of <w:body>. That
|
|
62
|
+
* meant tables inside SDTs were invisible to table operations and readDocxOutline.
|
|
63
|
+
* This helper walks the body tree and collects any <w:tbl> that appears as a
|
|
64
|
+
* *first‑class* block (we do not recurse into tables themselves, so nested
|
|
65
|
+
* tables are not double‑counted).
|
|
66
|
+
*/
|
|
67
|
+
export function getAllBodyTables(body) {
|
|
68
|
+
const result = [];
|
|
69
|
+
function collectFromNode(node) {
|
|
70
|
+
const name = node.nodeName;
|
|
71
|
+
if (name === 'w:tbl') {
|
|
72
|
+
result.push(node);
|
|
73
|
+
return; // don't recurse into tables to avoid nested counting
|
|
74
|
+
}
|
|
75
|
+
// If this is a structured document tag, look into its content container.
|
|
76
|
+
if (name === 'w:sdt') {
|
|
77
|
+
const sdtContent = findDirectChild(node, 'w:sdtContent');
|
|
78
|
+
if (sdtContent) {
|
|
79
|
+
for (const child of nodeListToArray(sdtContent.childNodes)) {
|
|
80
|
+
if (child.nodeType === 1)
|
|
81
|
+
collectFromNode(child);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Generic container: recurse into element children.
|
|
87
|
+
for (const child of nodeListToArray(node.childNodes)) {
|
|
88
|
+
if (child.nodeType === 1)
|
|
89
|
+
collectFromNode(child);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const child of getBodyChildren(body)) {
|
|
93
|
+
collectFromNode(child);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
98
|
+
// Body signature
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
100
|
+
/**
|
|
101
|
+
* Build a compact signature string from the body children array.
|
|
102
|
+
* Maps each node's qualified name to a short local name:
|
|
103
|
+
* w:p → p, w:tbl → tbl, w:sdt → sdt, w:sectPr → sectPr, …
|
|
104
|
+
* Returns e.g. "p,tbl,p,p,sectPr".
|
|
105
|
+
*/
|
|
106
|
+
export function bodySignature(children) {
|
|
107
|
+
return children
|
|
108
|
+
.map((ch) => {
|
|
109
|
+
const name = ch.nodeName;
|
|
110
|
+
const idx = name.indexOf(':');
|
|
111
|
+
return idx >= 0 ? name.substring(idx + 1) : name;
|
|
112
|
+
})
|
|
113
|
+
.join(',');
|
|
114
|
+
}
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
116
|
+
// Paragraph text helpers
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
118
|
+
/** Concatenate text from every <w:t> descendant of a paragraph. */
|
|
119
|
+
export function getParagraphText(p) {
|
|
120
|
+
const tNodes = p.getElementsByTagName('w:t');
|
|
121
|
+
let out = '';
|
|
122
|
+
for (let i = 0; i < tNodes.length; i++) {
|
|
123
|
+
out += tNodes.item(i)?.textContent ?? '';
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/** Read the style id from w:pPr/w:pStyle/@w:val, or null if absent. */
|
|
128
|
+
export function getParagraphStyle(p) {
|
|
129
|
+
for (const child of nodeListToArray(p.childNodes)) {
|
|
130
|
+
if (child.nodeType === 1 && child.nodeName === 'w:pPr') {
|
|
131
|
+
const pPr = child;
|
|
132
|
+
for (const prChild of nodeListToArray(pPr.childNodes)) {
|
|
133
|
+
if (prChild.nodeType === 1 && prChild.nodeName === 'w:pStyle') {
|
|
134
|
+
return prChild.getAttribute('w:val');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
143
|
+
// Table content extraction
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
145
|
+
/**
|
|
146
|
+
* Extract all text content from a table cell (w:tc).
|
|
147
|
+
* Returns the concatenated text from all paragraphs in the cell.
|
|
148
|
+
*/
|
|
149
|
+
export function getCellText(tc) {
|
|
150
|
+
const paragraphs = tc.getElementsByTagName('w:p');
|
|
151
|
+
const texts = [];
|
|
152
|
+
for (let i = 0; i < paragraphs.length; i++) {
|
|
153
|
+
const p = paragraphs.item(i);
|
|
154
|
+
if (p) {
|
|
155
|
+
const text = getParagraphText(p).trim();
|
|
156
|
+
if (text)
|
|
157
|
+
texts.push(text);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return texts.join(' '); // Join multiple paragraphs in cell with space
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Extract all rows from a table (w:tbl).
|
|
164
|
+
* Returns an array of rows, where each row is an array of cell text strings.
|
|
165
|
+
* First row is treated as header if it exists.
|
|
166
|
+
*/
|
|
167
|
+
export function getTableContent(tbl) {
|
|
168
|
+
const rows = [];
|
|
169
|
+
for (const child of nodeListToArray(tbl.childNodes)) {
|
|
170
|
+
if (child.nodeType === 1 && child.nodeName === 'w:tr') {
|
|
171
|
+
rows.push(child);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (rows.length === 0) {
|
|
175
|
+
return { rows: [] };
|
|
176
|
+
}
|
|
177
|
+
// Extract cells from each row
|
|
178
|
+
const tableRows = [];
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
const cells = [];
|
|
181
|
+
for (const child of nodeListToArray(row.childNodes)) {
|
|
182
|
+
if (child.nodeType === 1 && child.nodeName === 'w:tc') {
|
|
183
|
+
cells.push(getCellText(child));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (cells.length > 0) {
|
|
187
|
+
tableRows.push(cells);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// First row might be header - check if it has bold formatting
|
|
191
|
+
// For simplicity, we'll treat first row as potential header
|
|
192
|
+
// User can determine this based on style or content
|
|
193
|
+
if (tableRows.length > 0) {
|
|
194
|
+
const firstRow = tableRows[0];
|
|
195
|
+
const restRows = tableRows.slice(1);
|
|
196
|
+
return {
|
|
197
|
+
headers: firstRow.length > 0 ? firstRow : undefined,
|
|
198
|
+
rows: restRows.length > 0 ? restRows : [],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { rows: tableRows };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get table style from w:tblPr/w:tblStyle/@w:val, or null if absent.
|
|
205
|
+
*/
|
|
206
|
+
export function getTableStyle(tbl) {
|
|
207
|
+
const tblPr = tbl.getElementsByTagName('w:tblPr').item(0);
|
|
208
|
+
if (!tblPr)
|
|
209
|
+
return null;
|
|
210
|
+
const tblStyle = tblPr.getElementsByTagName('w:tblStyle').item(0);
|
|
211
|
+
if (!tblStyle)
|
|
212
|
+
return null;
|
|
213
|
+
return tblStyle.getAttribute('w:val');
|
|
214
|
+
}
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
216
|
+
// Image reference extraction
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
218
|
+
/**
|
|
219
|
+
* Extract image reference from a w:drawing element.
|
|
220
|
+
* Returns the relationship ID (rId) and media file path if found.
|
|
221
|
+
*/
|
|
222
|
+
export function getImageReference(drawing) {
|
|
223
|
+
// Find a:blip/@r:embed to get the relationship ID
|
|
224
|
+
const blip = drawing.getElementsByTagName('a:blip').item(0);
|
|
225
|
+
if (!blip)
|
|
226
|
+
return { rId: null, mediaPath: null };
|
|
227
|
+
const rId = blip.getAttribute('r:embed');
|
|
228
|
+
if (!rId)
|
|
229
|
+
return { rId: null, mediaPath: null };
|
|
230
|
+
// Media path will be resolved from relationships file
|
|
231
|
+
// For now, return the rId - the caller will resolve it from rels
|
|
232
|
+
return { rId, mediaPath: null };
|
|
233
|
+
}
|
|
234
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
235
|
+
// Minimal text replacement
|
|
236
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
237
|
+
/**
|
|
238
|
+
* Replace the text of a paragraph with minimal DOM changes.
|
|
239
|
+
* Sets the FIRST w:t to `text`, clears every subsequent w:t.
|
|
240
|
+
* Sets xml:space="preserve" so leading/trailing spaces survive.
|
|
241
|
+
* Does NOT remove/recreate runs or remove paragraph properties.
|
|
242
|
+
*
|
|
243
|
+
* WARNING: This function does NOT preserve multiple runs with different styles.
|
|
244
|
+
* Use setParagraphTextPreservingStyles() for cells with multiple styled runs.
|
|
245
|
+
*/
|
|
246
|
+
export function setParagraphTextMinimal(p, text) {
|
|
247
|
+
const tNodes = p.getElementsByTagName('w:t');
|
|
248
|
+
if (tNodes.length === 0)
|
|
249
|
+
return;
|
|
250
|
+
const first = tNodes.item(0);
|
|
251
|
+
first.textContent = text;
|
|
252
|
+
first.setAttribute('xml:space', 'preserve');
|
|
253
|
+
for (let i = 1; i < tNodes.length; i++) {
|
|
254
|
+
tNodes.item(i).textContent = '';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Replace paragraph text while preserving all run styles.
|
|
259
|
+
*
|
|
260
|
+
* This function preserves the structure of all runs (w:r) and their
|
|
261
|
+
* properties (w:rPr), distributing the new text across existing runs.
|
|
262
|
+
*
|
|
263
|
+
* Strategy:
|
|
264
|
+
* 1. Collect all runs with their properties
|
|
265
|
+
* 2. Distribute new text across runs (preserving run count and styles)
|
|
266
|
+
* 3. If new text is longer, extend the last run
|
|
267
|
+
* 4. If new text is shorter, clear excess runs but keep their structure
|
|
268
|
+
*/
|
|
269
|
+
export function setParagraphTextPreservingStyles(p, text) {
|
|
270
|
+
const doc = p.ownerDocument;
|
|
271
|
+
if (!doc)
|
|
272
|
+
return;
|
|
273
|
+
// Work on ALL <w:t> descendants, not just direct children.
|
|
274
|
+
// This covers runs inside hyperlinks, smart tags, etc.
|
|
275
|
+
const tNodes = p.getElementsByTagName('w:t');
|
|
276
|
+
if (tNodes.length === 0) {
|
|
277
|
+
// No text nodes exist - create a minimal run + text.
|
|
278
|
+
const r = doc.createElement('w:r');
|
|
279
|
+
const t = doc.createElement('w:t');
|
|
280
|
+
t.setAttribute('xml:space', 'preserve');
|
|
281
|
+
t.textContent = text;
|
|
282
|
+
r.appendChild(t);
|
|
283
|
+
p.appendChild(r);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// First text node gets the NEW text.
|
|
287
|
+
const first = tNodes.item(0);
|
|
288
|
+
first.textContent = text;
|
|
289
|
+
first.setAttribute('xml:space', 'preserve');
|
|
290
|
+
// All other text nodes are cleared, but their runs (and w:rPr) remain,
|
|
291
|
+
// so formatting structures are preserved while old text disappears.
|
|
292
|
+
for (let i = 1; i < tNodes.length; i++) {
|
|
293
|
+
const t = tNodes.item(i);
|
|
294
|
+
if (t)
|
|
295
|
+
t.textContent = '';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Replace cell text while preserving ALL paragraphs and their styles.
|
|
300
|
+
*
|
|
301
|
+
* This function works at the cell level:
|
|
302
|
+
* - Preserves ALL paragraphs in the cell (doesn't remove any)
|
|
303
|
+
* - Updates text in the first paragraph while preserving its styles
|
|
304
|
+
* - Keeps all other paragraphs intact with their original text and styles
|
|
305
|
+
*
|
|
306
|
+
* This ensures that cells with multiple paragraphs (each with different
|
|
307
|
+
* styles, font sizes, etc.) maintain their structure after text replacement.
|
|
308
|
+
*
|
|
309
|
+
* Example: If a cell has:
|
|
310
|
+
* - Paragraph 1: "LAWN AND LANDSCAPE" (Heading1 style, large font, red color)
|
|
311
|
+
* - Paragraph 2: "Take your weekends back..." (Normal style, smaller font, black color)
|
|
312
|
+
*
|
|
313
|
+
* Replacing with "EARTH AND MOUNTAIN" will:
|
|
314
|
+
* - Update paragraph 1 to "EARTH AND MOUNTAIN" (preserving Heading1 style, large font, red color)
|
|
315
|
+
* - Keep paragraph 2 completely intact with its original text and style
|
|
316
|
+
*/
|
|
317
|
+
export function setCellTextPreservingStyles(tc, text) {
|
|
318
|
+
// Convert NodeList to array to avoid live NodeList issues
|
|
319
|
+
const paragraphs = nodeListToArray(tc.getElementsByTagName('w:p'));
|
|
320
|
+
if (paragraphs.length === 0) {
|
|
321
|
+
// Cell has no paragraphs - create one
|
|
322
|
+
const doc = tc.ownerDocument;
|
|
323
|
+
if (!doc)
|
|
324
|
+
return;
|
|
325
|
+
const p = doc.createElement('w:p');
|
|
326
|
+
const r = doc.createElement('w:r');
|
|
327
|
+
const t = doc.createElement('w:t');
|
|
328
|
+
t.setAttribute('xml:space', 'preserve');
|
|
329
|
+
t.textContent = text;
|
|
330
|
+
r.appendChild(t);
|
|
331
|
+
p.appendChild(r);
|
|
332
|
+
tc.appendChild(p);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
// Update first paragraph with new text, preserving all its styles
|
|
336
|
+
// This function preserves all runs and their properties (colors, bold, italic, etc.)
|
|
337
|
+
setParagraphTextPreservingStyles(paragraphs[0], text);
|
|
338
|
+
// CRITICAL: All other paragraphs remain completely untouched
|
|
339
|
+
// They keep their original text, styles, runs, and all formatting
|
|
340
|
+
// This ensures multi-paragraph cells maintain their full structure
|
|
341
|
+
}
|
|
342
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
343
|
+
// Run-level formatting helpers
|
|
344
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
345
|
+
/**
|
|
346
|
+
* Ensure a <w:r> element has w:rPr/w:color[@w:val=hex].
|
|
347
|
+
* Creates w:rPr and w:color if they don't exist.
|
|
348
|
+
* Only touches the colour — leaves every other run property intact.
|
|
349
|
+
*/
|
|
350
|
+
export function ensureRunColor(run, hex) {
|
|
351
|
+
const doc = run.ownerDocument;
|
|
352
|
+
if (!doc)
|
|
353
|
+
return;
|
|
354
|
+
let rPr = findDirectChild(run, 'w:rPr');
|
|
355
|
+
if (!rPr) {
|
|
356
|
+
rPr = doc.createElement('w:rPr');
|
|
357
|
+
if (run.firstChild) {
|
|
358
|
+
run.insertBefore(rPr, run.firstChild);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
run.appendChild(rPr);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
let colorEl = findDirectChild(rPr, 'w:color');
|
|
365
|
+
if (!colorEl) {
|
|
366
|
+
colorEl = doc.createElement('w:color');
|
|
367
|
+
rPr.appendChild(colorEl);
|
|
368
|
+
}
|
|
369
|
+
colorEl.setAttribute('w:val', hex);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Apply run-level colour to every <w:r> in a paragraph.
|
|
373
|
+
*/
|
|
374
|
+
export function colorParagraphRuns(p, color) {
|
|
375
|
+
const runs = nodeListToArray(p.getElementsByTagName('w:r'));
|
|
376
|
+
for (const r of runs) {
|
|
377
|
+
ensureRunColor(r, color);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Apply bold / italic / color to every <w:r> in a paragraph.
|
|
382
|
+
* Preserves all existing w:rPr children; only modifies specified props.
|
|
383
|
+
*/
|
|
384
|
+
export function styleParagraphRuns(p, style) {
|
|
385
|
+
const doc = p.ownerDocument;
|
|
386
|
+
if (!doc)
|
|
387
|
+
return;
|
|
388
|
+
const runs = nodeListToArray(p.getElementsByTagName('w:r'));
|
|
389
|
+
for (const r of runs) {
|
|
390
|
+
let rPr = findDirectChild(r, 'w:rPr');
|
|
391
|
+
if (!rPr) {
|
|
392
|
+
rPr = doc.createElement('w:rPr');
|
|
393
|
+
if (r.firstChild) {
|
|
394
|
+
r.insertBefore(rPr, r.firstChild);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
r.appendChild(rPr);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (style.color) {
|
|
401
|
+
let colorNode = findDirectChild(rPr, 'w:color');
|
|
402
|
+
if (!colorNode) {
|
|
403
|
+
colorNode = doc.createElement('w:color');
|
|
404
|
+
rPr.appendChild(colorNode);
|
|
405
|
+
}
|
|
406
|
+
colorNode.setAttribute('w:val', style.color);
|
|
407
|
+
}
|
|
408
|
+
if (style.bold !== undefined) {
|
|
409
|
+
toggleElement(doc, rPr, 'w:b', style.bold);
|
|
410
|
+
}
|
|
411
|
+
if (style.italic !== undefined) {
|
|
412
|
+
toggleElement(doc, rPr, 'w:i', style.italic);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
417
|
+
// Counting helpers
|
|
418
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
419
|
+
/** Count direct w:tbl children of body. */
|
|
420
|
+
export function countTables(children) {
|
|
421
|
+
return children.filter((ch) => ch.nodeName === 'w:tbl').length;
|
|
422
|
+
}
|
|
423
|
+
/** Count <w:drawing> descendants (rough image count). */
|
|
424
|
+
export function countImages(body) {
|
|
425
|
+
return body.getElementsByTagName('w:drawing').length;
|
|
426
|
+
}
|
|
427
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
428
|
+
// Private helpers (DRY: used by multiple public functions)
|
|
429
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
430
|
+
/** Find the first direct child element with the given nodeName. */
|
|
431
|
+
function findDirectChild(parent, nodeName) {
|
|
432
|
+
for (const child of nodeListToArray(parent.childNodes)) {
|
|
433
|
+
if (child.nodeType === 1 && child.nodeName === nodeName) {
|
|
434
|
+
return child;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
/** Add or remove a simple flag element (e.g. w:b, w:i) inside a parent. */
|
|
440
|
+
function toggleElement(doc, parent, nodeName, enabled) {
|
|
441
|
+
const existing = findDirectChild(parent, nodeName);
|
|
442
|
+
if (enabled && !existing) {
|
|
443
|
+
parent.appendChild(doc.createElement(nodeName));
|
|
444
|
+
}
|
|
445
|
+
else if (!enabled && existing) {
|
|
446
|
+
parent.removeChild(existing);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DOCX
|
|
3
|
-
*
|
|
4
|
-
* Re-exports only the symbols that external consumers need.
|
|
5
|
-
* Internal modules (styled-html-parser, validators, converters, etc.)
|
|
6
|
-
* are consumed by sibling files and are NOT part of the public surface.
|
|
7
|
-
*
|
|
8
|
-
* @module docx
|
|
2
|
+
* DOCX file manipulation tools — barrel exports.
|
|
9
3
|
*/
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export type {
|
|
14
|
-
export {
|
|
4
|
+
export { readDocxOutline } from './read.js';
|
|
5
|
+
export { writeDocxPatched } from './write.js';
|
|
6
|
+
export { createDocxNew } from './create.js';
|
|
7
|
+
export type { DocxContentStructure, DocxContentItem, DocxContentParagraph, DocxContentTable, DocxContentImage, DocxTableCellContent, } from './types.js';
|
|
8
|
+
export { readDocx, extractTextFromDocx, getDocxMetadata, extractBodyXml } from './read.js';
|
|
9
|
+
export { writeDocx, modifyDocxContent, replaceBodyXml } from './modify.js';
|
|
10
|
+
export type { DocxMetadata, DocxParagraph, DocxRun, DocxModification, ParagraphOutline, TableOutline, ImageOutline, ReadDocxResult, WriteDocxStats, WriteDocxResult, BodySnapshot, DocxOp, OpResult, } from './types.js';
|
package/dist/tools/docx/index.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DOCX
|
|
3
|
-
*
|
|
4
|
-
* Re-exports only the symbols that external consumers need.
|
|
5
|
-
* Internal modules (styled-html-parser, validators, converters, etc.)
|
|
6
|
-
* are consumed by sibling files and are NOT part of the public surface.
|
|
7
|
-
*
|
|
8
|
-
* @module docx
|
|
2
|
+
* DOCX file manipulation tools — barrel exports.
|
|
9
3
|
*/
|
|
10
|
-
//
|
|
11
|
-
export {
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export {
|
|
4
|
+
// Patch-based tools (read_docx / write_docx)
|
|
5
|
+
export { readDocxOutline } from './read.js';
|
|
6
|
+
export { writeDocxPatched } from './write.js';
|
|
7
|
+
export { createDocxNew } from './create.js';
|
|
8
|
+
// Legacy functions (used by read_file, write_file, edit_block handlers)
|
|
9
|
+
export { readDocx, extractTextFromDocx, getDocxMetadata, extractBodyXml } from './read.js';
|
|
10
|
+
export { writeDocx, modifyDocxContent, replaceBodyXml } from './modify.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy DOCX modification operations.
|
|
3
|
+
*
|
|
4
|
+
* These functions support the older write_file / edit_block paths that
|
|
5
|
+
* modify DOCX via simple operations (replace, insert, delete, style).
|
|
6
|
+
* They are distinct from the new patch-based writeDocxPatched pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Single Responsibility: create / modify DOCX content using the legacy
|
|
9
|
+
* DocxModification interface. Delegates XML parsing and element
|
|
10
|
+
* manipulation to the shared dom.ts module.
|
|
11
|
+
*/
|
|
12
|
+
import type { DocxModification } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Open an existing DOCX, apply an ordered list of modifications to
|
|
15
|
+
* word/document.xml, and write the result to outputPath.
|
|
16
|
+
* Every other file in the ZIP (styles, images, rels, …) is preserved.
|
|
17
|
+
*/
|
|
18
|
+
export declare function modifyDocxContent(inputPath: string, outputPath: string, modifications: DocxModification[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Replace the entire w:body content of a DOCX with new body XML.
|
|
21
|
+
* Used by the body-XML replacement mode of write_file.
|
|
22
|
+
*/
|
|
23
|
+
export declare function replaceBodyXml(inputPath: string, outputPath: string, newBodyXml: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Create a brand-new minimal DOCX from a plain-text string.
|
|
26
|
+
* Double-newlines are treated as paragraph separators.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeDocx(outputPath: string, content: string | DocxModification[]): Promise<void>;
|