@yinyoudexing/xml2word 0.1.0 → 0.1.2
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/dist/createDocxZip-BWHSZ7VQ.js +500 -0
- package/dist/createDocxZip-BWHSZ7VQ.js.map +1 -0
- package/dist/htmlToWordBodyXml-LY6DZSTW.js +877 -0
- package/dist/htmlToWordBodyXml-LY6DZSTW.js.map +1 -0
- package/dist/index.cjs +947 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +30 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/createDocxZip-WVDRDYZT.js +0 -109
- package/dist/createDocxZip-WVDRDYZT.js.map +0 -1
- package/dist/htmlToWordBodyXml-RFBPSL2Q.js +0 -416
- package/dist/htmlToWordBodyXml-RFBPSL2Q.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,8 @@ function wrapBodyXml(bodyXml) {
|
|
|
48
48
|
<w:body>
|
|
49
49
|
${bodyXml}
|
|
50
50
|
<w:sectPr>
|
|
51
|
+
<w:headerReference w:type="default" r:id="rId6"/>
|
|
52
|
+
<w:footerReference w:type="default" r:id="rId7"/>
|
|
51
53
|
<w:pgSz w:w="12240" w:h="15840"/>
|
|
52
54
|
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
|
|
53
55
|
<w:cols w:space="708"/>
|
|
@@ -121,32 +123,421 @@ var init_validateXml = __esm({
|
|
|
121
123
|
// src/lib/createDocxZip.ts
|
|
122
124
|
var createDocxZip_exports = {};
|
|
123
125
|
__export(createDocxZip_exports, {
|
|
124
|
-
createDocxZipUint8Array: () => createDocxZipUint8Array
|
|
126
|
+
createDocxZipUint8Array: () => createDocxZipUint8Array,
|
|
127
|
+
createDocxZipWithAssetsUint8Array: () => createDocxZipWithAssetsUint8Array
|
|
125
128
|
});
|
|
129
|
+
function buildContentTypesXml(assets) {
|
|
130
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
131
|
+
defaults.set("rels", "application/vnd.openxmlformats-package.relationships+xml");
|
|
132
|
+
defaults.set("xml", "application/xml");
|
|
133
|
+
for (const asset of assets) {
|
|
134
|
+
if (!asset.contentType) continue;
|
|
135
|
+
const extMatch = asset.target.match(/\.([a-zA-Z0-9]+)$/);
|
|
136
|
+
if (!extMatch) continue;
|
|
137
|
+
const ext = extMatch[1].toLowerCase();
|
|
138
|
+
defaults.set(ext, asset.contentType);
|
|
139
|
+
}
|
|
140
|
+
const defaultLines = [...defaults.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([ext, ct]) => ` <Default Extension="${ext}" ContentType="${ct}"/>`).join("\n");
|
|
141
|
+
const overrides = [
|
|
142
|
+
' <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>',
|
|
143
|
+
' <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>',
|
|
144
|
+
' <Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>',
|
|
145
|
+
' <Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>',
|
|
146
|
+
' <Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>',
|
|
147
|
+
' <Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>',
|
|
148
|
+
' <Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>',
|
|
149
|
+
' <Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>'
|
|
150
|
+
].join("\n");
|
|
151
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
152
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
153
|
+
${defaultLines}
|
|
154
|
+
${overrides}
|
|
155
|
+
</Types>
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
function buildDocumentRelsXml(assets) {
|
|
159
|
+
const relLines = [
|
|
160
|
+
' <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>',
|
|
161
|
+
' <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>',
|
|
162
|
+
' <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>',
|
|
163
|
+
' <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>',
|
|
164
|
+
' <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>',
|
|
165
|
+
' <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>',
|
|
166
|
+
' <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>',
|
|
167
|
+
...assets.map(
|
|
168
|
+
(a) => ` <Relationship Id="${a.relationshipId}" Type="${a.relationshipType}" Target="${a.target}"/>`
|
|
169
|
+
)
|
|
170
|
+
].join("\n");
|
|
171
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
172
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
173
|
+
${relLines}
|
|
174
|
+
</Relationships>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
126
177
|
async function createDocxZipUint8Array(xml, options = {}) {
|
|
127
178
|
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
|
|
128
179
|
validateXmlIfNeeded(documentXml, options.validateXml ?? true);
|
|
129
180
|
const zip = new import_jszip.default();
|
|
130
|
-
zip.file("[Content_Types].xml",
|
|
181
|
+
zip.file("[Content_Types].xml", buildContentTypesXml([]));
|
|
131
182
|
const relsFolder = zip.folder("_rels");
|
|
132
183
|
relsFolder?.file(".rels", ROOT_RELS_XML);
|
|
133
184
|
const wordFolder = zip.folder("word");
|
|
134
185
|
wordFolder?.file("document.xml", documentXml);
|
|
186
|
+
wordFolder?.file("styles.xml", STYLES_XML);
|
|
187
|
+
wordFolder?.file("settings.xml", SETTINGS_XML);
|
|
188
|
+
wordFolder?.file("fontTable.xml", FONT_TABLE_XML);
|
|
189
|
+
wordFolder?.folder("theme")?.file("theme1.xml", THEME_XML);
|
|
190
|
+
wordFolder?.file("numbering.xml", NUMBERING_XML);
|
|
191
|
+
wordFolder?.file("header1.xml", HEADER1_XML);
|
|
192
|
+
wordFolder?.file("footer1.xml", FOOTER1_XML);
|
|
193
|
+
const wordRelsFolder = wordFolder?.folder("_rels");
|
|
194
|
+
wordRelsFolder?.file("document.xml.rels", buildDocumentRelsXml([]));
|
|
135
195
|
return zip.generateAsync({ type: "uint8array" });
|
|
136
196
|
}
|
|
137
|
-
|
|
197
|
+
async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
|
|
198
|
+
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
|
|
199
|
+
validateXmlIfNeeded(documentXml, options.validateXml ?? true);
|
|
200
|
+
const zip = new import_jszip.default();
|
|
201
|
+
zip.file("[Content_Types].xml", buildContentTypesXml(assets));
|
|
202
|
+
const relsFolder = zip.folder("_rels");
|
|
203
|
+
relsFolder?.file(".rels", ROOT_RELS_XML);
|
|
204
|
+
const wordFolder = zip.folder("word");
|
|
205
|
+
wordFolder?.file("document.xml", documentXml);
|
|
206
|
+
wordFolder?.file("styles.xml", STYLES_XML);
|
|
207
|
+
wordFolder?.file("settings.xml", SETTINGS_XML);
|
|
208
|
+
wordFolder?.file("fontTable.xml", FONT_TABLE_XML);
|
|
209
|
+
wordFolder?.folder("theme")?.file("theme1.xml", THEME_XML);
|
|
210
|
+
wordFolder?.file("numbering.xml", NUMBERING_XML);
|
|
211
|
+
wordFolder?.file("header1.xml", HEADER1_XML);
|
|
212
|
+
wordFolder?.file("footer1.xml", FOOTER1_XML);
|
|
213
|
+
const wordRelsFolder = wordFolder?.folder("_rels");
|
|
214
|
+
wordRelsFolder?.file("document.xml.rels", buildDocumentRelsXml(assets));
|
|
215
|
+
if (assets.length) {
|
|
216
|
+
for (const asset of assets) {
|
|
217
|
+
const targetPath = asset.target.replace(/^\.\//, "");
|
|
218
|
+
const normalized = targetPath.startsWith("word/") ? targetPath.slice("word/".length) : targetPath;
|
|
219
|
+
wordFolder?.file(normalized, asset.data);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return zip.generateAsync({ type: "uint8array" });
|
|
223
|
+
}
|
|
224
|
+
var import_jszip, STYLES_XML, SETTINGS_XML, NUMBERING_XML, HEADER1_XML, FOOTER1_XML, THEME_XML, FONT_TABLE_XML, ROOT_RELS_XML;
|
|
138
225
|
var init_createDocxZip = __esm({
|
|
139
226
|
"src/lib/createDocxZip.ts"() {
|
|
140
227
|
"use strict";
|
|
141
228
|
import_jszip = __toESM(require("jszip"), 1);
|
|
142
229
|
init_normalizeDocumentXml();
|
|
143
230
|
init_validateXml();
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
231
|
+
STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
232
|
+
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
233
|
+
<w:docDefaults>
|
|
234
|
+
<w:rPrDefault>
|
|
235
|
+
<w:rPr>
|
|
236
|
+
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/>
|
|
237
|
+
<w:sz w:val="28"/>
|
|
238
|
+
<w:szCs w:val="28"/>
|
|
239
|
+
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
|
|
240
|
+
</w:rPr>
|
|
241
|
+
</w:rPrDefault>
|
|
242
|
+
<w:pPrDefault>
|
|
243
|
+
<w:pPr>
|
|
244
|
+
<w:spacing w:before="0" w:after="160" w:line="360" w:lineRule="auto"/>
|
|
245
|
+
</w:pPr>
|
|
246
|
+
</w:pPrDefault>
|
|
247
|
+
</w:docDefaults>
|
|
248
|
+
|
|
249
|
+
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
|
|
250
|
+
<w:name w:val="Normal"/>
|
|
251
|
+
<w:qFormat/>
|
|
252
|
+
<w:pPr>
|
|
253
|
+
<w:spacing w:before="0" w:after="160" w:line="360" w:lineRule="auto"/>
|
|
254
|
+
</w:pPr>
|
|
255
|
+
<w:rPr>
|
|
256
|
+
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/>
|
|
257
|
+
<w:sz w:val="28"/>
|
|
258
|
+
<w:szCs w:val="28"/>
|
|
259
|
+
</w:rPr>
|
|
260
|
+
</w:style>
|
|
261
|
+
|
|
262
|
+
<w:style w:type="paragraph" w:styleId="Heading1">
|
|
263
|
+
<w:name w:val="heading 1"/>
|
|
264
|
+
<w:basedOn w:val="Normal"/>
|
|
265
|
+
<w:next w:val="Normal"/>
|
|
266
|
+
<w:uiPriority w:val="9"/>
|
|
267
|
+
<w:qFormat/>
|
|
268
|
+
<w:pPr>
|
|
269
|
+
<w:keepNext/>
|
|
270
|
+
<w:keepLines/>
|
|
271
|
+
<w:outlineLvl w:val="0"/>
|
|
272
|
+
<w:spacing w:before="240" w:after="120" w:line="360" w:lineRule="auto"/>
|
|
273
|
+
</w:pPr>
|
|
274
|
+
<w:rPr>
|
|
275
|
+
<w:b/>
|
|
276
|
+
<w:sz w:val="44"/>
|
|
277
|
+
<w:szCs w:val="44"/>
|
|
278
|
+
</w:rPr>
|
|
279
|
+
</w:style>
|
|
280
|
+
|
|
281
|
+
<w:style w:type="paragraph" w:styleId="Heading2">
|
|
282
|
+
<w:name w:val="heading 2"/>
|
|
283
|
+
<w:basedOn w:val="Normal"/>
|
|
284
|
+
<w:next w:val="Normal"/>
|
|
285
|
+
<w:uiPriority w:val="9"/>
|
|
286
|
+
<w:qFormat/>
|
|
287
|
+
<w:pPr>
|
|
288
|
+
<w:keepNext/>
|
|
289
|
+
<w:keepLines/>
|
|
290
|
+
<w:outlineLvl w:val="1"/>
|
|
291
|
+
<w:spacing w:before="200" w:after="100" w:line="360" w:lineRule="auto"/>
|
|
292
|
+
</w:pPr>
|
|
293
|
+
<w:rPr>
|
|
294
|
+
<w:b/>
|
|
295
|
+
<w:sz w:val="32"/>
|
|
296
|
+
<w:szCs w:val="32"/>
|
|
297
|
+
</w:rPr>
|
|
298
|
+
</w:style>
|
|
299
|
+
|
|
300
|
+
<w:style w:type="paragraph" w:styleId="Heading3">
|
|
301
|
+
<w:name w:val="heading 3"/>
|
|
302
|
+
<w:basedOn w:val="Normal"/>
|
|
303
|
+
<w:next w:val="Normal"/>
|
|
304
|
+
<w:uiPriority w:val="9"/>
|
|
305
|
+
<w:qFormat/>
|
|
306
|
+
<w:pPr>
|
|
307
|
+
<w:keepNext/>
|
|
308
|
+
<w:keepLines/>
|
|
309
|
+
<w:outlineLvl w:val="2"/>
|
|
310
|
+
<w:spacing w:before="180" w:after="90" w:line="360" w:lineRule="auto"/>
|
|
311
|
+
</w:pPr>
|
|
312
|
+
<w:rPr>
|
|
313
|
+
<w:b/>
|
|
314
|
+
<w:sz w:val="28"/>
|
|
315
|
+
<w:szCs w:val="28"/>
|
|
316
|
+
</w:rPr>
|
|
317
|
+
</w:style>
|
|
318
|
+
|
|
319
|
+
<w:style w:type="paragraph" w:styleId="Heading4">
|
|
320
|
+
<w:name w:val="heading 4"/>
|
|
321
|
+
<w:basedOn w:val="Normal"/>
|
|
322
|
+
<w:next w:val="Normal"/>
|
|
323
|
+
<w:uiPriority w:val="9"/>
|
|
324
|
+
<w:qFormat/>
|
|
325
|
+
<w:pPr>
|
|
326
|
+
<w:keepNext/>
|
|
327
|
+
<w:keepLines/>
|
|
328
|
+
<w:outlineLvl w:val="3"/>
|
|
329
|
+
<w:spacing w:before="160" w:after="80" w:line="360" w:lineRule="auto"/>
|
|
330
|
+
</w:pPr>
|
|
331
|
+
<w:rPr>
|
|
332
|
+
<w:b/>
|
|
333
|
+
<w:sz w:val="24"/>
|
|
334
|
+
<w:szCs w:val="24"/>
|
|
335
|
+
</w:rPr>
|
|
336
|
+
</w:style>
|
|
337
|
+
|
|
338
|
+
<w:style w:type="paragraph" w:styleId="Heading5">
|
|
339
|
+
<w:name w:val="heading 5"/>
|
|
340
|
+
<w:basedOn w:val="Normal"/>
|
|
341
|
+
<w:next w:val="Normal"/>
|
|
342
|
+
<w:uiPriority w:val="9"/>
|
|
343
|
+
<w:qFormat/>
|
|
344
|
+
<w:pPr>
|
|
345
|
+
<w:keepNext/>
|
|
346
|
+
<w:keepLines/>
|
|
347
|
+
<w:outlineLvl w:val="4"/>
|
|
348
|
+
<w:spacing w:before="140" w:after="70" w:line="360" w:lineRule="auto"/>
|
|
349
|
+
</w:pPr>
|
|
350
|
+
<w:rPr>
|
|
351
|
+
<w:b/>
|
|
352
|
+
<w:sz w:val="22"/>
|
|
353
|
+
<w:szCs w:val="22"/>
|
|
354
|
+
</w:rPr>
|
|
355
|
+
</w:style>
|
|
356
|
+
|
|
357
|
+
<w:style w:type="paragraph" w:styleId="Heading6">
|
|
358
|
+
<w:name w:val="heading 6"/>
|
|
359
|
+
<w:basedOn w:val="Normal"/>
|
|
360
|
+
<w:next w:val="Normal"/>
|
|
361
|
+
<w:uiPriority w:val="9"/>
|
|
362
|
+
<w:qFormat/>
|
|
363
|
+
<w:pPr>
|
|
364
|
+
<w:keepNext/>
|
|
365
|
+
<w:keepLines/>
|
|
366
|
+
<w:outlineLvl w:val="5"/>
|
|
367
|
+
<w:spacing w:before="120" w:after="60" w:line="360" w:lineRule="auto"/>
|
|
368
|
+
</w:pPr>
|
|
369
|
+
<w:rPr>
|
|
370
|
+
<w:b/>
|
|
371
|
+
<w:sz w:val="22"/>
|
|
372
|
+
<w:szCs w:val="22"/>
|
|
373
|
+
</w:rPr>
|
|
374
|
+
</w:style>
|
|
375
|
+
</w:styles>
|
|
376
|
+
`;
|
|
377
|
+
SETTINGS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
378
|
+
<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
379
|
+
<w:themeFontLang w:val="en-US" w:eastAsia="zh-CN"/>
|
|
380
|
+
<w:defaultTabStop w:val="720"/>
|
|
381
|
+
<w:compat/>
|
|
382
|
+
</w:settings>
|
|
383
|
+
`;
|
|
384
|
+
NUMBERING_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
385
|
+
<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
386
|
+
<w:abstractNum w:abstractNumId="1">
|
|
387
|
+
<w:multiLevelType w:val="hybridMultilevel"/>
|
|
388
|
+
<w:lvl w:ilvl="0">
|
|
389
|
+
<w:start w:val="1"/>
|
|
390
|
+
<w:numFmt w:val="bullet"/>
|
|
391
|
+
<w:lvlText w:val="\u2022"/>
|
|
392
|
+
<w:lvlJc w:val="left"/>
|
|
393
|
+
<w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>
|
|
394
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
395
|
+
</w:lvl>
|
|
396
|
+
<w:lvl w:ilvl="1">
|
|
397
|
+
<w:start w:val="1"/>
|
|
398
|
+
<w:numFmt w:val="bullet"/>
|
|
399
|
+
<w:lvlText w:val="\u25CB"/>
|
|
400
|
+
<w:lvlJc w:val="left"/>
|
|
401
|
+
<w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr>
|
|
402
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
403
|
+
</w:lvl>
|
|
404
|
+
<w:lvl w:ilvl="2">
|
|
405
|
+
<w:start w:val="1"/>
|
|
406
|
+
<w:numFmt w:val="bullet"/>
|
|
407
|
+
<w:lvlText w:val="\u25A0"/>
|
|
408
|
+
<w:lvlJc w:val="left"/>
|
|
409
|
+
<w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr>
|
|
410
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
411
|
+
</w:lvl>
|
|
412
|
+
</w:abstractNum>
|
|
413
|
+
<w:abstractNum w:abstractNumId="2">
|
|
414
|
+
<w:multiLevelType w:val="hybridMultilevel"/>
|
|
415
|
+
<w:lvl w:ilvl="0">
|
|
416
|
+
<w:start w:val="1"/>
|
|
417
|
+
<w:numFmt w:val="decimal"/>
|
|
418
|
+
<w:lvlText w:val="%1."/>
|
|
419
|
+
<w:lvlJc w:val="left"/>
|
|
420
|
+
<w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>
|
|
421
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
422
|
+
</w:lvl>
|
|
423
|
+
<w:lvl w:ilvl="1">
|
|
424
|
+
<w:start w:val="1"/>
|
|
425
|
+
<w:numFmt w:val="decimal"/>
|
|
426
|
+
<w:lvlText w:val="%1.%2."/>
|
|
427
|
+
<w:lvlJc w:val="left"/>
|
|
428
|
+
<w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr>
|
|
429
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
430
|
+
</w:lvl>
|
|
431
|
+
<w:lvl w:ilvl="2">
|
|
432
|
+
<w:start w:val="1"/>
|
|
433
|
+
<w:numFmt w:val="decimal"/>
|
|
434
|
+
<w:lvlText w:val="%1.%2.%3."/>
|
|
435
|
+
<w:lvlJc w:val="left"/>
|
|
436
|
+
<w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr>
|
|
437
|
+
<w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
|
|
438
|
+
</w:lvl>
|
|
439
|
+
</w:abstractNum>
|
|
440
|
+
<w:num w:numId="1"><w:abstractNumId w:val="1"/></w:num>
|
|
441
|
+
<w:num w:numId="2"><w:abstractNumId w:val="2"/></w:num>
|
|
442
|
+
</w:numbering>
|
|
443
|
+
`;
|
|
444
|
+
HEADER1_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
445
|
+
<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
446
|
+
<w:p>
|
|
447
|
+
<w:pPr><w:jc w:val="center"/></w:pPr>
|
|
448
|
+
<w:r><w:t></w:t></w:r>
|
|
449
|
+
</w:p>
|
|
450
|
+
</w:hdr>
|
|
451
|
+
`;
|
|
452
|
+
FOOTER1_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
453
|
+
<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
454
|
+
<w:p>
|
|
455
|
+
<w:pPr><w:jc w:val="right"/></w:pPr>
|
|
456
|
+
<w:r><w:t>\u7B2C </w:t></w:r>
|
|
457
|
+
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
|
|
458
|
+
<w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
|
|
459
|
+
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
|
|
460
|
+
<w:r><w:t>1</w:t></w:r>
|
|
461
|
+
<w:r><w:fldChar w:fldCharType="end"/></w:r>
|
|
462
|
+
<w:r><w:t> \u9875 / \u5171 </w:t></w:r>
|
|
463
|
+
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
|
|
464
|
+
<w:r><w:instrText xml:space="preserve"> NUMPAGES </w:instrText></w:r>
|
|
465
|
+
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
|
|
466
|
+
<w:r><w:t>1</w:t></w:r>
|
|
467
|
+
<w:r><w:fldChar w:fldCharType="end"/></w:r>
|
|
468
|
+
<w:r><w:t> \u9875</w:t></w:r>
|
|
469
|
+
</w:p>
|
|
470
|
+
</w:ftr>
|
|
471
|
+
`;
|
|
472
|
+
THEME_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
473
|
+
<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
|
|
474
|
+
<a:themeElements>
|
|
475
|
+
<a:clrScheme name="Office">
|
|
476
|
+
<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
|
|
477
|
+
<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
|
|
478
|
+
<a:dk2><a:srgbClr val="1F497D"/></a:dk2>
|
|
479
|
+
<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
|
|
480
|
+
<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
|
|
481
|
+
<a:accent2><a:srgbClr val="C0504D"/></a:accent2>
|
|
482
|
+
<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
|
|
483
|
+
<a:accent4><a:srgbClr val="8064A2"/></a:accent4>
|
|
484
|
+
<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
|
|
485
|
+
<a:accent6><a:srgbClr val="F79646"/></a:accent6>
|
|
486
|
+
<a:hlink><a:srgbClr val="0000FF"/></a:hlink>
|
|
487
|
+
<a:folHlink><a:srgbClr val="800080"/></a:folHlink>
|
|
488
|
+
</a:clrScheme>
|
|
489
|
+
<a:fontScheme name="Office">
|
|
490
|
+
<a:majorFont>
|
|
491
|
+
<a:latin typeface="Times New Roman"/>
|
|
492
|
+
<a:ea typeface="FangSong_GB2312"/>
|
|
493
|
+
<a:cs typeface="Times New Roman"/>
|
|
494
|
+
</a:majorFont>
|
|
495
|
+
<a:minorFont>
|
|
496
|
+
<a:latin typeface="Times New Roman"/>
|
|
497
|
+
<a:ea typeface="FangSong_GB2312"/>
|
|
498
|
+
<a:cs typeface="Times New Roman"/>
|
|
499
|
+
</a:minorFont>
|
|
500
|
+
</a:fontScheme>
|
|
501
|
+
<a:fmtScheme name="Office">
|
|
502
|
+
<a:fillStyleLst>
|
|
503
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
|
504
|
+
<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"/></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"/></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>
|
|
505
|
+
<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"/></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"/></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>
|
|
506
|
+
</a:fillStyleLst>
|
|
507
|
+
<a:lnStyleLst>
|
|
508
|
+
<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
|
|
509
|
+
<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
|
|
510
|
+
<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
|
|
511
|
+
</a:lnStyleLst>
|
|
512
|
+
<a:effectStyleLst>
|
|
513
|
+
<a:effectStyle><a:effectLst/></a:effectStyle>
|
|
514
|
+
<a:effectStyle><a:effectLst/></a:effectStyle>
|
|
515
|
+
<a:effectStyle><a:effectLst/></a:effectStyle>
|
|
516
|
+
</a:effectStyleLst>
|
|
517
|
+
<a:bgFillStyleLst>
|
|
518
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
|
519
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
|
520
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
|
521
|
+
</a:bgFillStyleLst>
|
|
522
|
+
</a:fmtScheme>
|
|
523
|
+
</a:themeElements>
|
|
524
|
+
<a:objectDefaults/>
|
|
525
|
+
<a:extraClrSchemeLst/>
|
|
526
|
+
</a:theme>
|
|
527
|
+
`;
|
|
528
|
+
FONT_TABLE_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
529
|
+
<w:fonts xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
530
|
+
<w:font w:name="Times New Roman">
|
|
531
|
+
<w:charset w:val="00"/>
|
|
532
|
+
<w:family w:val="roman"/>
|
|
533
|
+
<w:pitch w:val="variable"/>
|
|
534
|
+
</w:font>
|
|
535
|
+
<w:font w:name="FangSong_GB2312">
|
|
536
|
+
<w:charset w:val="86"/>
|
|
537
|
+
<w:family w:val="auto"/>
|
|
538
|
+
<w:pitch w:val="variable"/>
|
|
539
|
+
</w:font>
|
|
540
|
+
</w:fonts>
|
|
150
541
|
`;
|
|
151
542
|
ROOT_RELS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
152
543
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
@@ -161,6 +552,7 @@ var init_createDocxZip = __esm({
|
|
|
161
552
|
// src/lib/htmlToWordBodyXml.ts
|
|
162
553
|
var htmlToWordBodyXml_exports = {};
|
|
163
554
|
__export(htmlToWordBodyXml_exports, {
|
|
555
|
+
htmlToWordBodyWithAssets: () => htmlToWordBodyWithAssets,
|
|
164
556
|
htmlToWordBodyXml: () => htmlToWordBodyXml,
|
|
165
557
|
textToWordBodyXml: () => textToWordBodyXml
|
|
166
558
|
});
|
|
@@ -171,6 +563,12 @@ function shouldPreserveSpace(text) {
|
|
|
171
563
|
if (!text) return false;
|
|
172
564
|
return /^\s/.test(text) || /\s$/.test(text) || /\s{2,}/.test(text);
|
|
173
565
|
}
|
|
566
|
+
function shouldKeepWhitespaceOnlyRun(text) {
|
|
567
|
+
if (!text) return false;
|
|
568
|
+
if (/\r|\n/.test(text)) return false;
|
|
569
|
+
if (text.includes("\xA0")) return true;
|
|
570
|
+
return /\s{2,}/.test(text);
|
|
571
|
+
}
|
|
174
572
|
function parseStyleAttribute(style) {
|
|
175
573
|
if (!style) return {};
|
|
176
574
|
const normalized = style.replace(/\r/g, "\n");
|
|
@@ -270,7 +668,107 @@ function getTextContent(node) {
|
|
|
270
668
|
for (const c of children) out += getTextContent(c);
|
|
271
669
|
return out;
|
|
272
670
|
}
|
|
273
|
-
function
|
|
671
|
+
function decodeBase64ToUint8Array(base64) {
|
|
672
|
+
const BufferCtor = globalThis.Buffer;
|
|
673
|
+
if (BufferCtor) {
|
|
674
|
+
return new Uint8Array(BufferCtor.from(base64, "base64"));
|
|
675
|
+
}
|
|
676
|
+
const atobFn = globalThis.atob;
|
|
677
|
+
if (!atobFn) {
|
|
678
|
+
throw new Error("Base64 decode is not available in this environment.");
|
|
679
|
+
}
|
|
680
|
+
const bin = atobFn(base64);
|
|
681
|
+
const bytes = new Uint8Array(bin.length);
|
|
682
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
683
|
+
return bytes;
|
|
684
|
+
}
|
|
685
|
+
function parseImageDataUrl(src) {
|
|
686
|
+
const m = src.match(/^data:(image\/png|image\/jpeg);base64,([\s\S]+)$/i);
|
|
687
|
+
if (!m) return void 0;
|
|
688
|
+
const contentType = m[1].toLowerCase();
|
|
689
|
+
const base64 = m[2].replace(/\s+/g, "");
|
|
690
|
+
const data = decodeBase64ToUint8Array(base64);
|
|
691
|
+
const extension = contentType === "image/png" ? "png" : "jpeg";
|
|
692
|
+
return { contentType, data, extension };
|
|
693
|
+
}
|
|
694
|
+
function parseCssLengthToPx(value) {
|
|
695
|
+
if (!value) return void 0;
|
|
696
|
+
const v = value.trim().toLowerCase();
|
|
697
|
+
const px = v.match(/^(\d+(?:\.\d+)?)px$/);
|
|
698
|
+
if (px) return Math.max(1, Math.round(Number(px[1])));
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
function readUInt32BE(bytes, offset) {
|
|
702
|
+
if (offset < 0 || offset + 4 > bytes.length) return void 0;
|
|
703
|
+
return ((bytes[offset] ?? 0) << 24 | (bytes[offset + 1] ?? 0) << 16 | (bytes[offset + 2] ?? 0) << 8 | (bytes[offset + 3] ?? 0)) >>> 0;
|
|
704
|
+
}
|
|
705
|
+
function parsePngDimensions(data) {
|
|
706
|
+
if (data.length < 24) return void 0;
|
|
707
|
+
const signature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
708
|
+
for (let i = 0; i < signature.length; i++) {
|
|
709
|
+
if ((data[i] ?? 0) !== signature[i]) return void 0;
|
|
710
|
+
}
|
|
711
|
+
const widthPx = readUInt32BE(data, 16);
|
|
712
|
+
const heightPx = readUInt32BE(data, 20);
|
|
713
|
+
if (!widthPx || !heightPx) return void 0;
|
|
714
|
+
return { widthPx, heightPx };
|
|
715
|
+
}
|
|
716
|
+
function parseJpegDimensions(data) {
|
|
717
|
+
if (data.length < 4) return void 0;
|
|
718
|
+
if (data[0] !== 255 || data[1] !== 216) return void 0;
|
|
719
|
+
let offset = 2;
|
|
720
|
+
while (offset + 4 <= data.length) {
|
|
721
|
+
if (data[offset] !== 255) {
|
|
722
|
+
offset++;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
while (offset < data.length && data[offset] === 255) offset++;
|
|
726
|
+
if (offset >= data.length) return void 0;
|
|
727
|
+
const marker = data[offset];
|
|
728
|
+
offset++;
|
|
729
|
+
const isStandalone = marker === 217 || marker === 218;
|
|
730
|
+
if (isStandalone) break;
|
|
731
|
+
if (offset + 2 > data.length) return void 0;
|
|
732
|
+
const length = data[offset] << 8 | data[offset + 1];
|
|
733
|
+
if (length < 2 || offset + length > data.length) return void 0;
|
|
734
|
+
const isSof = marker === 192 || marker === 193 || marker === 194 || marker === 195 || marker === 197 || marker === 198 || marker === 199 || marker === 201 || marker === 202 || marker === 203 || marker === 205 || marker === 206 || marker === 207;
|
|
735
|
+
if (isSof) {
|
|
736
|
+
if (offset + 7 > data.length) return void 0;
|
|
737
|
+
const heightPx = data[offset + 3] << 8 | data[offset + 4];
|
|
738
|
+
const widthPx = data[offset + 5] << 8 | data[offset + 6];
|
|
739
|
+
if (!widthPx || !heightPx) return void 0;
|
|
740
|
+
return { widthPx, heightPx };
|
|
741
|
+
}
|
|
742
|
+
offset += length;
|
|
743
|
+
}
|
|
744
|
+
return void 0;
|
|
745
|
+
}
|
|
746
|
+
function parseIntrinsicImageSizePx(contentType, data) {
|
|
747
|
+
if (contentType === "image/png") return parsePngDimensions(data);
|
|
748
|
+
if (contentType === "image/jpeg") return parseJpegDimensions(data);
|
|
749
|
+
return void 0;
|
|
750
|
+
}
|
|
751
|
+
function applyMaxBoxPx(size, maxBox) {
|
|
752
|
+
const w = Math.max(1, Math.round(size.widthPx));
|
|
753
|
+
const h = Math.max(1, Math.round(size.heightPx));
|
|
754
|
+
const scale = Math.min(1, maxBox.maxWidthPx / w, maxBox.maxHeightPx / h);
|
|
755
|
+
return { widthPx: Math.max(1, Math.round(w * scale)), heightPx: Math.max(1, Math.round(h * scale)) };
|
|
756
|
+
}
|
|
757
|
+
function computeImageSizePx(node, intrinsic) {
|
|
758
|
+
const wAttr = node.attribs?.width ? Number(node.attribs.width) : void 0;
|
|
759
|
+
const hAttr = node.attribs?.height ? Number(node.attribs.height) : void 0;
|
|
760
|
+
const css = parseStyleAttribute(node.attribs?.style);
|
|
761
|
+
const wCss = parseCssLengthToPx(css.width);
|
|
762
|
+
const hCss = parseCssLengthToPx(css.height);
|
|
763
|
+
const widthAttrPx = Number.isFinite(wAttr) && wAttr ? Math.max(1, Math.round(wAttr)) : void 0;
|
|
764
|
+
const heightAttrPx = Number.isFinite(hAttr) && hAttr ? Math.max(1, Math.round(hAttr)) : void 0;
|
|
765
|
+
const ratio = intrinsic && intrinsic.widthPx > 0 && intrinsic.heightPx > 0 ? intrinsic.heightPx / intrinsic.widthPx : widthAttrPx && heightAttrPx ? heightAttrPx / widthAttrPx : 0.5;
|
|
766
|
+
const widthPx = typeof wCss === "number" ? wCss : typeof widthAttrPx === "number" ? widthAttrPx : intrinsic?.widthPx ?? 300;
|
|
767
|
+
const heightPx = typeof hCss === "number" ? hCss : typeof heightAttrPx === "number" ? heightAttrPx : intrinsic?.heightPx ?? 150;
|
|
768
|
+
const finalSize = typeof wCss === "number" && typeof hCss !== "number" ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof hCss === "number" && typeof wCss !== "number" ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : typeof widthAttrPx === "number" && typeof heightAttrPx !== "number" && intrinsic ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof heightAttrPx === "number" && typeof widthAttrPx !== "number" && intrinsic ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : { widthPx, heightPx };
|
|
769
|
+
return applyMaxBoxPx(finalSize, { maxWidthPx: 624, maxHeightPx: 864 });
|
|
770
|
+
}
|
|
771
|
+
function collectInlineRuns(node, inherited, out, result) {
|
|
274
772
|
if (node.type === "text") {
|
|
275
773
|
const text = node.data ?? "";
|
|
276
774
|
if (text) out.push({ kind: "text", text, style: inherited });
|
|
@@ -282,13 +780,57 @@ function collectInlineRuns(node, inherited, out) {
|
|
|
282
780
|
out.push({ kind: "br" });
|
|
283
781
|
return;
|
|
284
782
|
}
|
|
783
|
+
if (tag === "img") {
|
|
784
|
+
const src = node.attribs?.src;
|
|
785
|
+
if (!src) return;
|
|
786
|
+
const parsed = parseImageDataUrl(src);
|
|
787
|
+
if (!parsed) return;
|
|
788
|
+
const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
|
|
789
|
+
const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
|
|
790
|
+
const id = result.images.length + 1;
|
|
791
|
+
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
792
|
+
const target = `media/image${id}.${parsed.extension}`;
|
|
793
|
+
result.images.push({
|
|
794
|
+
relationshipId,
|
|
795
|
+
target,
|
|
796
|
+
data: parsed.data,
|
|
797
|
+
contentType: parsed.contentType,
|
|
798
|
+
widthPx,
|
|
799
|
+
heightPx
|
|
800
|
+
});
|
|
801
|
+
out.push({ kind: "image", image: { relationshipId, widthPx, heightPx } });
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (tag === "canvas") {
|
|
805
|
+
const dataUrl = node.attribs?.["data-image"] ?? node.attribs?.["data-src"];
|
|
806
|
+
if (!dataUrl) return;
|
|
807
|
+
const parsed = parseImageDataUrl(dataUrl);
|
|
808
|
+
if (!parsed) return;
|
|
809
|
+
const bufferW = node.attribs?.width ? Number(node.attribs.width) : void 0;
|
|
810
|
+
const bufferH = node.attribs?.height ? Number(node.attribs.height) : void 0;
|
|
811
|
+
const intrinsic = Number.isFinite(bufferW) && bufferW && Number.isFinite(bufferH) && bufferH ? { widthPx: Math.max(1, Math.round(bufferW)), heightPx: Math.max(1, Math.round(bufferH)) } : parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
|
|
812
|
+
const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
|
|
813
|
+
const id = result.images.length + 1;
|
|
814
|
+
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
815
|
+
const target = `media/image${id}.${parsed.extension}`;
|
|
816
|
+
result.images.push({
|
|
817
|
+
relationshipId,
|
|
818
|
+
target,
|
|
819
|
+
data: parsed.data,
|
|
820
|
+
contentType: parsed.contentType,
|
|
821
|
+
widthPx,
|
|
822
|
+
heightPx
|
|
823
|
+
});
|
|
824
|
+
out.push({ kind: "image", image: { relationshipId, widthPx, heightPx } });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
285
827
|
const next = mergeTextStyle(inherited, styleFromElement(node));
|
|
286
828
|
const children2 = node.children ?? [];
|
|
287
|
-
for (const c of children2) collectInlineRuns(c, next, out);
|
|
829
|
+
for (const c of children2) collectInlineRuns(c, next, out, result);
|
|
288
830
|
return;
|
|
289
831
|
}
|
|
290
832
|
const children = node.children ?? [];
|
|
291
|
-
for (const c of children) collectInlineRuns(c, inherited, out);
|
|
833
|
+
for (const c of children) collectInlineRuns(c, inherited, out, result);
|
|
292
834
|
}
|
|
293
835
|
function buildRunXml(style, text) {
|
|
294
836
|
const rPrParts = [];
|
|
@@ -309,6 +851,16 @@ function buildRunXml(style, text) {
|
|
|
309
851
|
const preserve = shouldPreserveSpace(text) ? ' xml:space="preserve"' : "";
|
|
310
852
|
return `<w:r>${rPrXml}<w:t${preserve}>${escaped}</w:t></w:r>`;
|
|
311
853
|
}
|
|
854
|
+
function pxToEmu(px) {
|
|
855
|
+
return Math.max(1, Math.round(px * 9525));
|
|
856
|
+
}
|
|
857
|
+
function buildImageRunXml(image) {
|
|
858
|
+
const cx = pxToEmu(image.widthPx);
|
|
859
|
+
const cy = pxToEmu(image.heightPx);
|
|
860
|
+
const docPrId = image.relationshipId.replace(/^rId/, "");
|
|
861
|
+
const name = `Picture ${docPrId}`;
|
|
862
|
+
return `<w:r><w:drawing xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${docPrId}" name="${escapeXmlText(name)}"/><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:nvPicPr><pic:cNvPr id="0" name="${escapeXmlText(name)}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${image.relationshipId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
863
|
+
}
|
|
312
864
|
function hasClass(node, className) {
|
|
313
865
|
const cls = node.attribs?.class;
|
|
314
866
|
if (!cls) return false;
|
|
@@ -317,7 +869,11 @@ function hasClass(node, className) {
|
|
|
317
869
|
function isSkippableSubtree(node) {
|
|
318
870
|
if (node.type !== "tag") return false;
|
|
319
871
|
const tag = node.name?.toLowerCase();
|
|
320
|
-
if (tag === "button"
|
|
872
|
+
if (tag === "button") return true;
|
|
873
|
+
if (tag === "canvas") {
|
|
874
|
+
const dataUrl = node.attribs?.["data-image"] ?? node.attribs?.["data-src"];
|
|
875
|
+
if (!dataUrl) return true;
|
|
876
|
+
}
|
|
321
877
|
if (tag === "img" && hasClass(node, "ProseMirror-separator")) return true;
|
|
322
878
|
if (node.attribs?.id === "pages") return true;
|
|
323
879
|
if (hasClass(node, "ProseMirror-widget")) return true;
|
|
@@ -356,9 +912,10 @@ function inferFirstFontSizeHalfPoints(node) {
|
|
|
356
912
|
}
|
|
357
913
|
return void 0;
|
|
358
914
|
}
|
|
359
|
-
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
|
|
915
|
+
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
360
916
|
const css = parseStyleAttribute(node.attribs?.style);
|
|
361
917
|
const parts = [];
|
|
918
|
+
if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
|
|
362
919
|
const align = css["text-align"]?.trim().toLowerCase();
|
|
363
920
|
const jcVal = align === "center" ? "center" : align === "right" ? "right" : align === "justify" ? "both" : void 0;
|
|
364
921
|
if (jcVal) parts.push(`<w:jc w:val="${jcVal}"/>`);
|
|
@@ -409,20 +966,28 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
|
|
|
409
966
|
if (!parts.length) return "";
|
|
410
967
|
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
411
968
|
}
|
|
412
|
-
function buildParagraphXmlFromContainer(node, baseStyle, extraInd) {
|
|
969
|
+
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
|
|
413
970
|
const baseFontHalfPoints = baseStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
|
|
414
|
-
const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd);
|
|
971
|
+
const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
|
|
415
972
|
const runs = [];
|
|
416
|
-
|
|
973
|
+
const res = result ?? {
|
|
974
|
+
bodyXml: "",
|
|
975
|
+
images: []
|
|
976
|
+
};
|
|
977
|
+
for (const c of node.children ?? []) collectInlineRuns(c, baseStyle, runs, res);
|
|
417
978
|
const rXml = [];
|
|
418
979
|
for (const token of runs) {
|
|
419
980
|
if (token.kind === "br") {
|
|
420
981
|
rXml.push("<w:r><w:br/></w:r>");
|
|
421
982
|
continue;
|
|
422
983
|
}
|
|
984
|
+
if (token.kind === "image") {
|
|
985
|
+
rXml.push(buildImageRunXml(token.image));
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
423
988
|
const text = token.text;
|
|
424
989
|
if (!text) continue;
|
|
425
|
-
if (!text.trim()) continue;
|
|
990
|
+
if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
|
|
426
991
|
rXml.push(buildRunXml(token.style, text));
|
|
427
992
|
}
|
|
428
993
|
if (!rXml.length) return "";
|
|
@@ -442,46 +1007,180 @@ function isExplicitPageBreak(node) {
|
|
|
442
1007
|
if (after?.includes("always") || before?.includes("always")) return true;
|
|
443
1008
|
return false;
|
|
444
1009
|
}
|
|
445
|
-
function
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const items = [];
|
|
451
|
-
const stack = [...listNode.children ?? []];
|
|
452
|
-
while (stack.length) {
|
|
453
|
-
const n = stack.shift();
|
|
454
|
-
if (n.type === "tag" && n.name?.toLowerCase() === "li") items.push(n);
|
|
455
|
-
}
|
|
1010
|
+
function buildListBlocks(listNode, ordered, level, result) {
|
|
1011
|
+
const liNodes = (listNode.children ?? []).filter(
|
|
1012
|
+
(c) => c.type === "tag" && c.name?.toLowerCase() === "li"
|
|
1013
|
+
);
|
|
1014
|
+
if (!liNodes.length) return [];
|
|
456
1015
|
const out = [];
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1016
|
+
const numId = ordered ? 2 : 1;
|
|
1017
|
+
const ilvl = Math.max(0, Math.min(8, Math.floor(level)));
|
|
1018
|
+
const leftTwips = 720 * (ilvl + 1);
|
|
1019
|
+
const hangingTwips = 360;
|
|
1020
|
+
for (const li of liNodes) {
|
|
1021
|
+
const nestedLists = [];
|
|
460
1022
|
const baseStyle = {};
|
|
461
1023
|
const runs = [];
|
|
462
|
-
|
|
463
|
-
|
|
1024
|
+
for (const c of li.children ?? []) {
|
|
1025
|
+
if (c.type === "tag") {
|
|
1026
|
+
const tag = c.name?.toLowerCase();
|
|
1027
|
+
if (tag === "ul" || tag === "ol") {
|
|
1028
|
+
nestedLists.push(c);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
collectInlineRuns(c, baseStyle, runs, result);
|
|
1033
|
+
}
|
|
464
1034
|
const rXml = [];
|
|
465
1035
|
for (const token of runs) {
|
|
466
1036
|
if (token.kind === "br") {
|
|
467
1037
|
rXml.push("<w:r><w:br/></w:r>");
|
|
468
1038
|
continue;
|
|
469
1039
|
}
|
|
1040
|
+
if (token.kind === "image") {
|
|
1041
|
+
rXml.push(buildImageRunXml(token.image));
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
470
1044
|
const text = token.text;
|
|
471
1045
|
if (!text) continue;
|
|
472
|
-
if (!text.trim()) continue;
|
|
1046
|
+
if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
|
|
473
1047
|
rXml.push(buildRunXml(token.style, text));
|
|
474
1048
|
}
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1049
|
+
if (rXml.length) {
|
|
1050
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
|
|
1051
|
+
const pPrXml = buildParagraphPrXml(
|
|
1052
|
+
li,
|
|
1053
|
+
baseFontHalfPoints,
|
|
1054
|
+
{ leftTwips, hangingTwips },
|
|
1055
|
+
void 0
|
|
1056
|
+
);
|
|
1057
|
+
const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
|
|
1058
|
+
const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
|
|
1059
|
+
out.push(`<w:p>${mergedPPrXml}${rXml.join("")}</w:p>`);
|
|
1060
|
+
}
|
|
1061
|
+
for (const nested of nestedLists) {
|
|
1062
|
+
const nestedOrdered = nested.name?.toLowerCase() === "ol";
|
|
1063
|
+
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
|
|
1064
|
+
}
|
|
481
1065
|
}
|
|
482
1066
|
return out;
|
|
483
1067
|
}
|
|
484
|
-
function
|
|
1068
|
+
function parseCellWidthTwips(node) {
|
|
1069
|
+
const css = parseStyleAttribute(node.attribs?.style);
|
|
1070
|
+
const width = parseCssLengthToTwips(css.width, 28);
|
|
1071
|
+
if (typeof width !== "number" || width <= 0) return void 0;
|
|
1072
|
+
return width;
|
|
1073
|
+
}
|
|
1074
|
+
function estimateTextWidthTwips(text, baseFontHalfPoints) {
|
|
1075
|
+
const basePt = baseFontHalfPoints / 2;
|
|
1076
|
+
const cjkRegex = /[\u3400-\u4dbf\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
|
|
1077
|
+
let cjk = 0;
|
|
1078
|
+
let latin = 0;
|
|
1079
|
+
let spaces = 0;
|
|
1080
|
+
for (const ch of text) {
|
|
1081
|
+
if (ch === " " || ch === " ") {
|
|
1082
|
+
spaces++;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
if (cjkRegex.test(ch)) {
|
|
1086
|
+
cjk++;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
latin++;
|
|
1090
|
+
}
|
|
1091
|
+
const cjkTwips = Math.round(basePt * 20);
|
|
1092
|
+
const latinTwips = Math.round(basePt * 11);
|
|
1093
|
+
const spaceTwips = Math.round(basePt * 6);
|
|
1094
|
+
return cjk * cjkTwips + latin * latinTwips + spaces * spaceTwips;
|
|
1095
|
+
}
|
|
1096
|
+
function parseBorderShorthand(value, baseFontHalfPoints) {
|
|
1097
|
+
if (!value) return void 0;
|
|
1098
|
+
const raw = value.trim().toLowerCase();
|
|
1099
|
+
if (!raw) return void 0;
|
|
1100
|
+
if (raw === "none" || raw === "0") return { val: "nil", sz: 0 };
|
|
1101
|
+
const tokens = raw.split(/\s+/).filter(Boolean);
|
|
1102
|
+
if (!tokens.length) return void 0;
|
|
1103
|
+
const css = Object.fromEntries(tokens.map((t, i) => [`${i}`, t]));
|
|
1104
|
+
const widthToken = Object.values(css).find((t) => /^(?:\d+(?:\.\d+)?)(?:px|pt)?$/.test(t));
|
|
1105
|
+
const styleToken = Object.values(css).find(
|
|
1106
|
+
(t) => ["none", "solid", "dashed", "dotted", "double", "hidden"].includes(t)
|
|
1107
|
+
);
|
|
1108
|
+
const colorToken = Object.values(css).find((t) => t.startsWith("#") || t.startsWith("rgb("));
|
|
1109
|
+
const widthTwips = parseCssLengthToTwips(widthToken, baseFontHalfPoints);
|
|
1110
|
+
const sz = (() => {
|
|
1111
|
+
if (typeof widthTwips !== "number") return 4;
|
|
1112
|
+
if (widthTwips <= 0) return 0;
|
|
1113
|
+
return Math.max(2, Math.round(widthTwips * 0.4));
|
|
1114
|
+
})();
|
|
1115
|
+
const val = (() => {
|
|
1116
|
+
if (!styleToken) return "single";
|
|
1117
|
+
if (styleToken === "none" || styleToken === "hidden") return "nil";
|
|
1118
|
+
if (styleToken === "solid") return "single";
|
|
1119
|
+
if (styleToken === "dashed") return "dashed";
|
|
1120
|
+
if (styleToken === "dotted") return "dotted";
|
|
1121
|
+
if (styleToken === "double") return "double";
|
|
1122
|
+
return "single";
|
|
1123
|
+
})();
|
|
1124
|
+
const colorHex = parseCssColorToHex(colorToken);
|
|
1125
|
+
return { val, sz, colorHex };
|
|
1126
|
+
}
|
|
1127
|
+
function buildBorderTag(tag, border, fallbackColorHex) {
|
|
1128
|
+
const b = border ?? { val: "single", sz: 4, colorHex: fallbackColorHex };
|
|
1129
|
+
const color = (b.colorHex ?? fallbackColorHex).toUpperCase();
|
|
1130
|
+
return `<w:${tag} w:val="${b.val}" w:sz="${b.sz}" w:space="0" w:color="${color}"/>`;
|
|
1131
|
+
}
|
|
1132
|
+
function injectTableCellParagraphSpacing(pXml) {
|
|
1133
|
+
if (!pXml.includes("<w:p")) return pXml;
|
|
1134
|
+
if (!pXml.includes("<w:p>")) return pXml;
|
|
1135
|
+
const spacingXml = '<w:spacing w:before="0" w:after="0" w:line="360" w:lineRule="auto"/><w:wordWrap w:val="1"/>';
|
|
1136
|
+
if (pXml.includes("<w:pPr>")) {
|
|
1137
|
+
if (pXml.includes("<w:spacing ")) return pXml;
|
|
1138
|
+
return pXml.replace("<w:pPr>", `<w:pPr>${spacingXml}`);
|
|
1139
|
+
}
|
|
1140
|
+
return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
|
|
1141
|
+
}
|
|
1142
|
+
function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
1143
|
+
const children = cell.children ?? [];
|
|
1144
|
+
const hasBlocks = children.some((c) => {
|
|
1145
|
+
if (c.type !== "tag") return false;
|
|
1146
|
+
const tag = c.name?.toLowerCase();
|
|
1147
|
+
return tag === "p" || tag === "ul" || tag === "ol" || tag === "img" || tag === "canvas" || /^h[1-6]$/.test(tag ?? "");
|
|
1148
|
+
});
|
|
1149
|
+
const out = [];
|
|
1150
|
+
if (!hasBlocks) {
|
|
1151
|
+
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
|
|
1152
|
+
if (p) out.push(p);
|
|
1153
|
+
return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
|
|
1154
|
+
}
|
|
1155
|
+
for (const c of children) {
|
|
1156
|
+
if (c.type === "tag") {
|
|
1157
|
+
const tag = c.name?.toLowerCase();
|
|
1158
|
+
if (tag === "p") {
|
|
1159
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
|
|
1160
|
+
if (p) out.push(p);
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (tag && /^h[1-6]$/.test(tag)) {
|
|
1164
|
+
const level = Number(tag.slice(1));
|
|
1165
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
|
|
1166
|
+
if (p) out.push(p);
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (tag === "ul" || tag === "ol") {
|
|
1170
|
+
out.push(...buildListBlocks(c, tag === "ol", 0, result));
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (tag === "img" || tag === "canvas") {
|
|
1174
|
+
const p = buildParagraphXmlFromSingleInlineNode(c, baseStyle, result);
|
|
1175
|
+
if (p) out.push(p);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (!out.length) return "<w:p/>";
|
|
1181
|
+
return out.map(injectTableCellParagraphSpacing).join("");
|
|
1182
|
+
}
|
|
1183
|
+
function buildTableXml(tableNode, result) {
|
|
485
1184
|
const rows = [];
|
|
486
1185
|
const stack = [...tableNode.children ?? []];
|
|
487
1186
|
while (stack.length) {
|
|
@@ -489,28 +1188,149 @@ function buildTableXml(tableNode) {
|
|
|
489
1188
|
if (n.type === "tag" && n.name?.toLowerCase() === "tr") rows.push(n);
|
|
490
1189
|
if (n.children?.length) stack.unshift(...n.children);
|
|
491
1190
|
}
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
1191
|
+
const rowCells = rows.map(
|
|
1192
|
+
(tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
|
|
1193
|
+
);
|
|
1194
|
+
const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
|
|
1195
|
+
const maxTableWidthTwips = 9360;
|
|
1196
|
+
const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
|
|
1197
|
+
let explicit;
|
|
1198
|
+
let estimated = 0;
|
|
1199
|
+
for (const cells of rowCells) {
|
|
1200
|
+
const cell = cells[i];
|
|
1201
|
+
if (!cell) continue;
|
|
1202
|
+
const w = parseCellWidthTwips(cell);
|
|
1203
|
+
if (typeof w === "number") explicit = explicit ?? w;
|
|
1204
|
+
const text = getTextContent(cell).replace(/\s+/g, " ").trim();
|
|
1205
|
+
if (!text) continue;
|
|
1206
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(cell) ?? 28;
|
|
1207
|
+
const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
|
|
1208
|
+
estimated = Math.max(estimated, wTwips);
|
|
1209
|
+
}
|
|
1210
|
+
const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
1211
|
+
return Math.max(720, Math.min(6e3, Math.round(base)));
|
|
1212
|
+
});
|
|
1213
|
+
const normalizedColWidths = (() => {
|
|
1214
|
+
const sum = estimatedColWidths.reduce((a, b) => a + b, 0);
|
|
1215
|
+
if (!sum) return estimatedColWidths;
|
|
1216
|
+
if (sum <= maxTableWidthTwips) return estimatedColWidths;
|
|
1217
|
+
const scaled = estimatedColWidths.map(
|
|
1218
|
+
(w) => Math.max(720, Math.floor(w * maxTableWidthTwips / sum))
|
|
496
1219
|
);
|
|
1220
|
+
const scaledSum = scaled.reduce((a, b) => a + b, 0);
|
|
1221
|
+
const diff = maxTableWidthTwips - scaledSum;
|
|
1222
|
+
if (diff !== 0 && scaled.length) scaled[scaled.length - 1] = Math.max(720, scaled[scaled.length - 1] + diff);
|
|
1223
|
+
return scaled;
|
|
1224
|
+
})();
|
|
1225
|
+
const tblGrid = `<w:tblGrid>${normalizedColWidths.map((w) => `<w:gridCol w:w="${w}"/>`).join("")}</w:tblGrid>`;
|
|
1226
|
+
const rowXml = [];
|
|
1227
|
+
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
1228
|
+
const tr = rows[rowIdx];
|
|
1229
|
+
const cells = rowCells[rowIdx] ?? [];
|
|
497
1230
|
const cellXml = [];
|
|
498
|
-
for (
|
|
1231
|
+
for (let i = 0; i < cells.length; i++) {
|
|
1232
|
+
const cell = cells[i];
|
|
499
1233
|
const isHeader = cell.name === "th";
|
|
500
1234
|
const baseStyle = isHeader ? { bold: true } : {};
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
)
|
|
1235
|
+
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
|
|
1236
|
+
const css = parseStyleAttribute(cell.attribs?.style);
|
|
1237
|
+
const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
|
|
1238
|
+
const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
|
|
1239
|
+
const vAlign = (() => {
|
|
1240
|
+
const v = css["vertical-align"]?.trim().toLowerCase();
|
|
1241
|
+
if (!v) return "";
|
|
1242
|
+
if (v === "middle" || v === "center") return '<w:vAlign w:val="center"/>';
|
|
1243
|
+
if (v === "bottom") return '<w:vAlign w:val="bottom"/>';
|
|
1244
|
+
if (v === "top") return '<w:vAlign w:val="top"/>';
|
|
1245
|
+
return "";
|
|
1246
|
+
})();
|
|
1247
|
+
const shd = (() => {
|
|
1248
|
+
const hex = parseCssColorToHex(css["background-color"]);
|
|
1249
|
+
if (!hex) return "";
|
|
1250
|
+
return `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
|
|
1251
|
+
})();
|
|
1252
|
+
const noWrap = (() => {
|
|
1253
|
+
const ws = css["white-space"]?.trim().toLowerCase();
|
|
1254
|
+
if (ws?.includes("nowrap")) return "<w:noWrap/>";
|
|
1255
|
+
return "";
|
|
1256
|
+
})();
|
|
1257
|
+
const cellBorder = (() => {
|
|
1258
|
+
const bAll = parseBorderShorthand(css.border, 28);
|
|
1259
|
+
const bTop = parseBorderShorthand(css["border-top"] ?? css.border, 28);
|
|
1260
|
+
const bLeft = parseBorderShorthand(css["border-left"] ?? css.border, 28);
|
|
1261
|
+
const bBottom = parseBorderShorthand(css["border-bottom"] ?? css.border, 28);
|
|
1262
|
+
const bRight = parseBorderShorthand(css["border-right"] ?? css.border, 28);
|
|
1263
|
+
const any = bAll || css.border || css["border-top"] || css["border-left"] || css["border-bottom"] || css["border-right"];
|
|
1264
|
+
if (!any) return "";
|
|
1265
|
+
const fallback = bAll?.colorHex ?? "D9D9D9";
|
|
1266
|
+
return `<w:tcBorders>${buildBorderTag("top", bTop, fallback)}${buildBorderTag(
|
|
1267
|
+
"left",
|
|
1268
|
+
bLeft,
|
|
1269
|
+
fallback
|
|
1270
|
+
)}${buildBorderTag("bottom", bBottom, fallback)}${buildBorderTag(
|
|
1271
|
+
"right",
|
|
1272
|
+
bRight,
|
|
1273
|
+
fallback
|
|
1274
|
+
)}</w:tcBorders>`;
|
|
1275
|
+
})();
|
|
1276
|
+
cellXml.push(`<w:tc><w:tcPr>${tcW}${vAlign}${shd}${noWrap}${cellBorder}</w:tcPr>${paragraphs}</w:tc>`);
|
|
506
1277
|
}
|
|
507
1278
|
if (cellXml.length) rowXml.push(`<w:tr>${cellXml.join("")}</w:tr>`);
|
|
508
1279
|
}
|
|
509
|
-
const
|
|
510
|
-
const
|
|
1280
|
+
const tblCss = parseStyleAttribute(tableNode.attribs?.style);
|
|
1281
|
+
const tblAlign = (() => {
|
|
1282
|
+
const ml = tblCss["margin-left"]?.trim().toLowerCase();
|
|
1283
|
+
const mr = tblCss["margin-right"]?.trim().toLowerCase();
|
|
1284
|
+
const m = tblCss.margin?.trim().toLowerCase();
|
|
1285
|
+
if (ml === "auto" && mr === "auto" || (m?.includes("auto") ?? false)) return '<w:tblJc w:val="center"/>';
|
|
1286
|
+
const ta = tblCss["text-align"]?.trim().toLowerCase();
|
|
1287
|
+
if (ta === "center") return '<w:tblJc w:val="center"/>';
|
|
1288
|
+
if (ta === "right") return '<w:tblJc w:val="right"/>';
|
|
1289
|
+
return "";
|
|
1290
|
+
})();
|
|
1291
|
+
const tblBorder = (() => {
|
|
1292
|
+
const border = parseBorderShorthand(tblCss.border, 28);
|
|
1293
|
+
if (tblCss.border) {
|
|
1294
|
+
const fallback2 = border?.colorHex ?? "D9D9D9";
|
|
1295
|
+
return `<w:tblBorders>${buildBorderTag("top", border, fallback2)}${buildBorderTag(
|
|
1296
|
+
"left",
|
|
1297
|
+
border,
|
|
1298
|
+
fallback2
|
|
1299
|
+
)}${buildBorderTag("bottom", border, fallback2)}${buildBorderTag(
|
|
1300
|
+
"right",
|
|
1301
|
+
border,
|
|
1302
|
+
fallback2
|
|
1303
|
+
)}${buildBorderTag("insideH", border, fallback2)}${buildBorderTag(
|
|
1304
|
+
"insideV",
|
|
1305
|
+
border,
|
|
1306
|
+
fallback2
|
|
1307
|
+
)}</w:tblBorders>`;
|
|
1308
|
+
}
|
|
1309
|
+
const fallback = "D9D9D9";
|
|
1310
|
+
return `<w:tblBorders>${buildBorderTag("top", void 0, fallback)}${buildBorderTag(
|
|
1311
|
+
"left",
|
|
1312
|
+
void 0,
|
|
1313
|
+
fallback
|
|
1314
|
+
)}${buildBorderTag("bottom", void 0, fallback)}${buildBorderTag(
|
|
1315
|
+
"right",
|
|
1316
|
+
void 0,
|
|
1317
|
+
fallback
|
|
1318
|
+
)}${buildBorderTag("insideH", void 0, fallback)}${buildBorderTag("insideV", void 0, fallback)}</w:tblBorders>`;
|
|
1319
|
+
})();
|
|
1320
|
+
const tblW = `<w:tblW w:w="${normalizedColWidths.reduce((a, b) => a + b, 0)}" w:type="dxa"/>`;
|
|
1321
|
+
const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
|
|
511
1322
|
return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
|
|
512
1323
|
}
|
|
513
|
-
function
|
|
1324
|
+
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
|
|
1325
|
+
const wrapper = {
|
|
1326
|
+
type: "tag",
|
|
1327
|
+
name: "p",
|
|
1328
|
+
attribs: { style: "text-align: center;" },
|
|
1329
|
+
children: [node]
|
|
1330
|
+
};
|
|
1331
|
+
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
|
|
1332
|
+
}
|
|
1333
|
+
function collectBodyBlocks(node, out, result) {
|
|
514
1334
|
if (isSkippableSubtree(node)) return;
|
|
515
1335
|
if (node.type === "tag") {
|
|
516
1336
|
const tag = node.name?.toLowerCase();
|
|
@@ -519,27 +1339,32 @@ function collectBodyBlocks(node, out) {
|
|
|
519
1339
|
return;
|
|
520
1340
|
}
|
|
521
1341
|
if (tag === "p") {
|
|
522
|
-
const pXml = buildParagraphXmlFromContainer(node, {});
|
|
1342
|
+
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
|
|
1343
|
+
if (pXml) out.push(pXml);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (tag === "img" || tag === "canvas") {
|
|
1347
|
+
const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
|
|
523
1348
|
if (pXml) out.push(pXml);
|
|
524
1349
|
return;
|
|
525
1350
|
}
|
|
526
1351
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
527
1352
|
const level = Number(tag.slice(1));
|
|
528
|
-
const hXml = buildParagraphXmlFromContainer(node,
|
|
1353
|
+
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
|
|
529
1354
|
if (hXml) out.push(hXml);
|
|
530
1355
|
return;
|
|
531
1356
|
}
|
|
532
1357
|
if (tag === "table") {
|
|
533
|
-
const tblXml = buildTableXml(node);
|
|
1358
|
+
const tblXml = buildTableXml(node, result);
|
|
534
1359
|
if (tblXml) out.push(tblXml);
|
|
535
1360
|
return;
|
|
536
1361
|
}
|
|
537
1362
|
if (tag === "ul" || tag === "ol") {
|
|
538
|
-
out.push(...buildListBlocks(node, tag === "ol"));
|
|
1363
|
+
out.push(...buildListBlocks(node, tag === "ol", 0, result));
|
|
539
1364
|
return;
|
|
540
1365
|
}
|
|
541
1366
|
}
|
|
542
|
-
for (const c of node.children ?? []) collectBodyBlocks(c, out);
|
|
1367
|
+
for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
|
|
543
1368
|
}
|
|
544
1369
|
function textToWordBodyXml(text) {
|
|
545
1370
|
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
@@ -557,26 +1382,53 @@ function textToWordBodyXml(text) {
|
|
|
557
1382
|
}
|
|
558
1383
|
return out.join("");
|
|
559
1384
|
}
|
|
560
|
-
function
|
|
1385
|
+
function htmlToWordBody(html) {
|
|
561
1386
|
const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
562
1387
|
const doc = (0, import_htmlparser2.parseDocument)(normalized, {
|
|
563
1388
|
lowerCaseAttributeNames: true,
|
|
564
1389
|
lowerCaseTags: true,
|
|
565
1390
|
recognizeSelfClosing: true
|
|
566
1391
|
});
|
|
1392
|
+
const result = { bodyXml: "", images: [] };
|
|
567
1393
|
const out = [];
|
|
568
|
-
collectBodyBlocks(doc, out);
|
|
569
|
-
|
|
570
|
-
|
|
1394
|
+
collectBodyBlocks(doc, out, result);
|
|
1395
|
+
result.bodyXml = out.join("");
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
function htmlToWordBodyXml(html) {
|
|
1399
|
+
const { bodyXml } = htmlToWordBody(html);
|
|
1400
|
+
if (!bodyXml) {
|
|
1401
|
+
const text = getTextContent(
|
|
1402
|
+
(0, import_htmlparser2.parseDocument)(html, {
|
|
1403
|
+
lowerCaseAttributeNames: true,
|
|
1404
|
+
lowerCaseTags: true,
|
|
1405
|
+
recognizeSelfClosing: true
|
|
1406
|
+
})
|
|
1407
|
+
);
|
|
571
1408
|
return textToWordBodyXml(text);
|
|
572
1409
|
}
|
|
573
|
-
return
|
|
1410
|
+
return bodyXml;
|
|
574
1411
|
}
|
|
575
|
-
|
|
1412
|
+
function htmlToWordBodyWithAssets(html) {
|
|
1413
|
+
const result = htmlToWordBody(html);
|
|
1414
|
+
if (!result.bodyXml) {
|
|
1415
|
+
const text = getTextContent(
|
|
1416
|
+
(0, import_htmlparser2.parseDocument)(html, {
|
|
1417
|
+
lowerCaseAttributeNames: true,
|
|
1418
|
+
lowerCaseTags: true,
|
|
1419
|
+
recognizeSelfClosing: true
|
|
1420
|
+
})
|
|
1421
|
+
);
|
|
1422
|
+
return { bodyXml: textToWordBodyXml(text), images: [] };
|
|
1423
|
+
}
|
|
1424
|
+
return result;
|
|
1425
|
+
}
|
|
1426
|
+
var import_htmlparser2, IMAGE_RELATIONSHIP_ID_OFFSET, PAGE_BREAK_XML;
|
|
576
1427
|
var init_htmlToWordBodyXml = __esm({
|
|
577
1428
|
"src/lib/htmlToWordBodyXml.ts"() {
|
|
578
1429
|
"use strict";
|
|
579
1430
|
import_htmlparser2 = require("htmlparser2");
|
|
1431
|
+
IMAGE_RELATIONSHIP_ID_OFFSET = 7;
|
|
580
1432
|
PAGE_BREAK_XML = '<w:p><w:r><w:br w:type="page"/></w:r></w:p>';
|
|
581
1433
|
}
|
|
582
1434
|
});
|
|
@@ -618,13 +1470,36 @@ async function xmlToDocxBuffer(xml, options = {}) {
|
|
|
618
1470
|
return BufferCtor.from(docx);
|
|
619
1471
|
}
|
|
620
1472
|
async function htmlToDocxUint8Array(html, options = {}) {
|
|
621
|
-
const { htmlToWordBodyXml: htmlToWordBodyXml2, textToWordBodyXml: textToWordBodyXml2 } = await Promise.resolve().then(() => (init_htmlToWordBodyXml(), htmlToWordBodyXml_exports));
|
|
1473
|
+
const { htmlToWordBodyWithAssets: htmlToWordBodyWithAssets2, htmlToWordBodyXml: htmlToWordBodyXml2, textToWordBodyXml: textToWordBodyXml2 } = await Promise.resolve().then(() => (init_htmlToWordBodyXml(), htmlToWordBodyXml_exports));
|
|
1474
|
+
const { createDocxZipWithAssetsUint8Array: createDocxZipWithAssetsUint8Array2 } = await Promise.resolve().then(() => (init_createDocxZip(), createDocxZip_exports));
|
|
622
1475
|
const format = options.inputFormat ?? "auto";
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1476
|
+
const isHtml = format === "html" ? true : format === "text" ? false : looksLikeHtml(html) ? true : false;
|
|
1477
|
+
if (!isHtml) {
|
|
1478
|
+
const bodyXml2 = textToWordBodyXml2(html);
|
|
1479
|
+
return xmlToDocxUint8Array(bodyXml2, {
|
|
1480
|
+
inputKind: "body",
|
|
1481
|
+
validateXml: options.validateXml
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
const { bodyXml, images } = htmlToWordBodyWithAssets2(html);
|
|
1485
|
+
if (!images.length) {
|
|
1486
|
+
return xmlToDocxUint8Array(bodyXml || htmlToWordBodyXml2(html), {
|
|
1487
|
+
inputKind: "body",
|
|
1488
|
+
validateXml: options.validateXml
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
const assets = images.map((img) => ({
|
|
1492
|
+
relationshipId: img.relationshipId,
|
|
1493
|
+
relationshipType: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
1494
|
+
target: img.target,
|
|
1495
|
+
data: img.data,
|
|
1496
|
+
contentType: img.contentType
|
|
1497
|
+
}));
|
|
1498
|
+
return createDocxZipWithAssetsUint8Array2(
|
|
1499
|
+
bodyXml,
|
|
1500
|
+
{ inputKind: "body", validateXml: options.validateXml },
|
|
1501
|
+
assets
|
|
1502
|
+
);
|
|
628
1503
|
}
|
|
629
1504
|
async function htmlToDocxBlob(html, options = {}) {
|
|
630
1505
|
const docx = await htmlToDocxUint8Array(html, options);
|