From a167ef1ae1e341455d7c7a08c13af78e1292349c Mon Sep 17 00:00:00 2001 From: tallison Date: Sat, 10 Oct 2026 09:02:53 -0400 Subject: [PATCH] TIKA-4968: wrap OMML equations in span class=math in docx --- CHANGES.txt | 3 ++ .../microsoft/ooxml/FormattingTagManager.java | 37 +++++++++++++++--- .../ooxml/OOXMLTikaBodyPartHandler.java | 10 +++++ .../OOXMLWordAndPowerPointTextHandler.java | 12 ++++++ .../ooxml/XWPFBodyContentsHandler.java | 9 +++++ .../microsoft/ooxml/OOXMLDocxSAXTest.java | 8 ++++ .../test-documents/testWORD_omml.docx | Bin 0 -> 1067 bytes 7 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_omml.docx diff --git a/CHANGES.txt b/CHANGES.txt index 48dc600091e..c472aac5824 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,8 @@ Release 4.2.0 - unreleased + * The docx parser wraps OMML equations (m:oMath) in ; their + text was previously emitted as unmarked body text (TIKA-4968). + * Config properties whose value reaches the host (paths, executables, class names, URLs, credentials, operator-sized limits) are marked @OperatorOnly and refused in per-request JSON before any setter runs. Newly locked: pdf-parser's diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FormattingTagManager.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FormattingTagManager.java index 3e8ae2f0de8..392832cef1f 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FormattingTagManager.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FormattingTagManager.java @@ -44,8 +44,9 @@ class FormattingTagManager { private final XHTMLContentHandler xhtml; - // Outermost to innermost: hyperlink > bold > italic > strike > underline + // Outermost to innermost: hyperlink > math > bold > italic > strike > underline private String currentHyperlink = null; + private boolean isMath = false; private boolean wrapperHyperlinkActive = false; private boolean isBold = false; private boolean isItalics = false; @@ -65,7 +66,7 @@ void openHyperlink(String url) throws SAXException { if (url == null) { return; } - closeFormattingTags(); + closeInlineTags(); if (currentHyperlink != null) { xhtml.endElement("a"); } @@ -79,7 +80,7 @@ void openHyperlink(String url) throws SAXException { */ void closeHyperlink() throws SAXException { if (currentHyperlink != null && wrapperHyperlinkActive) { - closeFormattingTags(); + closeInlineTags(); xhtml.endElement("a"); currentHyperlink = null; wrapperHyperlinkActive = false; @@ -102,7 +103,7 @@ void applyFormatting(RunProperties runProperties) throws SAXException { if (!wrapperHyperlinkActive) { String newHyperlink = runProperties.getHlinkClickUrl(); if (!Objects.equals(newHyperlink, currentHyperlink)) { - closeFormattingTags(); + closeInlineTags(); if (currentHyperlink != null) { xhtml.endElement("a"); } @@ -178,11 +179,32 @@ void applyFormatting(RunProperties runProperties) throws SAXException { } } + /** + * Opens {@code } for an OMML equation. A hyperlink change inside + * the equation closes it. + */ + void openMath() throws SAXException { + if (isMath) { + return; + } + closeFormattingTags(); + xhtml.startElement("span", "class", "math"); + isMath = true; + } + + void closeMath() throws SAXException { + if (isMath) { + closeFormattingTags(); + xhtml.endElement("span"); + isMath = false; + } + } + /** * Closes all currently open tags in proper nesting order. */ void closeAll() throws SAXException { - closeFormattingTags(); + closeInlineTags(); if (currentHyperlink != null) { xhtml.endElement("a"); currentHyperlink = null; @@ -190,6 +212,11 @@ void closeAll() throws SAXException { } } + private void closeInlineTags() throws SAXException { + closeFormattingTags(); + closeMath(); + } + private void closeFormattingTags() throws SAXException { if (isUnderline) { xhtml.endElement("u"); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLTikaBodyPartHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLTikaBodyPartHandler.java index 0366ab96700..2098335b9b1 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLTikaBodyPartHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLTikaBodyPartHandler.java @@ -142,6 +142,16 @@ public void hyperlinkEnd() throws SAXException { formattingTags.closeHyperlink(); } + @Override + public void startMath() throws SAXException { + formattingTags.openMath(); + } + + @Override + public void endMath() throws SAXException { + formattingTags.closeMath(); + } + @Override public void startParagraph(ParagraphProperties paragraphProperties) throws SAXException { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java index e9aa3f001a8..df6a02d8d9b 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java @@ -97,6 +97,9 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler { "http://schemas.openxmlformats.org/drawingml/2006/main"; private final static String V_NS = "urn:schemas-microsoft-com:vml"; private final static String C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart"; + private final static String M_NS = + "http://schemas.openxmlformats.org/officeDocument/2006/math"; + private final static String O_MATH = "oMath"; private final static String OFFICE_DOC_RELATIONSHIP_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; private final static char[] TAB_CHAR = new char[]{'\t'}; @@ -170,6 +173,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler { // Field code tracking for instrText-based hyperlinks private boolean inField = false; private boolean inInstrText = false; + private int mathDepth = 0; private boolean inFieldHyperlink = false; private final StringBuilder instrTextBuffer = new StringBuilder(); private EditType editType = @@ -267,6 +271,10 @@ public void startElement(String uri, String localName, String qName, Attributes //yes, I know, likely premature optimization... if (RPR.equals(localName)) { inRPr = true; + } else if (O_MATH.equals(localName) && M_NS.equals(uri)) { + if (mathDepth++ == 0) { + bodyContentsHandler.startMath(); + } } else if (R.equals(localName)) { inR = true; } else if (T.equals(localName)) { @@ -555,6 +563,10 @@ public void endElement(String uri, String localName, String qName) throws SAXExc return; } else if (RPR.equals(localName)) { inRPr = false; + } else if (O_MATH.equals(localName) && M_NS.equals(uri)) { + if (mathDepth > 0 && --mathDepth == 0) { + bodyContentsHandler.endMath(); + } } else if (R.equals(localName)) { handleEndOfRun(); } else if (T.equals(localName)) { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFBodyContentsHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFBodyContentsHandler.java index a9eb400e988..401b0e720f8 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFBodyContentsHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFBodyContentsHandler.java @@ -46,6 +46,15 @@ default void fieldCodeHyperlinkStart(String link) throws SAXException { void hyperlinkEnd() throws SAXException; + /** + * Called at the start of an OMML equation ({@code m:oMath}); its runs follow. + */ + default void startMath() throws SAXException { + } + + default void endMath() throws SAXException { + } + void startParagraph(ParagraphProperties paragraphProperties) throws SAXException; void endParagraph() throws SAXException; diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java index 92cb9380587..569028deea6 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java @@ -144,6 +144,14 @@ public void testMissingText() throws Exception { assertContains("Rich_text_in_cell", xmlResult.xml); } + @Test + public void testOmmlMath() throws Exception { + String xml = getXML("testWORD_omml.docx").xml; + assertContains("

area A=πrr2 done

", xml); + assertContains("

x+y

", xml); + assertContains("

bold z after

", xml); + } + @Test public void testBoldHyperlink() throws Exception { String xml = getXML("testWORD_boldHyperlink.docx").xml; diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_omml.docx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_omml.docx new file mode 100644 index 0000000000000000000000000000000000000000..bde754a6706a810f96940f6675ecab3d087791ef GIT binary patch literal 1067 zcmWIWW@Zs#U|`^2_~qsmd(vNO!)qXK9U}vSFp!RR&d)1J%`1rysVqn>j@7Hk&8ZF8 z>vz~d;9e!yY3(jq2>~Zhs}IIZ&Xo<5Ydv+|Y<^no{rj6m?#l+Q$9KQ4ULXCoNj&<@ z8>O$G+_nZO%*>GW-TNnEzsdKY0>(`bloR#8pC~eoDSch*Bo^v(=k(4gUly3omSo=0 z#ko=``3$3%?dK)JGyG=++~4+guGvJcpv+T`6x-i%y5IPrG_U+?tzQ*)_H|8a}RE=EA+OMN?A9Vef>EP26y|vUlZRwlny`%o1Kj=g&Tljq6G{a zZXg|Bl$ukluLmM~&s*~yGT>o*FgMucUS>Wbpy%};|hZb*FXJ%Gl zbGyCY{&ep54aPeYeU4eP`Gzt(s_!-OGq&JA{*}+;)FDm7>UJJ6p9>#Ds}Ds!*)&;A zSV*(On`haa!}BhwsBcjUwOgP)`J%gak>=uMjwiY=c$k;&UHb7Z!@tByafTgxvl@83 z57g(__qjbfczU(#^!hnJyhI{hdS@QI>+X7Wb*!kEM*hp=-G4YyJ!Y3KD26iG9q2XBUGih$ z*4ta={BW-0v@Ye=@yh9*!D~gb&NDTaMTUP^`rVAV;HuEn0_Ehu_PAURPI<2=hjfo9 zBT*}Hy#QUGV~?c+7G1QtxMKf>8N2ed_V_b>Q>i<7dO>#H6SFjF=A#$9&&&}Gl{g?| zwZ!yOkO0>-=NHor*;br7lf0m_d*7$0u@jqLF=nn-UlKY0*#>J1?+4$vzkd*M`Q*0h zsGBni1(;X9EIZILb57DPvp1X%75+cpIg>}f(!^Y4`SK<(yq$Q?g6a2tmihPh{$U94W@Hj!#+|!>VGRZiz`)0v z*U$|>Pl6Eb3=9p7GC(7c5+u4t^e9GX{0h{F5#a&etZX19%s{vkNXGzE1_J{CmJX%n literal 0 HcmV?d00001