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