Preserve QWord DOCX tables on import

This commit is contained in:
Курнат Андрей
2026-06-01 08:05:14 +03:00
parent 95be5e3a8a
commit 9ad29a184b
3 changed files with 78 additions and 13 deletions
+51 -12
View File
@@ -601,14 +601,14 @@ async function styledDocxHtmlFromArrayBuffer(arrayBuffer: ArrayBuffer) {
const relationshipsXml = await zip.file("word/_rels/document.xml.rels")?.async("string");
const hyperlinkTargets = relationshipsXml ? docxHyperlinkTargetsById(parser.parse(relationshipsXml)) : {};
const result = orderedDocxDocumentToHtml(orderedParser.parse(documentXml), hyperlinkTargets);
return result.hasInlineColorOrHighlight || result.hasParagraphAlignment ? result.html : "";
return result.hasInlineColorOrHighlight || result.hasParagraphAlignment || result.hasTable ? result.html : "";
} catch {
return "";
}
}
function isSimpleStyledDocxDocument(documentXml: string) {
return !/<w:(tbl|drawing|pict|numPr|sdt)\b/i.test(documentXml);
return !/<w:(drawing|pict|numPr|sdt)\b/i.test(documentXml);
}
function orderedDocxDocumentToHtml(documentXml: unknown, hyperlinkTargets: Record<string, string> = {}) {
@@ -616,19 +616,30 @@ function orderedDocxDocumentToHtml(documentXml: unknown, hyperlinkTargets: Recor
const body = firstOrderedElement(orderedElementChildren(document), "w:body");
let hasInlineColorOrHighlight = false;
let hasParagraphAlignment = false;
const paragraphs = orderedDirectElements(body, "w:p")
.map((paragraph) => {
const result = orderedDocxParagraphToHtml(paragraph, hyperlinkTargets);
hasInlineColorOrHighlight = hasInlineColorOrHighlight || result.hasInlineColorOrHighlight;
hasParagraphAlignment = hasParagraphAlignment || result.hasParagraphAlignment;
return result.html;
})
.filter(Boolean);
let hasTable = false;
const blocks = orderedElementChildren(body)
.flatMap((block) => {
const name = orderedElementName(block);
if (name === "w:p") {
const result = orderedDocxParagraphToHtml(block, hyperlinkTargets);
hasInlineColorOrHighlight = hasInlineColorOrHighlight || result.hasInlineColorOrHighlight;
hasParagraphAlignment = hasParagraphAlignment || result.hasParagraphAlignment;
return result.html ? [result.html] : [];
}
if (name === "w:tbl") {
const html = orderedDocxTableToHtml(block, hyperlinkTargets);
hasTable = hasTable || Boolean(html);
return html ? [html] : [];
}
return [];
});
return {
html: paragraphs.join(""),
html: blocks.join(""),
hasInlineColorOrHighlight,
hasParagraphAlignment
hasParagraphAlignment,
hasTable
};
}
@@ -656,6 +667,34 @@ function orderedDocxParagraphToHtml(paragraph: unknown, hyperlinkTargets: Record
};
}
function orderedDocxTableToHtml(table: unknown, hyperlinkTargets: Record<string, string>) {
const rows = orderedDirectElements(table, "w:tr")
.map((row) => {
const cells = orderedDirectElements(row, "w:tc")
.map((cell) => {
const paragraphs = orderedDirectElements(cell, "w:p")
.map((paragraph) => orderedDocxParagraphToHtml(paragraph, hyperlinkTargets).html)
.filter(Boolean);
const content = paragraphs.length > 0 ? paragraphs.join("") : escapeHtml(orderedDocxCellText(cell));
return content ? `<td>${content}</td>` : "<td></td>";
})
.join("");
return cells ? `<tr>${cells}</tr>` : "";
})
.filter(Boolean);
return rows.length > 0 ? `<table><tbody>${rows.join("")}</tbody></table>` : "";
}
function orderedDocxCellText(cell: unknown) {
return orderedDirectElements(cell, "w:p")
.flatMap((paragraph) => orderedDocxParagraphInlineRecords(paragraph, {}))
.map((inline) => docxRunText(asRecord(inline.run)))
.join(" ")
.trim();
}
function docxParagraphTagName(properties: XmlRecord) {
const styleValue = docxAttribute(childRecord(properties, "w:pStyle"), "w:val").toLowerCase();
if (["title", "heading1", "heading 1"].includes(styleValue)) {