@yinyoudexing/xml2word 0.1.1 → 0.1.3

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/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"/>
@@ -136,17 +138,36 @@ function buildContentTypesXml(assets) {
136
138
  defaults.set(ext, asset.contentType);
137
139
  }
138
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");
139
151
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
140
152
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
141
153
  ${defaultLines}
142
- <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
154
+ ${overrides}
143
155
  </Types>
144
156
  `;
145
157
  }
146
158
  function buildDocumentRelsXml(assets) {
147
- const relLines = assets.map(
148
- (a) => ` <Relationship Id="${a.relationshipId}" Type="${a.relationshipType}" Target="${a.target}"/>`
149
- ).join("\n");
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");
150
171
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
151
172
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
152
173
  ${relLines}
@@ -162,6 +183,15 @@ async function createDocxZipUint8Array(xml, options = {}) {
162
183
  relsFolder?.file(".rels", ROOT_RELS_XML);
163
184
  const wordFolder = zip.folder("word");
164
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([]));
165
195
  return zip.generateAsync({ type: "uint8array" });
166
196
  }
167
197
  async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
@@ -173,9 +203,16 @@ async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
173
203
  relsFolder?.file(".rels", ROOT_RELS_XML);
174
204
  const wordFolder = zip.folder("word");
175
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));
176
215
  if (assets.length) {
177
- const wordRelsFolder = wordFolder?.folder("_rels");
178
- wordRelsFolder?.file("document.xml.rels", buildDocumentRelsXml(assets));
179
216
  for (const asset of assets) {
180
217
  const targetPath = asset.target.replace(/^\.\//, "");
181
218
  const normalized = targetPath.startsWith("word/") ? targetPath.slice("word/".length) : targetPath;
@@ -184,13 +221,324 @@ async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
184
221
  }
185
222
  return zip.generateAsync({ type: "uint8array" });
186
223
  }
187
- var import_jszip, ROOT_RELS_XML;
224
+ var import_jszip, STYLES_XML, SETTINGS_XML, NUMBERING_XML, HEADER1_XML, FOOTER1_XML, THEME_XML, FONT_TABLE_XML, ROOT_RELS_XML;
188
225
  var init_createDocxZip = __esm({
189
226
  "src/lib/createDocxZip.ts"() {
190
227
  "use strict";
191
228
  import_jszip = __toESM(require("jszip"), 1);
192
229
  init_normalizeDocumentXml();
193
230
  init_validateXml();
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>
541
+ `;
194
542
  ROOT_RELS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
195
543
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
196
544
  <Relationship Id="rId1"
@@ -215,6 +563,12 @@ function shouldPreserveSpace(text) {
215
563
  if (!text) return false;
216
564
  return /^\s/.test(text) || /\s$/.test(text) || /\s{2,}/.test(text);
217
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
+ }
218
572
  function parseStyleAttribute(style) {
219
573
  if (!style) return {};
220
574
  const normalized = style.replace(/\r/g, "\n");
@@ -244,6 +598,17 @@ function parseCssColorToHex(value) {
244
598
  if (hex) return hex.toUpperCase();
245
599
  return parseRgbToHex(v);
246
600
  }
601
+ function extractBackgroundFillHex(css) {
602
+ const raw = (css["background-color"] ?? css.background)?.trim();
603
+ if (!raw) return void 0;
604
+ const direct = parseCssColorToHex(raw);
605
+ if (direct) return direct;
606
+ const hex = raw.match(/#([0-9a-fA-F]{6})/)?.[0];
607
+ if (hex) return parseCssColorToHex(hex);
608
+ const rgb = raw.match(/rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/)?.[0];
609
+ if (rgb) return parseCssColorToHex(rgb);
610
+ return void 0;
611
+ }
247
612
  function parseFontSizeToHalfPoints(value) {
248
613
  if (!value) return void 0;
249
614
  const v = value.trim().toLowerCase();
@@ -434,7 +799,7 @@ function collectInlineRuns(node, inherited, out, result) {
434
799
  const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
435
800
  const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
436
801
  const id = result.images.length + 1;
437
- const relationshipId = `rId${id}`;
802
+ const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
438
803
  const target = `media/image${id}.${parsed.extension}`;
439
804
  result.images.push({
440
805
  relationshipId,
@@ -457,7 +822,7 @@ function collectInlineRuns(node, inherited, out, result) {
457
822
  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);
458
823
  const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
459
824
  const id = result.images.length + 1;
460
- const relationshipId = `rId${id}`;
825
+ const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
461
826
  const target = `media/image${id}.${parsed.extension}`;
462
827
  result.images.push({
463
828
  relationshipId,
@@ -558,9 +923,12 @@ function inferFirstFontSizeHalfPoints(node) {
558
923
  }
559
924
  return void 0;
560
925
  }
561
- function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
926
+ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
562
927
  const css = parseStyleAttribute(node.attribs?.style);
563
928
  const parts = [];
929
+ if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
930
+ const shdHex = extractBackgroundFillHex(css);
931
+ if (shdHex) parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${shdHex}"/>`);
564
932
  const align = css["text-align"]?.trim().toLowerCase();
565
933
  const jcVal = align === "center" ? "center" : align === "right" ? "right" : align === "justify" ? "both" : void 0;
566
934
  if (jcVal) parts.push(`<w:jc w:val="${jcVal}"/>`);
@@ -611,15 +979,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
611
979
  if (!parts.length) return "";
612
980
  return `<w:pPr>${parts.join("")}</w:pPr>`;
613
981
  }
614
- function buildParagraphXmlFromContainer(node, baseStyle, extraInd, result) {
615
- const baseFontHalfPoints = baseStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
616
- const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd);
982
+ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
983
+ const containerStyle = mergeTextStyle(baseStyle, styleFromElement(node));
984
+ const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
985
+ const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
617
986
  const runs = [];
618
987
  const res = result ?? {
619
988
  bodyXml: "",
620
989
  images: []
621
990
  };
622
- for (const c of node.children ?? []) collectInlineRuns(c, baseStyle, runs, res);
991
+ for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res);
623
992
  const rXml = [];
624
993
  for (const token of runs) {
625
994
  if (token.kind === "br") {
@@ -632,7 +1001,7 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, result) {
632
1001
  }
633
1002
  const text = token.text;
634
1003
  if (!text) continue;
635
- if (!text.trim()) continue;
1004
+ if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
636
1005
  rXml.push(buildRunXml(token.style, text));
637
1006
  }
638
1007
  if (!rXml.length) return "";
@@ -652,25 +1021,30 @@ function isExplicitPageBreak(node) {
652
1021
  if (after?.includes("always") || before?.includes("always")) return true;
653
1022
  return false;
654
1023
  }
655
- function buildHeadingBaseStyle(level) {
656
- const size = level === 1 ? 44 : level === 2 ? 32 : level === 3 ? 28 : level === 4 ? 24 : 22;
657
- return { bold: true, fontSizeHalfPoints: size };
658
- }
659
- function buildListBlocks(listNode, ordered, result) {
660
- const items = [];
661
- const stack = [...listNode.children ?? []];
662
- while (stack.length) {
663
- const n = stack.shift();
664
- if (n.type === "tag" && n.name?.toLowerCase() === "li") items.push(n);
665
- }
1024
+ function buildListBlocks(listNode, ordered, level, result) {
1025
+ const liNodes = (listNode.children ?? []).filter(
1026
+ (c) => c.type === "tag" && c.name?.toLowerCase() === "li"
1027
+ );
1028
+ if (!liNodes.length) return [];
666
1029
  const out = [];
667
- for (let i = 0; i < items.length; i++) {
668
- const prefix = ordered ? `${i + 1}. ` : "\u2022 ";
669
- const li = items[i];
670
- const baseStyle = {};
1030
+ const numId = ordered ? 2 : 1;
1031
+ const ilvl = Math.max(0, Math.min(8, Math.floor(level)));
1032
+ const leftTwips = 720 * (ilvl + 1);
1033
+ const hangingTwips = 360;
1034
+ for (const li of liNodes) {
1035
+ const nestedLists = [];
1036
+ const baseStyle = styleFromElement(li);
671
1037
  const runs = [];
672
- runs.push({ kind: "text", text: prefix, style: baseStyle });
673
- for (const c of li.children ?? []) collectInlineRuns(c, baseStyle, runs, result);
1038
+ for (const c of li.children ?? []) {
1039
+ if (c.type === "tag") {
1040
+ const tag = c.name?.toLowerCase();
1041
+ if (tag === "ul" || tag === "ol") {
1042
+ nestedLists.push(c);
1043
+ continue;
1044
+ }
1045
+ }
1046
+ collectInlineRuns(c, baseStyle, runs, result);
1047
+ }
674
1048
  const rXml = [];
675
1049
  for (const token of runs) {
676
1050
  if (token.kind === "br") {
@@ -683,18 +1057,143 @@ function buildListBlocks(listNode, ordered, result) {
683
1057
  }
684
1058
  const text = token.text;
685
1059
  if (!text) continue;
686
- if (!text.trim()) continue;
1060
+ if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
687
1061
  rXml.push(buildRunXml(token.style, text));
688
1062
  }
689
- if (!rXml.length) continue;
690
- const pPrXml = buildParagraphPrXml(li, inferFirstFontSizeHalfPoints(li) ?? 28, {
691
- leftTwips: 720,
692
- hangingTwips: 360
693
- });
694
- out.push(`<w:p>${pPrXml}${rXml.join("")}</w:p>`);
1063
+ if (rXml.length) {
1064
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
1065
+ const pPrXml = buildParagraphPrXml(
1066
+ li,
1067
+ baseFontHalfPoints,
1068
+ { leftTwips, hangingTwips },
1069
+ void 0
1070
+ );
1071
+ const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
1072
+ const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
1073
+ out.push(`<w:p>${mergedPPrXml}${rXml.join("")}</w:p>`);
1074
+ }
1075
+ for (const nested of nestedLists) {
1076
+ const nestedOrdered = nested.name?.toLowerCase() === "ol";
1077
+ out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
1078
+ }
695
1079
  }
696
1080
  return out;
697
1081
  }
1082
+ function parseCellWidthTwips(node) {
1083
+ const css = parseStyleAttribute(node.attribs?.style);
1084
+ const width = parseCssLengthToTwips(css.width, 28);
1085
+ if (typeof width !== "number" || width <= 0) return void 0;
1086
+ return width;
1087
+ }
1088
+ function estimateTextWidthTwips(text, baseFontHalfPoints) {
1089
+ const basePt = baseFontHalfPoints / 2;
1090
+ const cjkRegex = /[\u3400-\u4dbf\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
1091
+ let cjk = 0;
1092
+ let latin = 0;
1093
+ let spaces = 0;
1094
+ for (const ch of text) {
1095
+ if (ch === " " || ch === " ") {
1096
+ spaces++;
1097
+ continue;
1098
+ }
1099
+ if (cjkRegex.test(ch)) {
1100
+ cjk++;
1101
+ continue;
1102
+ }
1103
+ latin++;
1104
+ }
1105
+ const cjkTwips = Math.round(basePt * 20);
1106
+ const latinTwips = Math.round(basePt * 11);
1107
+ const spaceTwips = Math.round(basePt * 6);
1108
+ return cjk * cjkTwips + latin * latinTwips + spaces * spaceTwips;
1109
+ }
1110
+ function parseBorderShorthand(value, baseFontHalfPoints) {
1111
+ if (!value) return void 0;
1112
+ const raw = value.trim().toLowerCase();
1113
+ if (!raw) return void 0;
1114
+ if (raw === "none" || raw === "0") return { val: "nil", sz: 0 };
1115
+ const tokens = raw.split(/\s+/).filter(Boolean);
1116
+ if (!tokens.length) return void 0;
1117
+ const css = Object.fromEntries(tokens.map((t, i) => [`${i}`, t]));
1118
+ const widthToken = Object.values(css).find((t) => /^(?:\d+(?:\.\d+)?)(?:px|pt)?$/.test(t));
1119
+ const styleToken = Object.values(css).find(
1120
+ (t) => ["none", "solid", "dashed", "dotted", "double", "hidden"].includes(t)
1121
+ );
1122
+ const colorToken = Object.values(css).find((t) => t.startsWith("#") || t.startsWith("rgb("));
1123
+ const widthTwips = parseCssLengthToTwips(widthToken, baseFontHalfPoints);
1124
+ const sz = (() => {
1125
+ if (typeof widthTwips !== "number") return 4;
1126
+ if (widthTwips <= 0) return 0;
1127
+ return Math.max(2, Math.round(widthTwips * 0.4));
1128
+ })();
1129
+ const val = (() => {
1130
+ if (!styleToken) return "single";
1131
+ if (styleToken === "none" || styleToken === "hidden") return "nil";
1132
+ if (styleToken === "solid") return "single";
1133
+ if (styleToken === "dashed") return "dashed";
1134
+ if (styleToken === "dotted") return "dotted";
1135
+ if (styleToken === "double") return "double";
1136
+ return "single";
1137
+ })();
1138
+ const colorHex = parseCssColorToHex(colorToken);
1139
+ return { val, sz, colorHex };
1140
+ }
1141
+ function buildBorderTag(tag, border, fallbackColorHex) {
1142
+ const b = border ?? { val: "single", sz: 4, colorHex: fallbackColorHex };
1143
+ const color = (b.colorHex ?? fallbackColorHex).toUpperCase();
1144
+ return `<w:${tag} w:val="${b.val}" w:sz="${b.sz}" w:space="0" w:color="${color}"/>`;
1145
+ }
1146
+ function injectTableCellParagraphSpacing(pXml) {
1147
+ if (!pXml.includes("<w:p")) return pXml;
1148
+ if (!pXml.includes("<w:p>")) return pXml;
1149
+ const spacingXml = '<w:spacing w:before="0" w:after="0" w:line="360" w:lineRule="auto"/><w:wordWrap w:val="1"/>';
1150
+ if (pXml.includes("<w:pPr>")) {
1151
+ if (pXml.includes("<w:spacing ")) return pXml;
1152
+ return pXml.replace("<w:pPr>", `<w:pPr>${spacingXml}`);
1153
+ }
1154
+ return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
1155
+ }
1156
+ function buildTableCellBlocksXml(cell, baseStyle, result) {
1157
+ const children = cell.children ?? [];
1158
+ const hasBlocks = children.some((c) => {
1159
+ if (c.type !== "tag") return false;
1160
+ const tag = c.name?.toLowerCase();
1161
+ return tag === "p" || tag === "ul" || tag === "ol" || tag === "img" || tag === "canvas" || /^h[1-6]$/.test(tag ?? "");
1162
+ });
1163
+ const out = [];
1164
+ if (!hasBlocks) {
1165
+ const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
1166
+ if (p) out.push(p);
1167
+ return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
1168
+ }
1169
+ for (const c of children) {
1170
+ if (c.type === "tag") {
1171
+ const tag = c.name?.toLowerCase();
1172
+ if (tag === "p") {
1173
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
1174
+ if (p) out.push(p);
1175
+ continue;
1176
+ }
1177
+ if (tag && /^h[1-6]$/.test(tag)) {
1178
+ const level = Number(tag.slice(1));
1179
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
1180
+ if (p) out.push(p);
1181
+ continue;
1182
+ }
1183
+ if (tag === "ul" || tag === "ol") {
1184
+ out.push(...buildListBlocks(c, tag === "ol", 0, result));
1185
+ continue;
1186
+ }
1187
+ if (tag === "img" || tag === "canvas") {
1188
+ const p = buildParagraphXmlFromSingleInlineNode(c, baseStyle, result);
1189
+ if (p) out.push(p);
1190
+ continue;
1191
+ }
1192
+ }
1193
+ }
1194
+ if (!out.length) return "<w:p/>";
1195
+ return out.map(injectTableCellParagraphSpacing).join("");
1196
+ }
698
1197
  function buildTableXml(tableNode, result) {
699
1198
  const rows = [];
700
1199
  const stack = [...tableNode.children ?? []];
@@ -703,25 +1202,137 @@ function buildTableXml(tableNode, result) {
703
1202
  if (n.type === "tag" && n.name?.toLowerCase() === "tr") rows.push(n);
704
1203
  if (n.children?.length) stack.unshift(...n.children);
705
1204
  }
706
- const rowXml = [];
707
- for (const tr of rows) {
708
- const cells = (tr.children ?? []).filter(
709
- (c) => c.type === "tag" && (c.name === "td" || c.name === "th")
1205
+ const rowCells = rows.map(
1206
+ (tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
1207
+ );
1208
+ const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
1209
+ const maxTableWidthTwips = 9360;
1210
+ const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
1211
+ let explicit;
1212
+ let estimated = 0;
1213
+ for (const cells of rowCells) {
1214
+ const cell = cells[i];
1215
+ if (!cell) continue;
1216
+ const w = parseCellWidthTwips(cell);
1217
+ if (typeof w === "number") explicit = explicit ?? w;
1218
+ const text = getTextContent(cell).replace(/\s+/g, " ").trim();
1219
+ if (!text) continue;
1220
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(cell) ?? 28;
1221
+ const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
1222
+ estimated = Math.max(estimated, wTwips);
1223
+ }
1224
+ const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
1225
+ return Math.max(720, Math.min(6e3, Math.round(base)));
1226
+ });
1227
+ const normalizedColWidths = (() => {
1228
+ const sum = estimatedColWidths.reduce((a, b) => a + b, 0);
1229
+ if (!sum) return estimatedColWidths;
1230
+ if (sum <= maxTableWidthTwips) return estimatedColWidths;
1231
+ const scaled = estimatedColWidths.map(
1232
+ (w) => Math.max(720, Math.floor(w * maxTableWidthTwips / sum))
710
1233
  );
1234
+ const scaledSum = scaled.reduce((a, b) => a + b, 0);
1235
+ const diff = maxTableWidthTwips - scaledSum;
1236
+ if (diff !== 0 && scaled.length) scaled[scaled.length - 1] = Math.max(720, scaled[scaled.length - 1] + diff);
1237
+ return scaled;
1238
+ })();
1239
+ const tblGrid = `<w:tblGrid>${normalizedColWidths.map((w) => `<w:gridCol w:w="${w}"/>`).join("")}</w:tblGrid>`;
1240
+ const rowXml = [];
1241
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
1242
+ const tr = rows[rowIdx];
1243
+ const cells = rowCells[rowIdx] ?? [];
711
1244
  const cellXml = [];
712
- for (const cell of cells) {
1245
+ for (let i = 0; i < cells.length; i++) {
1246
+ const cell = cells[i];
713
1247
  const isHeader = cell.name === "th";
714
1248
  const baseStyle = isHeader ? { bold: true } : {};
715
- const pXml = buildParagraphXmlFromContainer(cell, baseStyle, void 0, result);
716
- const paragraphs = pXml ? pXml : "<w:p/>";
717
- cellXml.push(
718
- `<w:tc><w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>${paragraphs}</w:tc>`
719
- );
1249
+ const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
1250
+ const css = parseStyleAttribute(cell.attribs?.style);
1251
+ const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
1252
+ const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
1253
+ const vAlign = (() => {
1254
+ const v = css["vertical-align"]?.trim().toLowerCase();
1255
+ if (!v) return "";
1256
+ if (v === "middle" || v === "center") return '<w:vAlign w:val="center"/>';
1257
+ if (v === "bottom") return '<w:vAlign w:val="bottom"/>';
1258
+ if (v === "top") return '<w:vAlign w:val="top"/>';
1259
+ return "";
1260
+ })();
1261
+ const shd = (() => {
1262
+ const hex = parseCssColorToHex(css["background-color"]);
1263
+ if (!hex) return "";
1264
+ return `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
1265
+ })();
1266
+ const noWrap = (() => {
1267
+ const ws = css["white-space"]?.trim().toLowerCase();
1268
+ if (ws?.includes("nowrap")) return "<w:noWrap/>";
1269
+ return "";
1270
+ })();
1271
+ const cellBorder = (() => {
1272
+ const bAll = parseBorderShorthand(css.border, 28);
1273
+ const bTop = parseBorderShorthand(css["border-top"] ?? css.border, 28);
1274
+ const bLeft = parseBorderShorthand(css["border-left"] ?? css.border, 28);
1275
+ const bBottom = parseBorderShorthand(css["border-bottom"] ?? css.border, 28);
1276
+ const bRight = parseBorderShorthand(css["border-right"] ?? css.border, 28);
1277
+ const any = bAll || css.border || css["border-top"] || css["border-left"] || css["border-bottom"] || css["border-right"];
1278
+ if (!any) return "";
1279
+ const fallback = bAll?.colorHex ?? "D9D9D9";
1280
+ return `<w:tcBorders>${buildBorderTag("top", bTop, fallback)}${buildBorderTag(
1281
+ "left",
1282
+ bLeft,
1283
+ fallback
1284
+ )}${buildBorderTag("bottom", bBottom, fallback)}${buildBorderTag(
1285
+ "right",
1286
+ bRight,
1287
+ fallback
1288
+ )}</w:tcBorders>`;
1289
+ })();
1290
+ cellXml.push(`<w:tc><w:tcPr>${tcW}${vAlign}${shd}${noWrap}${cellBorder}</w:tcPr>${paragraphs}</w:tc>`);
720
1291
  }
721
1292
  if (cellXml.length) rowXml.push(`<w:tr>${cellXml.join("")}</w:tr>`);
722
1293
  }
723
- const tblPr = `<w:tblPr><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:left w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:right w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/></w:tblBorders></w:tblPr>`;
724
- const tblGrid = `<w:tblGrid/>`;
1294
+ const tblCss = parseStyleAttribute(tableNode.attribs?.style);
1295
+ const tblAlign = (() => {
1296
+ const ml = tblCss["margin-left"]?.trim().toLowerCase();
1297
+ const mr = tblCss["margin-right"]?.trim().toLowerCase();
1298
+ const m = tblCss.margin?.trim().toLowerCase();
1299
+ if (ml === "auto" && mr === "auto" || (m?.includes("auto") ?? false)) return '<w:tblJc w:val="center"/>';
1300
+ const ta = tblCss["text-align"]?.trim().toLowerCase();
1301
+ if (ta === "center") return '<w:tblJc w:val="center"/>';
1302
+ if (ta === "right") return '<w:tblJc w:val="right"/>';
1303
+ return "";
1304
+ })();
1305
+ const tblBorder = (() => {
1306
+ const border = parseBorderShorthand(tblCss.border, 28);
1307
+ if (tblCss.border) {
1308
+ const fallback2 = border?.colorHex ?? "D9D9D9";
1309
+ return `<w:tblBorders>${buildBorderTag("top", border, fallback2)}${buildBorderTag(
1310
+ "left",
1311
+ border,
1312
+ fallback2
1313
+ )}${buildBorderTag("bottom", border, fallback2)}${buildBorderTag(
1314
+ "right",
1315
+ border,
1316
+ fallback2
1317
+ )}${buildBorderTag("insideH", border, fallback2)}${buildBorderTag(
1318
+ "insideV",
1319
+ border,
1320
+ fallback2
1321
+ )}</w:tblBorders>`;
1322
+ }
1323
+ const fallback = "D9D9D9";
1324
+ return `<w:tblBorders>${buildBorderTag("top", void 0, fallback)}${buildBorderTag(
1325
+ "left",
1326
+ void 0,
1327
+ fallback
1328
+ )}${buildBorderTag("bottom", void 0, fallback)}${buildBorderTag(
1329
+ "right",
1330
+ void 0,
1331
+ fallback
1332
+ )}${buildBorderTag("insideH", void 0, fallback)}${buildBorderTag("insideV", void 0, fallback)}</w:tblBorders>`;
1333
+ })();
1334
+ const tblW = `<w:tblW w:w="${normalizedColWidths.reduce((a, b) => a + b, 0)}" w:type="dxa"/>`;
1335
+ const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
725
1336
  return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
726
1337
  }
727
1338
  function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
@@ -731,7 +1342,71 @@ function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
731
1342
  attribs: { style: "text-align: center;" },
732
1343
  children: [node]
733
1344
  };
734
- return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, result);
1345
+ return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
1346
+ }
1347
+ function isRecognizedBlockTag(tag) {
1348
+ if (!tag) return false;
1349
+ if (tag === "p") return true;
1350
+ if (tag === "table") return true;
1351
+ if (tag === "ul" || tag === "ol") return true;
1352
+ if (tag === "img" || tag === "canvas") return true;
1353
+ if (/^h[1-6]$/.test(tag)) return true;
1354
+ if (tag === "pre") return true;
1355
+ return false;
1356
+ }
1357
+ function collectDivBlocks(node, out, result) {
1358
+ const parentStyle = node.attribs?.style;
1359
+ const inlineBuffer = [];
1360
+ const flushInline = () => {
1361
+ const wrapper = {
1362
+ type: "tag",
1363
+ name: "p",
1364
+ attribs: { style: parentStyle },
1365
+ children: inlineBuffer.splice(0)
1366
+ };
1367
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
1368
+ if (pXml) out.push(pXml);
1369
+ };
1370
+ const children = node.children ?? [];
1371
+ for (const child of children) {
1372
+ if (child.type === "tag") {
1373
+ const tag = child.name?.toLowerCase();
1374
+ if (isExplicitPageBreak(child)) {
1375
+ if (inlineBuffer.length) flushInline();
1376
+ out.push(PAGE_BREAK_XML);
1377
+ continue;
1378
+ }
1379
+ if (isRecognizedBlockTag(tag)) {
1380
+ if (inlineBuffer.length) flushInline();
1381
+ collectBodyBlocks(child, out, result);
1382
+ continue;
1383
+ }
1384
+ if (tag === "div") {
1385
+ const childHasRecognizedBlocks = (child.children ?? []).some((gc) => {
1386
+ if (gc.type !== "tag") return false;
1387
+ return isRecognizedBlockTag(gc.name?.toLowerCase());
1388
+ });
1389
+ if (childHasRecognizedBlocks) {
1390
+ if (inlineBuffer.length) flushInline();
1391
+ collectBodyBlocks(child, out, result);
1392
+ continue;
1393
+ }
1394
+ if (inlineBuffer.length) flushInline();
1395
+ const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
1396
+ const wrapper = {
1397
+ type: "tag",
1398
+ name: "p",
1399
+ attribs: { style: mergedStyle || void 0 },
1400
+ children: child.children ?? []
1401
+ };
1402
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
1403
+ if (pXml) out.push(pXml);
1404
+ continue;
1405
+ }
1406
+ }
1407
+ inlineBuffer.push(child);
1408
+ }
1409
+ if (inlineBuffer.length) flushInline();
735
1410
  }
736
1411
  function collectBodyBlocks(node, out, result) {
737
1412
  if (isSkippableSubtree(node)) return;
@@ -742,7 +1417,7 @@ function collectBodyBlocks(node, out, result) {
742
1417
  return;
743
1418
  }
744
1419
  if (tag === "p") {
745
- const pXml = buildParagraphXmlFromContainer(node, {}, void 0, result);
1420
+ const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
746
1421
  if (pXml) out.push(pXml);
747
1422
  return;
748
1423
  }
@@ -753,7 +1428,7 @@ function collectBodyBlocks(node, out, result) {
753
1428
  }
754
1429
  if (tag && /^h[1-6]$/.test(tag)) {
755
1430
  const level = Number(tag.slice(1));
756
- const hXml = buildParagraphXmlFromContainer(node, buildHeadingBaseStyle(level), void 0, result);
1431
+ const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
757
1432
  if (hXml) out.push(hXml);
758
1433
  return;
759
1434
  }
@@ -763,7 +1438,11 @@ function collectBodyBlocks(node, out, result) {
763
1438
  return;
764
1439
  }
765
1440
  if (tag === "ul" || tag === "ol") {
766
- out.push(...buildListBlocks(node, tag === "ol", result));
1441
+ out.push(...buildListBlocks(node, tag === "ol", 0, result));
1442
+ return;
1443
+ }
1444
+ if (tag === "div") {
1445
+ collectDivBlocks(node, out, result);
767
1446
  return;
768
1447
  }
769
1448
  }
@@ -826,11 +1505,12 @@ function htmlToWordBodyWithAssets(html) {
826
1505
  }
827
1506
  return result;
828
1507
  }
829
- var import_htmlparser2, PAGE_BREAK_XML;
1508
+ var import_htmlparser2, IMAGE_RELATIONSHIP_ID_OFFSET, PAGE_BREAK_XML;
830
1509
  var init_htmlToWordBodyXml = __esm({
831
1510
  "src/lib/htmlToWordBodyXml.ts"() {
832
1511
  "use strict";
833
1512
  import_htmlparser2 = require("htmlparser2");
1513
+ IMAGE_RELATIONSHIP_ID_OFFSET = 7;
834
1514
  PAGE_BREAK_XML = '<w:p><w:r><w:br w:type="page"/></w:r></w:p>';
835
1515
  }
836
1516
  });