Respect DOCX underline none on import

This commit is contained in:
Курнат Андрей
2026-06-02 12:53:58 +03:00
parent 81a0de2ba9
commit e857beabd0
2 changed files with 41 additions and 1 deletions
+10 -1
View File
@@ -1493,7 +1493,7 @@ function docxRunToHtml(run: unknown, imageDataByRelationshipId: DocxImageDataByR
if (hasDocxBoolean(properties, "w:i")) {
html = `<em>${html}</em>`;
}
if (hasDocxBoolean(properties, "w:u")) {
if (hasDocxUnderline(properties)) {
html = `<u>${html}</u>`;
}
if (hasStrike) {
@@ -1707,6 +1707,15 @@ function hasDocxBoolean(properties: XmlRecord, key: string) {
return !["0", "false", "off"].includes(value.toLowerCase());
}
function hasDocxUnderline(properties: XmlRecord) {
if (!Object.prototype.hasOwnProperty.call(properties, "w:u")) {
return false;
}
const value = (docxAttribute(childRecord(properties, "w:u"), "w:val") || docxAttribute(childRecord(properties, "w:u"), "val")).trim().toLowerCase();
return !["0", "false", "off", "none"].includes(value);
}
function childRecord(value: unknown, key: string): XmlRecord {
const child = asRecord(value)[key];
if (Array.isArray(child)) {
+31
View File
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import JSZip from "jszip";
import { exportDocxBlob, htmlToWordBlocks, importDocxFile } from "./wordOffice";
const tinyPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
@@ -128,6 +129,36 @@ describe("wordOffice DOCX import", () => {
]);
});
it("does not import DOCX underline when w:u val is none", async () => {
const sourceBlob = await exportDocxBlob("UnderlineNone.docx", '<p><span style="color: #0f5fae;">Not underlined</span></p>');
const zip = await JSZip.loadAsync(await sourceBlob.arrayBuffer());
const documentXml = (await zip.file("word/document.xml")?.async("string")) ?? "";
const patchedDocumentXml = documentXml.replace(/(<w:rPr\b[^>]*>[\s\S]*?)(<\/w:rPr>)/, '$1<w:u w:val="none"/>$2');
expect(patchedDocumentXml).toContain('<w:u w:val="none"/>');
zip.file("word/document.xml", patchedDocumentXml);
const patchedBlob = await zip.generateAsync({
type: "blob",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(
new File([patchedBlob], "UnderlineNone.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
})
);
expect(imported.html).not.toContain("<u>Not underlined</u>");
expect(imported.html).not.toContain("<u>");
expect(imported.html).toContain("color: #0F5FAE;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Not underlined",
runs: [{ text: "Not underlined", color: "0F5FAE" }]
}
]);
});
it("imports DOCX subscript and superscript from OOXML", async () => {
const blob = await exportDocxBlob("Indexes.docx", "<p>H<sub>2</sub>O m<sup>2</sup></p>");
const file = new File([blob], "Indexes.docx", {