Skip to content

Commit 0d830bc

Browse files
committed
TIKA-4744 - fix wordml tags
1 parent 77b754e commit 0d830bc

4 files changed

Lines changed: 1509 additions & 2 deletions

File tree

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/xml/AbstractXML2003Parser.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.tika.sax.EmbeddedContentHandler;
3939
import org.apache.tika.sax.TaggedContentHandler;
4040
import org.apache.tika.sax.TeeContentHandler;
41+
import org.apache.tika.sax.XHTMLBalancingHandler;
4142
import org.apache.tika.sax.XHTMLContentHandler;
4243
import org.apache.tika.utils.XMLReaderUtils;
4344

@@ -88,7 +89,15 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
8889
final XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata, context);
8990
xhtml.startDocument();
9091

91-
TaggedContentHandler tagged = new TaggedContentHandler(xhtml);
92+
// Wrap xhtml in a balancing handler so that if the inner SAX parser
93+
// aborts mid-element (e.g., on truncated input -- common in crawled
94+
// sources clipped to 1MB), we can drain the open elements before
95+
// the finally block calls xhtml.endDocument(). Without this drain
96+
// the trailing </body></html> mismatches the unclosed <p>/<tr>/etc.
97+
// left on the wire, and StrictXHTMLValidator masks the real parse
98+
// error with a misleading well-formedness exception.
99+
final XHTMLBalancingHandler balancer = new XHTMLBalancingHandler(xhtml);
100+
TaggedContentHandler tagged = new TaggedContentHandler(balancer);
92101
tis.setCloseShield();
93102
try {
94103
//need to get new SAXParser because
@@ -99,6 +108,8 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
99108
getContentHandler(tagged, metadata, context)));
100109
} catch (SAXException e) {
101110
WriteLimitReachedException.throwIfWriteLimitReached(e);
111+
// Close anything the aborted parse left open, then propagate.
112+
balancer.drainOpenElements();
102113
throw new TikaException("XML parse error", e);
103114
} finally {
104115
tis.removeCloseShield();

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/xml/WordMLParser.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,27 @@ public void startElement(String uri, String localName, String qName, Attributes
121121
//close p if already in a p to prevent nested <p>
122122
if (inP) {
123123
handler.endElement(XHTMLContentHandler.XHTML, P, P);
124+
inP = false;
124125
}
125-
inP = true;
126+
} else if (html.equals(TABLE) && inP) {
127+
// WordML allows <w:tbl> nested inside <w:p>. XHTML
128+
// does not allow a block-level table inside an
129+
// inline <p>: the inline <p> would still be on the
130+
// SAX stack when the table's <tr>/<td> children
131+
// (and their inner <w:p>'s) emit, and the next
132+
// "close p before nested p" event would land on top
133+
// of <td>/<tr> instead of <p>. Close the open <p>
134+
// before opening the table.
135+
handler.endElement(XHTMLContentHandler.XHTML, P, P);
136+
inP = false;
126137
}
127138
handler.startElement(XHTMLContentHandler.XHTML, html, html, EMPTY_ATTRS);
128139
if (html.equals(TABLE)) {
129140
handler.startElement(XHTMLContentHandler.XHTML, TBODY, TBODY, EMPTY_ATTRS);
130141
}
142+
if (P.equals(localName)) {
143+
inP = true;
144+
}
131145
}
132146
if (BR.equals(localName)) {
133147
handler.characters(NEWLINE, 0, 1);

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/xml/XML2003ParserTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ public void testBasicExcel() throws Exception {
6363

6464
}
6565

66+
@Test //TIKA-4744
67+
public void testTableInsideParagraph() throws Exception {
68+
// WordML allows <w:tbl> nested inside <w:p>. The XHTML emission must
69+
// close the <p> before opening the <table>, otherwise the inline
70+
// <p> stays on the SAX stack while <tr>/<td> emit -- and the inner
71+
// paragraphs inside cells trip the "close p before nested p" logic
72+
// emitting </p> while <td> is topmost.
73+
// getXML wraps the handler in StrictXHTMLValidator, so any imbalance
74+
// would throw before the assertions.
75+
XMLResult r = getXML("testWORDML_tableInsideParagraph.doc");
76+
assertEquals("application/vnd.ms-wordml", r.metadata.get(Metadata.CONTENT_TYPE));
77+
// Structural sanity: tables emit with tbody and rows.
78+
assertContains("<table><tbody>", r.xml);
79+
}
80+
6681
@Test
6782
@Timeout(60000)
6883
public void testMultiThreaded() throws Exception {

0 commit comments

Comments
 (0)