diff options
| author | Michael Williamson <mike@zwobble.org> | 2025-06-02 18:50:31 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2025-06-02 18:50:31 +0100 |
| commit | ee76b524a54ff235416458abb8b17bedcb478eb8 (patch) | |
| tree | c639fa0f66fdbe78b4c76eb562cb42d751caa03e | |
| parent | 3f5dd013a9259827aef9c63d8c33ed4aab88cdc6 (diff) | |
Handle SDTs representing checkboxes wrapped in other elements
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | lib/docx/body-reader.js | 62 | ||||
| -rw-r--r-- | lib/transforms.js | 1 | ||||
| -rw-r--r-- | test/docx/body-reader.tests.js | 85 |
4 files changed, 137 insertions, 15 deletions
@@ -3,6 +3,10 @@ * Add "Heading" and "Body" styles, as found in documents created by Apple Pages, to the default style map. +* Handle structured document tags representing checkboxes wrapped in other + elements, such as table cells. Previously, the wrapping elements would have + been ignored. + # 1.9.1 * Ignore AlternateContent elements when there is no Fallback element. diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index 93144c3..2ab5ba7 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -8,6 +8,7 @@ var documents = require("../documents"); var Result = require("../results").Result; var warning = require("../results").warning; var xml = require("../xml"); +var transforms = require("../transforms"); var uris = require("./uris"); function createBodyReader(options) { @@ -385,21 +386,52 @@ function BodyReader(options) { }, "w:sdt": function(element) { - var checkbox = element - .firstOrEmpty("w:sdtPr") - .first("wordml:checkbox"); - - if (checkbox) { - var checkedElement = checkbox.first("wordml:checked"); - var isChecked = !!checkedElement && readBooleanAttributeValue( - checkedElement.attributes["wordml:val"] - ); - return elementResult(documents.checkbox({ - checked: isChecked - })); - } else { - return readXmlElements(element.firstOrEmpty("w:sdtContent").children); - } + var contentResult = readXmlElements(element.firstOrEmpty("w:sdtContent").children); + return contentResult.map(function(content) { + // From the WordML standard: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/3350cb64-931f-41f7-8824-f18b2568ce66 + // + // > A CT_SdtCheckbox element that specifies that the parent + // > structured document tag is a checkbox when displayed in the + // > document. The parent structured document tag contents MUST + // > contain a single character and optionally an additional + // > character in a deleted run. + + var checkbox = element + .firstOrEmpty("w:sdtPr") + .first("wordml:checkbox"); + + if (checkbox) { + var checkedElement = checkbox.first("wordml:checked"); + var isChecked = !!checkedElement && readBooleanAttributeValue( + checkedElement.attributes["wordml:val"] + ); + var documentCheckbox = documents.checkbox({ + checked: isChecked + }); + + var hasCheckbox = false; + var replacedContent = content.map(transforms._elementsOfType( + documents.types.text, + function(text) { + if (text.value.length > 0 && !hasCheckbox) { + hasCheckbox = true; + return documentCheckbox; + } else { + return text; + } + } + )); + + if (hasCheckbox) { + return replacedContent; + } else { + return documentCheckbox; + } + + } else { + return content; + } + }); }, "w:ins": readChildElements, diff --git a/lib/transforms.js b/lib/transforms.js index 01ba0d8..3c5cf73 100644 --- a/lib/transforms.js +++ b/lib/transforms.js @@ -3,6 +3,7 @@ var _ = require("underscore"); exports.paragraph = paragraph; exports.run = run; exports._elements = elements; +exports._elementsOfType = elementsOfType; exports.getDescendantsOfType = getDescendantsOfType; exports.getDescendants = getDescendants; diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 8f36998..9413b2e 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -724,6 +724,91 @@ test("checkboxes", { var result = readXmlElementValue(sdtXml); assertThat(result, isCheckbox({checked: equalTo(true)})); + }, + + "when structured document tag checkbox has sdtContent then checkbox replaces single character": function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row( + xml.element("w:sdt", {}, [ + xml.element("w:sdtPr", {}, [ + xml.element("wordml:checkbox", {}, [ + xml.element("wordml:checked", {"wordml:val": "1"}) + ]) + ]), + xml.element("w:sdtContent", {}, [ + xml.element("w:tc", {}, [ + xml.element("w:p", {}, [ + xml.element("w:r", {}, [ + xml.element("w:t", {}, [ + xml.text("☐") + ]) + ]) + ]) + ]) + ]) + ]) + ) + ]); + + var result = readXmlElementValue(tableXml); + + assert.deepEqual(result, new documents.Table([ + new documents.TableRow([ + new documents.TableCell([ + new documents.Paragraph([ + new documents.Run([ + new documents.Checkbox({checked: true}) + ]) + ]) + ]) + ]) + ])); + }, + + "when structured document tag checkbox has sdtContent then deleted content is ignored": function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row( + xml.element("w:sdt", {}, [ + xml.element("w:sdtPr", {}, [ + xml.element("wordml:checkbox", {}, [ + xml.element("wordml:checked", {"wordml:val": "1"}) + ]) + ]), + xml.element("w:sdtContent", {}, [ + xml.element("w:tc", {}, [ + xml.element("w:p", {}, [ + xml.element("w:r", {}, [ + xml.element("w:t", {}, [ + xml.text("☐") + ]) + ]), + xml.element("w:del", {}, [ + xml.element("w:r", {}, [ + xml.element("w:t", {}, [ + xml.text("☐") + ]) + ]) + ]) + ]) + ]) + ]) + ]) + ) + ]); + + var result = readXmlElementValue(tableXml); + + assert.deepEqual(result, new documents.Table([ + new documents.TableRow([ + new documents.TableCell([ + new documents.Paragraph([ + new documents.Run([ + new documents.Checkbox({checked: true}) + ]) + ]) + ]) + ]) + ])); } }); |
