You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/10/18 16:50:57 UTC

(tika) branch main updated: TIKA-4968: wrap OMML equations in span class=math in docx (#3318)

This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new eb024f1e60 TIKA-4968: wrap OMML equations in span class=math in docx (#3318)
eb024f1e60 is described below

commit eb024f1e6099d63b4bed75ca770b191f3be030db
Author: Tim Allison <ta...@apache.org>
AuthorDate: Sun Oct 18 17:50:51 2026 +0100

    TIKA-4968: wrap OMML equations in span class=math in docx (#3318)
---
 CHANGES.txt                                        |   5 +++-
 .../microsoft/ooxml/FormattingTagManager.java      |  31 +++++++++++++++++++--
 .../microsoft/ooxml/OOXMLTikaBodyPartHandler.java  |  10 +++++++
 .../ooxml/OOXMLWordAndPowerPointTextHandler.java   |  12 ++++++++
 .../microsoft/ooxml/XWPFBodyContentsHandler.java   |   9 ++++++
 .../parser/microsoft/ooxml/OOXMLDocxSAXTest.java   |   8 ++++++
 .../resources/test-documents/testWORD_omml.docx    | Bin 0 -> 1067 bytes
 7 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 1b5aaf91e5..16556bfc19 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,12 +1,15 @@
 Release 4.2.0 - unreleased
 
+   * The docx parser wraps OMML equations (m:oMath) in <span class="math">; their
+     text was previously emitted as unmarked body text (TIKA-4968).
+
    * The docx parser links REF, PAGEREF and NOTEREF fields, and in-document
      HYPERLINK \l fields, to their bookmarks (<a href="#bookmark">). Fields are now
      tracked as a stack, so a field nested in a HYPERLINK field no longer ends the
      enclosing link early. Simple fields (w:fldSimple) are now parsed as well, so
      HYPERLINK, INCLUDEPICTURE and similar fields written that way are linked or
      reported like their complex-field equivalents (TIKA-4967).
-     
+
    * The docx parser now applies bold and italics set by a run's character style
      (e.g. Word's built-in Strong and Emphasis), following basedOn chains; previously
      only direct formatting was emitted. The new OfficeParserConfig option
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 8717b524aa..9c77492064 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 > span > bold > italic > strike > underline
+    // Outermost to innermost: hyperlink > math > span > bold > italic > strike > underline
     private String currentHyperlink = null;
+    private boolean isMath = false;
     private String currentSpanClass = null;
     private boolean wrapperHyperlinkActive = false;
     private boolean isBold = false;
@@ -123,7 +124,7 @@ class FormattingTagManager {
         }
 
         if (!Objects.equals(spanClass, currentSpanClass)) {
-            closeInlineTags();
+            closeStyledTags();
             if (spanClass != null) {
                 xhtml.startElement("span", "class", spanClass);
             }
@@ -195,6 +196,27 @@ class FormattingTagManager {
         }
     }
 
+    /**
+     * Opens {@code <span class="math">} for an OMML equation. A hyperlink change inside
+     * the equation closes it.
+     */
+    void openMath() throws SAXException {
+        if (isMath) {
+            return;
+        }
+        closeStyledTags();
+        xhtml.startElement("span", "class", "math");
+        isMath = true;
+    }
+
+    void closeMath() throws SAXException {
+        if (isMath) {
+            closeStyledTags();
+            xhtml.endElement("span");
+            isMath = false;
+        }
+    }
+
     /**
      * Closes all currently open tags in proper nesting order.
      */
@@ -208,6 +230,11 @@ class FormattingTagManager {
     }
 
     private void closeInlineTags() throws SAXException {
+        closeStyledTags();
+        closeMath();
+    }
+
+    private void closeStyledTags() throws SAXException {
         closeFormattingTags();
         if (currentSpanClass != null) {
             xhtml.endElement("span");
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 7df2342fda..f849f92d70 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
@@ -168,6 +168,16 @@ public class OOXMLTikaBodyPartHandler
         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 281c2ed186..569b4065ae 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
@@ -99,6 +99,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'};
@@ -178,6 +181,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
     private final Deque<FieldFrame> fieldStack = new ArrayDeque<>();
     private int hyperlinkElementDepth = 0;
     private boolean inInstrText = false;
+    private int mathDepth = 0;
     private EditType editType =
             EditType.NONE;
 
@@ -273,6 +277,10 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
         //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)) {
@@ -554,6 +562,10 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
             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 a9eb400e98..401b0e720f 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 @@ public interface XWPFBodyContentsHandler {
 
     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 80590ec5e4..03ba3d3b3c 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
@@ -145,6 +145,14 @@ public class OOXMLDocxSAXTest extends TikaTest {
         assertContains("Rich_text_in_cell", xmlResult.xml);
     }
 
+    @Test
+    public void testOmmlMath() throws Exception {
+        String xml = getXML("testWORD_omml.docx").xml;
+        assertContains("<p>area <span class=\"math\">A=<b>π</b>rr2</span> done</p>", xml);
+        assertContains("<p><span class=\"math\">x+y</span></p>", xml);
+        assertContains("<p><b>bold </b><span class=\"math\">z</span><b> after</b></p>", 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 0000000000..bde754a670
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_omml.docx differ