@coding01/docsjs 0.1.3 → 0.1.5

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.
@@ -40,6 +40,9 @@ function getAttr(node, name) {
40
40
  function emuToPx(emu) {
41
41
  return emu * 96 / 914400;
42
42
  }
43
+ function twipToPx(twip) {
44
+ return twip * 96 / 1440;
45
+ }
43
46
  function parseDrawingSizePx(drawing) {
44
47
  const extentNode = queryAllByLocalName(drawing, "extent").find((node) => {
45
48
  const parent = node.parentElement;
@@ -68,9 +71,7 @@ function imageDimensionAttributes(sizePx) {
68
71
  }
69
72
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
70
73
  }
71
- function parseAnchorPositionPx(drawing) {
72
- const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
73
- if (!anchor) return { leftPx: null, topPx: null };
74
+ function parseAnchorPositionPx(anchor) {
74
75
  let leftPx = null;
75
76
  let topPx = null;
76
77
  const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
@@ -85,34 +86,80 @@ function parseAnchorPositionPx(drawing) {
85
86
  if (Number.isFinite(top)) topPx = emuToPx(top);
86
87
  return { leftPx, topPx };
87
88
  }
88
- function parseAnchorWrapMode(drawing) {
89
- const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
90
- if (!anchor) return null;
89
+ function parseAnchorWrapMode(anchor) {
91
90
  if (directChildrenByLocalName(anchor, "wrapSquare")[0]) return "square";
92
91
  if (directChildrenByLocalName(anchor, "wrapTight")[0]) return "tight";
93
92
  if (directChildrenByLocalName(anchor, "wrapTopAndBottom")[0]) return "topAndBottom";
94
93
  if (directChildrenByLocalName(anchor, "wrapNone")[0]) return "none";
95
94
  return null;
96
95
  }
97
- function mergeImageStyle(baseAttrs, anchorPos, wrapMode) {
98
- if (anchorPos.leftPx === null && anchorPos.topPx === null) return baseAttrs;
96
+ function parseAnchorMeta(drawing) {
97
+ const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
98
+ if (!anchor) return null;
99
+ const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
100
+ const positionV = directChildrenByLocalName(anchor, "positionV")[0] ?? null;
101
+ const relativeFromH = getAttr(positionH, "relativeFrom");
102
+ const relativeFromV = getAttr(positionV, "relativeFrom");
103
+ const parseDistPx = (name) => {
104
+ const raw = getAttr(anchor, name);
105
+ const emu = raw ? Number.parseInt(raw, 10) : Number.NaN;
106
+ return Number.isFinite(emu) && emu >= 0 ? emuToPx(emu) : null;
107
+ };
108
+ const rawHeight = getAttr(anchor, "relativeHeight");
109
+ const parsedHeight = rawHeight ? Number.parseInt(rawHeight, 10) : Number.NaN;
110
+ const boolAttr = (name, fallback) => {
111
+ const raw = (getAttr(anchor, name) ?? "").toLowerCase();
112
+ if (raw === "1" || raw === "true" || raw === "on") return true;
113
+ if (raw === "0" || raw === "false" || raw === "off") return false;
114
+ return fallback;
115
+ };
116
+ return {
117
+ position: parseAnchorPositionPx(anchor),
118
+ wrapMode: parseAnchorWrapMode(anchor),
119
+ distTPx: parseDistPx("distT"),
120
+ distBPx: parseDistPx("distB"),
121
+ distLPx: parseDistPx("distL"),
122
+ distRPx: parseDistPx("distR"),
123
+ relativeFromH,
124
+ relativeFromV,
125
+ behindDoc: boolAttr("behindDoc", false),
126
+ allowOverlap: boolAttr("allowOverlap", true),
127
+ layoutInCell: boolAttr("layoutInCell", true),
128
+ relativeHeight: Number.isFinite(parsedHeight) ? parsedHeight : null
129
+ };
130
+ }
131
+ function mergeImageStyle(baseAttrs, anchorMeta) {
132
+ if (!anchorMeta) return baseAttrs;
133
+ const { position, wrapMode } = anchorMeta;
134
+ if (position.leftPx === null && position.topPx === null) return baseAttrs;
99
135
  const styleParts = [
100
136
  "position:absolute",
101
- anchorPos.leftPx !== null ? `left:${anchorPos.leftPx.toFixed(2)}px` : "",
102
- anchorPos.topPx !== null ? `top:${anchorPos.topPx.toFixed(2)}px` : "",
103
- "z-index:3"
137
+ position.leftPx !== null ? `left:${position.leftPx.toFixed(2)}px` : "",
138
+ position.topPx !== null ? `top:${position.topPx.toFixed(2)}px` : "",
139
+ `z-index:${anchorMeta.behindDoc ? 0 : anchorMeta.relativeHeight ?? 3}`,
140
+ anchorMeta.distTPx !== null ? `margin-top:${anchorMeta.distTPx.toFixed(2)}px` : "",
141
+ anchorMeta.distBPx !== null ? `margin-bottom:${anchorMeta.distBPx.toFixed(2)}px` : "",
142
+ anchorMeta.distLPx !== null ? `margin-left:${anchorMeta.distLPx.toFixed(2)}px` : "",
143
+ anchorMeta.distRPx !== null ? `margin-right:${anchorMeta.distRPx.toFixed(2)}px` : ""
104
144
  ].filter((x) => x.length > 0);
105
145
  if (wrapMode === "topAndBottom") {
106
- styleParts.push("display:block");
107
- }
146
+ styleParts.push("display:block", "clear:both");
147
+ }
148
+ const anchorAttrs = [
149
+ `data-word-anchor="1"`,
150
+ wrapMode ? `data-word-wrap="${wrapMode}"` : "",
151
+ anchorMeta.relativeFromH ? `data-word-anchor-relh="${escapeHtml(anchorMeta.relativeFromH)}"` : "",
152
+ anchorMeta.relativeFromV ? `data-word-anchor-relv="${escapeHtml(anchorMeta.relativeFromV)}"` : "",
153
+ anchorMeta.behindDoc ? `data-word-anchor-behind="1"` : `data-word-anchor-behind="0"`,
154
+ anchorMeta.allowOverlap ? `data-word-anchor-overlap="1"` : `data-word-anchor-overlap="0"`,
155
+ anchorMeta.layoutInCell ? `data-word-anchor-layout-cell="1"` : `data-word-anchor-layout-cell="0"`
156
+ ].filter((x) => x.length > 0).join(" ");
108
157
  if (!baseAttrs.includes("style=")) {
109
- const wrapAttr = wrapMode ? ` data-word-wrap="${wrapMode}"` : "";
110
- return `${baseAttrs} style="${styleParts.join(";")}" data-word-anchor="1"${wrapAttr}`;
158
+ return `${baseAttrs} style="${styleParts.join(";")}" ${anchorAttrs}`;
111
159
  }
112
160
  return baseAttrs.replace(/style="([^"]*)"/, (_m, styleText) => {
113
161
  const merged = [styleText, ...styleParts].filter((x) => x.length > 0).join(";");
114
- const wrapAttr = wrapMode ? ` data-word-wrap="${wrapMode}"` : "";
115
- return `style="${merged}" data-word-anchor="1"${wrapAttr}`;
162
+ return `style="${merged}" ${anchorAttrs}`;
116
163
  });
117
164
  }
118
165
  function parseDocRelsMap(relsXmlText) {
@@ -138,11 +185,16 @@ function extToMime(ext) {
138
185
  if (lower === "svg") return "image/svg+xml";
139
186
  return "application/octet-stream";
140
187
  }
188
+ function normalizeWordPath(relTarget) {
189
+ const normalized = relTarget.replace(/\\/g, "/").replace(/^\/+/, "");
190
+ if (normalized.startsWith("word/")) return normalized;
191
+ if (normalized.startsWith("../")) return `word/${normalized.replace(/^(\.\.\/)+/, "")}`;
192
+ return `word/${normalized}`;
193
+ }
141
194
  async function imageRidToDataUrl(zip, relMap, rid) {
142
195
  const relTarget = relMap[rid];
143
196
  if (!relTarget) return null;
144
- const normalized = relTarget.replace(/^\/+/, "");
145
- const path = normalized.startsWith("word/") ? normalized : `word/${normalized}`;
197
+ const path = normalizeWordPath(relTarget);
146
198
  const file = zip.file(path);
147
199
  if (!file) return null;
148
200
  const base64 = await file.async("base64");
@@ -150,6 +202,55 @@ async function imageRidToDataUrl(zip, relMap, rid) {
150
202
  const mime = extToMime(ext);
151
203
  return `data:${mime};base64,${base64}`;
152
204
  }
205
+ async function readXmlByRid(zip, relMap, rid) {
206
+ const relTarget = relMap[rid];
207
+ if (!relTarget) return null;
208
+ const path = normalizeWordPath(relTarget);
209
+ const file = zip.file(path);
210
+ return file ? file.async("string") : null;
211
+ }
212
+ function parseChartType(chartDoc) {
213
+ const known = ["barChart", "lineChart", "pieChart", "areaChart", "scatterChart", "radarChart", "doughnutChart"];
214
+ for (const type of known) {
215
+ if (queryByLocalName(chartDoc, type)) return type.replace(/Chart$/, "");
216
+ }
217
+ return "unknown";
218
+ }
219
+ function parseChartSummary(chartXmlText) {
220
+ const chartDoc = parseXml(chartXmlText);
221
+ const title = queryAllByLocalName(chartDoc, "t").map((n) => (n.textContent ?? "").trim()).find((v) => v.length > 0) ?? "Chart";
222
+ const seriesCount = queryAllByLocalName(chartDoc, "ser").length;
223
+ const pointCount = queryAllByLocalName(chartDoc, "pt").length;
224
+ const type = parseChartType(chartDoc);
225
+ return { title, type, seriesCount, pointCount };
226
+ }
227
+ function extractSmartArtText(diagramXmlText) {
228
+ const diagramDoc = parseXml(diagramXmlText);
229
+ return queryAllByLocalName(diagramDoc, "t").map((n) => (n.textContent ?? "").trim()).filter((v) => v.length > 0).slice(0, 12);
230
+ }
231
+ function ommlNodeToText(node) {
232
+ if (node.localName === "t") return node.textContent ?? "";
233
+ if (node.localName === "f") {
234
+ const num = queryByLocalName(node, "num");
235
+ const den = queryByLocalName(node, "den");
236
+ return `(${num ? ommlNodeToText(num) : "?"})/(${den ? ommlNodeToText(den) : "?"})`;
237
+ }
238
+ if (node.localName === "sSup") {
239
+ const e = queryByLocalName(node, "e");
240
+ const sup = queryByLocalName(node, "sup");
241
+ return `${e ? ommlNodeToText(e) : ""}^(${sup ? ommlNodeToText(sup) : ""})`;
242
+ }
243
+ if (node.localName === "sSub") {
244
+ const e = queryByLocalName(node, "e");
245
+ const sub = queryByLocalName(node, "sub");
246
+ return `${e ? ommlNodeToText(e) : ""}_(${sub ? ommlNodeToText(sub) : ""})`;
247
+ }
248
+ if (node.localName === "rad") {
249
+ const e = queryByLocalName(node, "e");
250
+ return `sqrt(${e ? ommlNodeToText(e) : ""})`;
251
+ }
252
+ return Array.from(node.children).map((child) => ommlNodeToText(child)).join("");
253
+ }
153
254
  function runStyleToCss(rPr) {
154
255
  if (!rPr) return "";
155
256
  const declarations = [];
@@ -268,44 +369,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
268
369
  const tag = paragraphTag(paragraph);
269
370
  const alignStyle = paragraphAlignStyle(paragraph);
270
371
  const dataAttr = paragraphDataAttr(paragraphIndex);
271
- const runs = queryAllByLocalName(paragraph, "r");
272
- if (runs.length === 0) {
372
+ const hasRenderableNode = queryAllByLocalName(paragraph, "r").length > 0 || queryAllByLocalName(paragraph, "oMath").length > 0 || queryAllByLocalName(paragraph, "oMathPara").length > 0;
373
+ if (!hasRenderableNode) {
273
374
  return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}><br/></${tag}>`;
274
375
  }
275
- const parts = [];
276
- const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
277
- for (let i = 0; i < renderedPageBreakCount; i += 1) {
278
- parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
376
+ function parseRevisionMeta(node, type) {
377
+ return {
378
+ type,
379
+ id: getAttr(node, "w:id") ?? getAttr(node, "id"),
380
+ author: getAttr(node, "w:author") ?? getAttr(node, "author"),
381
+ date: getAttr(node, "w:date") ?? getAttr(node, "date")
382
+ };
383
+ }
384
+ function inferRevisionMeta(run, fallback) {
385
+ if (fallback) return fallback;
386
+ let cursor = run;
387
+ while (cursor) {
388
+ if (cursor.localName === "ins") return parseRevisionMeta(cursor, "ins");
389
+ if (cursor.localName === "del") return parseRevisionMeta(cursor, "del");
390
+ if (cursor.localName === "p") break;
391
+ cursor = cursor.parentElement;
392
+ }
393
+ return null;
279
394
  }
280
- for (const run of runs) {
395
+ function revisionMetaAttrs(meta) {
396
+ const attrs = [`data-word-revision="${meta.type}"`];
397
+ if (meta.id) attrs.push(`data-word-revision-id="${escapeHtml(meta.id)}"`);
398
+ if (meta.author) attrs.push(`data-word-revision-author="${escapeHtml(meta.author)}"`);
399
+ if (meta.date) attrs.push(`data-word-revision-date="${escapeHtml(meta.date)}"`);
400
+ return attrs.join(" ");
401
+ }
402
+ async function runToHtml(run, revisionFallback) {
403
+ const result = [];
281
404
  const rPr = queryByLocalName(run, "rPr");
282
405
  const css = runStyleToCss(rPr);
283
406
  const footnoteRef = queryByLocalName(run, "footnoteReference");
284
407
  const footnoteId = getAttr(footnoteRef, "w:id") ?? getAttr(footnoteRef, "id");
285
408
  if (footnoteId && footnotesMap[footnoteId]) {
286
409
  usedFootnoteIds.push(footnoteId);
287
- parts.push(
410
+ result.push(
288
411
  `<sup data-word-footnote-ref="${footnoteId}"><a href="#word-footnote-${footnoteId}">[${footnoteId}]</a></sup>`
289
412
  );
290
- continue;
413
+ return result;
291
414
  }
292
415
  const endnoteRef = queryByLocalName(run, "endnoteReference");
293
416
  const endnoteId = getAttr(endnoteRef, "w:id") ?? getAttr(endnoteRef, "id");
294
417
  if (endnoteId && endnotesMap[endnoteId]) {
295
418
  usedEndnoteIds.push(endnoteId);
296
- parts.push(
419
+ result.push(
297
420
  `<sup data-word-endnote-ref="${endnoteId}"><a href="#word-endnote-${endnoteId}">[${endnoteId}]</a></sup>`
298
421
  );
299
- continue;
422
+ return result;
300
423
  }
301
424
  const commentRef = queryByLocalName(run, "commentReference");
302
425
  const commentId = getAttr(commentRef, "w:id") ?? getAttr(commentRef, "id");
303
426
  if (commentId && commentsMap[commentId]) {
304
427
  usedCommentIds.push(commentId);
305
- parts.push(
428
+ result.push(
306
429
  `<sup data-word-comment-ref="${commentId}"><a href="#word-comment-${commentId}">[c${commentId}]</a></sup>`
307
430
  );
308
- continue;
431
+ return result;
309
432
  }
310
433
  const drawing = queryByLocalName(run, "drawing");
311
434
  if (drawing) {
@@ -316,13 +439,35 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
316
439
  if (src) {
317
440
  const imageSize = parseDrawingSizePx(drawing);
318
441
  const dimensionAttrs = imageDimensionAttributes(imageSize);
319
- const anchorPos = parseAnchorPositionPx(drawing);
320
- const wrapMode = parseAnchorWrapMode(drawing);
321
- const attrs = mergeImageStyle(dimensionAttrs, anchorPos, wrapMode);
322
- parts.push(`<img src="${src}" alt="word-image"${attrs}/>`);
323
- continue;
442
+ const anchorMeta = parseAnchorMeta(drawing);
443
+ const attrs = mergeImageStyle(dimensionAttrs, anchorMeta);
444
+ result.push(`<img src="${src}" alt="word-image"${attrs}/>`);
445
+ return result;
324
446
  }
325
447
  }
448
+ const chartRef = queryByLocalName(drawing, "chart");
449
+ const chartRid = getAttr(chartRef, "r:id") ?? getAttr(chartRef, "id");
450
+ if (chartRid) {
451
+ const chartXmlText = await readXmlByRid(zip, relMap, chartRid);
452
+ if (chartXmlText) {
453
+ const summary = parseChartSummary(chartXmlText);
454
+ result.push(
455
+ `<figure data-word-chart="1" data-word-chart-type="${summary.type}" data-word-chart-series="${summary.seriesCount}" data-word-chart-points="${summary.pointCount}"><figcaption>${escapeHtml(summary.title)}</figcaption><div>Chart(${escapeHtml(summary.type)}): series=${summary.seriesCount}, points=${summary.pointCount}</div></figure>`
456
+ );
457
+ return result;
458
+ }
459
+ }
460
+ const smartArtRef = queryByLocalName(drawing, "relIds");
461
+ const smartArtRid = getAttr(smartArtRef, "r:dm") ?? getAttr(smartArtRef, "dm");
462
+ if (smartArtRid) {
463
+ const diagramXmlText = await readXmlByRid(zip, relMap, smartArtRid);
464
+ const textItems = diagramXmlText ? extractSmartArtText(diagramXmlText) : [];
465
+ const preview = textItems.length > 0 ? `: ${escapeHtml(textItems.join(" / "))}` : "";
466
+ result.push(
467
+ `<figure data-word-smartart="1" data-word-smartart-items="${textItems.length}"><figcaption>SmartArt fallback${preview}</figcaption></figure>`
468
+ );
469
+ return result;
470
+ }
326
471
  }
327
472
  const texts = queryAllByLocalName(run, "t").map((t) => t.textContent ?? "").join("");
328
473
  const delTexts = queryAllByLocalName(run, "delText").map((t) => t.textContent ?? "").join("");
@@ -333,40 +478,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
333
478
  }).length;
334
479
  const lineBreakCount = Math.max(0, brNodes.length - pageBreakCount);
335
480
  const runText2 = `${escapeHtml(texts || delTexts)}${"<br/>".repeat(lineBreakCount)}`;
336
- if (!runText2) continue;
337
- let revisionType = null;
338
- let cursor = run;
339
- while (cursor) {
340
- if (cursor.localName === "ins") {
341
- revisionType = "ins";
342
- break;
343
- }
344
- if (cursor.localName === "del") {
345
- revisionType = "del";
346
- break;
347
- }
348
- if (cursor.localName === "p") break;
349
- cursor = cursor.parentElement;
350
- }
351
- if (css) {
352
- const span = `<span style="${css}">${runText2}</span>`;
353
- if (revisionType) {
354
- const tag2 = revisionType === "ins" ? "ins" : "del";
355
- parts.push(`<${tag2} data-word-revision="${revisionType}">${span}</${tag2}>`);
356
- } else {
357
- parts.push(span);
358
- }
359
- } else {
360
- if (revisionType) {
361
- const tag2 = revisionType === "ins" ? "ins" : "del";
362
- parts.push(`<${tag2} data-word-revision="${revisionType}">${runText2}</${tag2}>`);
481
+ if (runText2) {
482
+ const revisionMeta = inferRevisionMeta(run, revisionFallback);
483
+ if (css) {
484
+ const span = `<span style="${css}">${runText2}</span>`;
485
+ if (revisionMeta) {
486
+ const tagName = revisionMeta.type === "ins" ? "ins" : "del";
487
+ result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${span}</${tagName}>`);
488
+ } else {
489
+ result.push(span);
490
+ }
491
+ } else if (revisionMeta) {
492
+ const tagName = revisionMeta.type === "ins" ? "ins" : "del";
493
+ result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${runText2}</${tagName}>`);
363
494
  } else {
364
- parts.push(runText2);
495
+ result.push(runText2);
365
496
  }
366
497
  }
367
498
  for (let i = 0; i < pageBreakCount; i += 1) {
368
- parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
499
+ result.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
500
+ }
501
+ return result;
502
+ }
503
+ async function nodeToHtml(node, revisionFallback) {
504
+ if (node.localName === "commentRangeStart") {
505
+ const id = getAttr(node, "w:id") ?? getAttr(node, "id");
506
+ return id ? [`<span data-word-comment-range-start="${id}"></span>`] : [];
507
+ }
508
+ if (node.localName === "commentRangeEnd") {
509
+ const id = getAttr(node, "w:id") ?? getAttr(node, "id");
510
+ return id ? [`<span data-word-comment-range-end="${id}"></span>`] : [];
511
+ }
512
+ if (node.localName === "r") {
513
+ return runToHtml(node, revisionFallback);
369
514
  }
515
+ if (node.localName === "oMath" || node.localName === "oMathPara") {
516
+ const linear = ommlNodeToText(node).trim();
517
+ if (!linear) return [];
518
+ return [`<span data-word-omml="1">${escapeHtml(linear)}</span>`];
519
+ }
520
+ if (node.localName === "ins" || node.localName === "del") {
521
+ const scopedMeta = parseRevisionMeta(node, node.localName === "ins" ? "ins" : "del");
522
+ const nested2 = [];
523
+ for (const child of Array.from(node.children)) {
524
+ nested2.push(...await nodeToHtml(child, scopedMeta));
525
+ }
526
+ return nested2;
527
+ }
528
+ const nested = [];
529
+ for (const child of Array.from(node.children)) {
530
+ nested.push(...await nodeToHtml(child, revisionFallback));
531
+ }
532
+ return nested;
533
+ }
534
+ const parts = [];
535
+ const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
536
+ for (let i = 0; i < renderedPageBreakCount; i += 1) {
537
+ parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
538
+ }
539
+ for (const child of Array.from(paragraph.children)) {
540
+ parts.push(...await nodeToHtml(child, null));
370
541
  }
371
542
  const content = parts.join("") || "<br/>";
372
543
  return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}>${content}</${tag}>`;
@@ -396,6 +567,101 @@ function parseTcVMerge(tc) {
396
567
  const rawVal = (getAttr(vMerge, "w:val") ?? getAttr(vMerge, "val") ?? "continue").toLowerCase();
397
568
  return rawVal === "restart" ? "restart" : "continue";
398
569
  }
570
+ function parseTblGridWidthsPx(table) {
571
+ const grid = directChildrenByLocalName(table, "tblGrid")[0] ?? null;
572
+ if (!grid) return [];
573
+ return directChildrenByLocalName(grid, "gridCol").map((col) => {
574
+ const raw = getAttr(col, "w:w") ?? getAttr(col, "w");
575
+ const twip = raw ? Number.parseInt(raw, 10) : Number.NaN;
576
+ return Number.isFinite(twip) && twip > 0 ? twipToPx(twip) : 0;
577
+ }).filter((px) => px > 0);
578
+ }
579
+ function borderSizeToPx(size) {
580
+ return size / 6;
581
+ }
582
+ function parseBorderCss(borderNode) {
583
+ if (!borderNode) return null;
584
+ const val = (getAttr(borderNode, "w:val") ?? getAttr(borderNode, "val") ?? "").toLowerCase();
585
+ if (!val || val === "nil" || val === "none") return "none";
586
+ const color = (getAttr(borderNode, "w:color") ?? getAttr(borderNode, "color") ?? "222222").replace(/^#/, "");
587
+ const rawSize = getAttr(borderNode, "w:sz") ?? getAttr(borderNode, "sz");
588
+ const size = rawSize ? Number.parseInt(rawSize, 10) : Number.NaN;
589
+ const px = Number.isFinite(size) && size > 0 ? borderSizeToPx(size) : 1;
590
+ const style = val === "single" ? "solid" : val;
591
+ return `${px.toFixed(2)}px ${style} #${color}`;
592
+ }
593
+ function parseTableStyleProfile(table) {
594
+ const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
595
+ const tblBorders = tblPr ? directChildrenByLocalName(tblPr, "tblBorders")[0] ?? null : null;
596
+ const layout = tblPr ? directChildrenByLocalName(tblPr, "tblLayout")[0] ?? null : null;
597
+ const spacing = tblPr ? directChildrenByLocalName(tblPr, "tblCellSpacing")[0] ?? null : null;
598
+ const spacingType = (getAttr(spacing, "w:type") ?? getAttr(spacing, "type") ?? "dxa").toLowerCase();
599
+ const spacingRaw = getAttr(spacing, "w:w") ?? getAttr(spacing, "w");
600
+ const spacingVal = spacingRaw ? Number.parseFloat(spacingRaw) : Number.NaN;
601
+ const borderSpacingPx = spacingType === "dxa" && Number.isFinite(spacingVal) && spacingVal > 0 ? twipToPx(spacingVal) : 0;
602
+ const borderCollapse = borderSpacingPx > 0 ? "separate" : "collapse";
603
+ const tableLayout = (getAttr(layout, "w:type") ?? getAttr(layout, "type") ?? "").toLowerCase() === "autofit" ? "auto" : "fixed";
604
+ const top = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "top")[0] ?? null : null);
605
+ const bottom = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "bottom")[0] ?? null : null);
606
+ const left = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "left")[0] ?? null : null);
607
+ const right = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "right")[0] ?? null : null);
608
+ const insideH = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideH")[0] ?? null : null);
609
+ const insideV = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideV")[0] ?? null : null);
610
+ const borderCss = top ?? right ?? bottom ?? left ?? "1px solid #222";
611
+ return {
612
+ tableLayout,
613
+ borderCollapse,
614
+ borderSpacingPx,
615
+ borderCss,
616
+ insideHCss: insideH,
617
+ insideVCss: insideV
618
+ };
619
+ }
620
+ function parseTableWidthStyle(table, gridWidthsPx) {
621
+ const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
622
+ const tblW = tblPr ? directChildrenByLocalName(tblPr, "tblW")[0] ?? null : null;
623
+ const type = (getAttr(tblW, "w:type") ?? getAttr(tblW, "type") ?? "").toLowerCase();
624
+ const rawVal = getAttr(tblW, "w:w") ?? getAttr(tblW, "w");
625
+ const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
626
+ if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
627
+ return `width:${twipToPx(numericVal).toFixed(2)}px`;
628
+ }
629
+ if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
630
+ return `width:${(numericVal / 50).toFixed(2)}%`;
631
+ }
632
+ const gridTotal = gridWidthsPx.reduce((sum, item) => sum + item, 0);
633
+ if (gridTotal > 0) return `width:${gridTotal.toFixed(2)}px;max-width:100%`;
634
+ return "width:100%";
635
+ }
636
+ function parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx) {
637
+ const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
638
+ const tcW = tcPr ? directChildrenByLocalName(tcPr, "tcW")[0] ?? null : null;
639
+ const type = (getAttr(tcW, "w:type") ?? getAttr(tcW, "type") ?? "").toLowerCase();
640
+ const rawVal = getAttr(tcW, "w:w") ?? getAttr(tcW, "w");
641
+ const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
642
+ if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
643
+ return `width:${twipToPx(numericVal).toFixed(2)}px`;
644
+ }
645
+ if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
646
+ return `width:${(numericVal / 50).toFixed(2)}%`;
647
+ }
648
+ const width = gridWidthsPx.slice(colCursor, colCursor + colSpan).reduce((sum, item) => sum + item, 0);
649
+ if (width > 0) return `width:${width.toFixed(2)}px`;
650
+ return "";
651
+ }
652
+ function parseCellBorderStyle(cell, tableStyle) {
653
+ const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
654
+ const tcBorders = tcPr ? directChildrenByLocalName(tcPr, "tcBorders")[0] ?? null : null;
655
+ if (!tcBorders) {
656
+ const fallback = tableStyle.insideHCss ?? tableStyle.insideVCss ?? tableStyle.borderCss;
657
+ return `border:${fallback}`;
658
+ }
659
+ const top = parseBorderCss(directChildrenByLocalName(tcBorders, "top")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
660
+ const right = parseBorderCss(directChildrenByLocalName(tcBorders, "right")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
661
+ const bottom = parseBorderCss(directChildrenByLocalName(tcBorders, "bottom")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
662
+ const left = parseBorderCss(directChildrenByLocalName(tcBorders, "left")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
663
+ return `border-top:${top};border-right:${right};border-bottom:${bottom};border-left:${left}`;
664
+ }
399
665
  function tableCellHtml(cell, paragraphIndexMap) {
400
666
  const blocks = [];
401
667
  for (const child of Array.from(cell.children)) {
@@ -416,6 +682,8 @@ function tableCellHtml(cell, paragraphIndexMap) {
416
682
  }
417
683
  function tableToHtml(table, paragraphIndexMap) {
418
684
  const rows = directChildrenByLocalName(table, "tr");
685
+ const gridWidthsPx = parseTblGridWidthsPx(table);
686
+ const tableStyle = parseTableStyleProfile(table);
419
687
  const activeByCol = /* @__PURE__ */ new Map();
420
688
  const allOrigins = [];
421
689
  let nextOriginId = 1;
@@ -442,6 +710,8 @@ function tableToHtml(table, paragraphIndexMap) {
442
710
  }
443
711
  const html = tableCellHtml(cell, paragraphIndexMap);
444
712
  const attrs = [];
713
+ const widthStyle = parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx);
714
+ const borderStyle = parseCellBorderStyle(cell, tableStyle);
445
715
  if (vMerge === "restart") {
446
716
  const origin = {
447
717
  id: `m${nextOriginId}`,
@@ -459,7 +729,7 @@ function tableToHtml(table, paragraphIndexMap) {
459
729
  }
460
730
  if (colSpan > 1) attrs.push(`colspan="${colSpan}"`);
461
731
  emittedCells.push(
462
- `<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="border:1px solid #222;vertical-align:top;">${html}</td>`
732
+ `<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${borderStyle};vertical-align:top;${widthStyle}">${html}</td>`
463
733
  );
464
734
  colCursor += colSpan;
465
735
  }
@@ -478,7 +748,9 @@ function tableToHtml(table, paragraphIndexMap) {
478
748
  const replacement = origin.rowSpan > 1 ? `rowspan="${origin.rowSpan}"` : "";
479
749
  merged = merged.replace(marker, replacement).replace(/\s{2,}/g, " ");
480
750
  }
481
- return `<table style="border-collapse:collapse;table-layout:fixed;width:100%;border:1px solid #222;">${merged}</table>`;
751
+ const tableWidthStyle = parseTableWidthStyle(table, gridWidthsPx);
752
+ const spacing = tableStyle.borderSpacingPx > 0 ? `border-spacing:${tableStyle.borderSpacingPx.toFixed(2)}px;` : "";
753
+ return `<table style="border-collapse:${tableStyle.borderCollapse};${spacing}table-layout:${tableStyle.tableLayout};${tableWidthStyle};border:${tableStyle.borderCss};">${merged}</table>`;
482
754
  }
483
755
  async function parseDocxToHtmlSnapshot(file) {
484
756
  const maybeArrayBuffer = file.arrayBuffer;
@@ -768,7 +1040,7 @@ function createFallbackWordStyleProfile(sourceFileName = "snapshot") {
768
1040
  paragraphProfiles: []
769
1041
  };
770
1042
  }
771
- function twipToPx(twip) {
1043
+ function twipToPx2(twip) {
772
1044
  return twip / 15;
773
1045
  }
774
1046
  function getAttr2(node, attr) {
@@ -812,10 +1084,10 @@ function parsePageGeometry(documentXml) {
812
1084
  const top = getTwipAttr(pgMar, "w:top") ?? getTwipAttr(pgMar, "top") ?? null;
813
1085
  const bottom = getTwipAttr(pgMar, "w:bottom") ?? getTwipAttr(pgMar, "bottom") ?? null;
814
1086
  return {
815
- contentWidthPx: pageW === null ? null : twipToPx(pageW - left - right),
816
- pageHeightPx: pageH === null ? null : twipToPx(pageH),
817
- marginTopPx: top === null ? null : twipToPx(top),
818
- marginBottomPx: bottom === null ? null : twipToPx(bottom)
1087
+ contentWidthPx: pageW === null ? null : twipToPx2(pageW - left - right),
1088
+ pageHeightPx: pageH === null ? null : twipToPx2(pageH),
1089
+ marginTopPx: top === null ? null : twipToPx2(top),
1090
+ marginBottomPx: bottom === null ? null : twipToPx2(bottom)
819
1091
  };
820
1092
  }
821
1093
  function parseHeadingAlignFromDocument(documentXml) {
@@ -988,15 +1260,15 @@ function parseParagraphProfiles(documentXml, numberingMap) {
988
1260
  text,
989
1261
  isEmpty: text.length === 0,
990
1262
  align: parseParagraphAlign(paragraph),
991
- beforePx: before === null ? null : twipToPx(before),
992
- afterPx: after === null ? null : twipToPx(after),
1263
+ beforePx: before === null ? null : twipToPx2(before),
1264
+ afterPx: after === null ? null : twipToPx2(after),
993
1265
  lineHeightRatio: line === null || lineHeightRule !== "auto" ? null : line / 240,
994
- lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx(line),
1266
+ lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx2(line),
995
1267
  lineHeightRule,
996
- indentLeftPx: left === null ? null : twipToPx(left),
997
- indentRightPx: right === null ? null : twipToPx(right),
998
- firstLinePx: firstLine === null ? null : twipToPx(firstLine),
999
- hangingPx: hanging === null ? null : twipToPx(hanging),
1268
+ indentLeftPx: left === null ? null : twipToPx2(left),
1269
+ indentRightPx: right === null ? null : twipToPx2(right),
1270
+ firstLinePx: firstLine === null ? null : twipToPx2(firstLine),
1271
+ hangingPx: hanging === null ? null : twipToPx2(hanging),
1000
1272
  listNumId,
1001
1273
  listLevel,
1002
1274
  listFormat: listSpec?.numFmt ?? null,
@@ -1031,19 +1303,19 @@ function parseTableDefaults(stylesXml) {
1031
1303
  return {
1032
1304
  topPx: (() => {
1033
1305
  const v = getTwipAttr(top, "w:w") ?? getTwipAttr(top, "w") ?? null;
1034
- return v === null ? null : twipToPx(v);
1306
+ return v === null ? null : twipToPx2(v);
1035
1307
  })(),
1036
1308
  leftPx: (() => {
1037
1309
  const v = getTwipAttr(left, "w:w") ?? getTwipAttr(left, "w") ?? null;
1038
- return v === null ? null : twipToPx(v);
1310
+ return v === null ? null : twipToPx2(v);
1039
1311
  })(),
1040
1312
  bottomPx: (() => {
1041
1313
  const v = getTwipAttr(bottom, "w:w") ?? getTwipAttr(bottom, "w") ?? null;
1042
- return v === null ? null : twipToPx(v);
1314
+ return v === null ? null : twipToPx2(v);
1043
1315
  })(),
1044
1316
  rightPx: (() => {
1045
1317
  const v = getTwipAttr(right, "w:w") ?? getTwipAttr(right, "w") ?? null;
1046
- return v === null ? null : twipToPx(v);
1318
+ return v === null ? null : twipToPx2(v);
1047
1319
  })()
1048
1320
  };
1049
1321
  }
@@ -1141,9 +1413,9 @@ function parseDefaults(stylesXml) {
1141
1413
  const rawLineRule = (getAttr2(spacing, "w:lineRule") ?? getAttr2(spacing, "lineRule") ?? "auto").toLowerCase();
1142
1414
  const bodyLineHeightRule = rawLineRule === "exact" ? "exact" : rawLineRule === "atleast" ? "atLeast" : "auto";
1143
1415
  const bodyLineHeightRatio = line === null || bodyLineHeightRule !== "auto" ? null : line / 240;
1144
- const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx(line);
1416
+ const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx2(line);
1145
1417
  const after = getTwipAttr(spacing, "w:after") ?? getTwipAttr(spacing, "after") ?? null;
1146
- const paragraphAfterPx = after === null ? null : twipToPx(after);
1418
+ const paragraphAfterPx = after === null ? null : twipToPx2(after);
1147
1419
  return { bodyFontPx, bodyLineHeightRatio, bodyLineHeightPx, bodyLineHeightRule, paragraphAfterPx };
1148
1420
  }
1149
1421
  function parseHeading1Style(stylesXml) {
@@ -1918,4 +2190,4 @@ export {
1918
2190
  DocsWordElement,
1919
2191
  defineDocsWordElement
1920
2192
  };
1921
- //# sourceMappingURL=chunk-PRPDJOB7.js.map
2193
+ //# sourceMappingURL=chunk-IBVWD4UO.js.map