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/03/24 01:26:36 UTC
(tika) branch main updated: TIKA-4692 improve ooxml sax parsers (#2700)
This is an automated email from the ASF dual-hosted git repository.
tallison 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 cfa6becdc0 TIKA-4692 improve ooxml sax parsers (#2700)
cfa6becdc0 is described below
commit cfa6becdc0794c88d279e791fe98c2eaff011314
Author: Tim Allison <ta...@apache.org>
AuthorDate: Mon Mar 23 21:26:31 2026 -0400
TIKA-4692 improve ooxml sax parsers (#2700)
---
.../main/java/org/apache/tika/metadata/Office.java | 4 +
.../org/apache/tika/sax/XHTMLContentHandler.java | 1 -
.../main/java/org/apache/tika/utils/DateUtils.java | 3 +
.../tika/parser/microsoft/AbstractListManager.java | 2 +-
.../tika/parser/microsoft/OfficeParserConfig.java | 28 +
...aragraphProperties.java => CommentAuthors.java} | 59 +-
.../{xwpf/XWPFNumberingShim.java => EditType.java} | 21 +-
.../parser/microsoft/ooxml/FieldCodeParser.java | 109 ++
.../parser/microsoft/ooxml/InlineTagManager.java | 196 ++++
.../microsoft/ooxml/OOXMLInlineBodyPartMap.java | 82 ++
.../microsoft/ooxml/OOXMLPartContentCollector.java | 239 ++++
.../microsoft/ooxml/OOXMLPictureTracker.java | 99 ++
.../microsoft/ooxml/OOXMLTikaBodyPartHandler.java | 390 ++++---
.../ooxml/OOXMLWordAndPowerPointTextHandler.java | 333 ++----
.../microsoft/ooxml/ParagraphProperties.java | 9 +
.../parser/microsoft/ooxml/PlaceHolderSkipper.java | 72 ++
.../microsoft/ooxml/SAXBasedMetadataExtractor.java | 457 ++++++++
.../ooxml/SXSLFPowerPointExtractorDecorator.java | 401 +++----
.../ooxml/SXWPFWordExtractorDecorator.java | 243 +++-
.../microsoft/ooxml/XSLFCommentAuthorHandler.java | 54 +
.../microsoft/ooxml/XSLFCommentsHandler.java | 88 ++
.../microsoft/ooxml/XWPFBodyContentsHandler.java | 124 +++
.../parser/microsoft/ooxml/XWPFListManager.java | 122 +--
.../ooxml/XWPFWordExtractorDecorator.java | 24 +-
.../xslf/XSLFEventBasedPowerPointExtractor.java | 14 +-
.../ooxml/xwpf/XWPFEventBasedWordExtractor.java | 48 +-
.../microsoft/ooxml/xwpf/XWPFFeatureExtractor.java | 8 +-
.../microsoft/ooxml/xwpf/XWPFNumberingShim.java | 268 ++++-
...tractorTest.java => AbstractOOXMLDocxTest.java} | 862 +++++----------
...tractorTest.java => AbstractOOXMLPptxTest.java} | 343 +-----
.../parser/microsoft/ooxml/OOXMLDocxDOMTest.java | 133 +++
.../parser/microsoft/ooxml/OOXMLDocxSAXTest.java | 366 +++++++
.../parser/microsoft/ooxml/OOXMLParserTest.java | 1155 --------------------
.../parser/microsoft/ooxml/OOXMLPptxDOMTest.java | 204 ++++
.../parser/microsoft/ooxml/OOXMLPptxSAXTest.java | 272 +++++
.../resources/test-documents/testWORD_2006ml.docx | Bin 165566 -> 151733 bytes
.../ooxml/AbstractOOXMLDocxPackageTest.java | 51 +
.../ooxml/AbstractOOXMLPptxPackageTest.java} | 46 +-
...actorTest.java => OOXMLDocxDOMPackageTest.java} | 38 +-
...actorTest.java => OOXMLDocxSAXPackageTest.java} | 35 +-
.../parser/microsoft/ooxml/OOXMLParserTest.java | 106 +-
.../microsoft/ooxml/OOXMLPptxDOMPackageTest.java} | 21 +-
.../microsoft/ooxml/OOXMLPptxSAXPackageTest.java} | 26 +-
43 files changed, 4102 insertions(+), 3054 deletions(-)
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Office.java b/tika-core/src/main/java/org/apache/tika/metadata/Office.java
index cf717b38cc..393e3d5004 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Office.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Office.java
@@ -195,6 +195,10 @@ public interface Office {
Property HAS_ANIMATIONS = Property.internalBoolean("msoffice:ppt:has-animations");
+ Property NUM_UNLISTED_SLIDES = Property.internalInteger("msoffice:ppt:num-unlisted-slides");
+
+ Property UNLISTED_SLIDE_NAMES = Property.internalTextBag("msoffice:ppt:unlisted-slide-names");
+
//w:vanish or isVanish or isFldVanish
Property HAS_HIDDEN_TEXT = Property.internalBoolean("msoffice:doc:has-hidden-text");
diff --git a/tika-core/src/main/java/org/apache/tika/sax/XHTMLContentHandler.java b/tika-core/src/main/java/org/apache/tika/sax/XHTMLContentHandler.java
index bae8c4b885..3fd7766d03 100644
--- a/tika-core/src/main/java/org/apache/tika/sax/XHTMLContentHandler.java
+++ b/tika-core/src/main/java/org/apache/tika/sax/XHTMLContentHandler.java
@@ -94,7 +94,6 @@ public class XHTMLContentHandler extends SafeContentHandler {
private boolean headStarted = false;
private boolean headEnded = false;
private boolean useFrameset = false;
-
public XHTMLContentHandler(ContentHandler handler, Metadata metadata) {
this(handler, metadata, null);
}
diff --git a/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java b/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
index a6a68fef6e..699ecad50b 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
@@ -148,6 +148,9 @@ public class DateUtils {
* @return
*/
public Date tryToParse(String dateString) {
+ if (dateString == null || dateString.length() < 6) {
+ return null;
+ }
// Java doesn't like timezones in the form ss+hh:mm
// It only likes the hhmm form, without the colon
int n = dateString.length();
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractListManager.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractListManager.java
index 2e5f8f21c4..12d806d09d 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractListManager.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/AbstractListManager.java
@@ -248,7 +248,7 @@ public abstract class AbstractListManager {
}
}
- protected static class LevelTuple {
+ public static class LevelTuple {
private final int start;
private final int restart;
private final String lvlText;
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
index 7bba9647b1..db6d4e78e9 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParserConfig.java
@@ -35,6 +35,8 @@ public class OfficeParserConfig implements Serializable {
private boolean useSAXDocxExtractor = false;
private boolean useSAXPptxExtractor = false;
+ private boolean preferAlternateContentChoice = true;
+
private boolean writeSelectHeadersInBody = false;
private boolean extractAllAlternativesFromMSG = false;
@@ -167,6 +169,32 @@ public class OfficeParserConfig implements Serializable {
this.useSAXPptxExtractor = useSAXPptxExtractor;
}
+ /**
+ * In OOXML, {@code mc:AlternateContent} wraps {@code mc:Choice} (newer/richer
+ * rendering, e.g. DrawingML text boxes) and {@code mc:Fallback} (degraded VML
+ * for older consumers). When {@code true} (default), the SAX parser processes
+ * the Choice branch and skips Fallback. When {@code false}, it processes
+ * Fallback and skips Choice (legacy behavior prior to Tika 4.x).
+ * <p>
+ * For text extraction, Choice typically contains equal or more content than
+ * Fallback.
+ * <p>
+ * Default: {@code true}
+ *
+ * @return whether to prefer mc:Choice over mc:Fallback
+ */
+ public boolean isPreferAlternateContentChoice() {
+ return preferAlternateContentChoice;
+ }
+
+ /**
+ * @param preferAlternateContentChoice whether to prefer mc:Choice over mc:Fallback
+ * @see #isPreferAlternateContentChoice()
+ */
+ public void setPreferAlternateContentChoice(boolean preferAlternateContentChoice) {
+ this.preferAlternateContentChoice = preferAlternateContentChoice;
+ }
+
public boolean isConcatenatePhoneticRuns() {
return concatenatePhoneticRuns;
}
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/ParagraphProperties.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/CommentAuthors.java
similarity index 52%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/CommentAuthors.java
index 80c004bea2..654eceee23 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/CommentAuthors.java
@@ -16,40 +16,39 @@
*/
package org.apache.tika.parser.microsoft.ooxml;
+import java.util.HashMap;
+import java.util.Map;
-public class ParagraphProperties {
-
- private String styleId;
- private int ilvl = -1;
- private int numId = -1;
-
- public String getStyleID() {
- return styleId;
- }
-
- public void setStyleID(String styleId) {
- this.styleId = styleId;
- }
-
- public void reset() {
- styleId = null;
- ilvl = -1;
- numId = -1;
- }
-
- public int getIlvl() {
- return ilvl;
- }
-
- public void setIlvl(int ilvl) {
- this.ilvl = ilvl;
+/**
+ * Stores comment author names and initials by ID for PPTX comment handling.
+ */
+class CommentAuthors {
+ final Map<String, String> nameMap = new HashMap<>();
+ final Map<String, String> initialMap = new HashMap<>();
+
+ void add(String id, String name, String initials) {
+ if (id == null) {
+ return;
+ }
+ if (name != null) {
+ nameMap.put(id, name);
+ }
+ if (initials != null) {
+ initialMap.put(id, initials);
+ }
}
- public int getNumId() {
- return numId;
+ String getName(String id) {
+ if (id == null) {
+ return null;
+ }
+ return nameMap.get(id);
}
- public void setNumId(int numId) {
- this.numId = numId;
+ String getInitials(String id) {
+ if (id == null) {
+ return null;
+ }
+ return initialMap.get(id);
}
}
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/xwpf/XWPFNumberingShim.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/EditType.java
similarity index 60%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/EditType.java
index f563223964..cbc9d1ae86 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/EditType.java
@@ -14,23 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.tika.parser.microsoft.ooxml.xwpf;
-
-import java.io.IOException;
-
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
-import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
-
-/**
- * Stub class of POI's XWPFNumbering because onDocumentRead() is protected
- */
-public class XWPFNumberingShim extends XWPFNumbering {
-
- public XWPFNumberingShim(PackagePart part) throws IOException, OpenXML4JException {
- super(part);
- onDocumentRead();
- }
-
+package org.apache.tika.parser.microsoft.ooxml;
+public enum EditType {
+ NONE, INSERT, DELETE, MOVE_TO, MOVE_FROM
}
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/FieldCodeParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FieldCodeParser.java
new file mode 100644
index 0000000000..d71cbdc7f9
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/FieldCodeParser.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses OOXML field codes (instrText) to extract URLs from HYPERLINK,
+ * INCLUDEPICTURE, INCLUDETEXT, IMPORT, and LINK fields.
+ * <p>
+ * This class has no Tika dependencies and could be contributed to POI.
+ */
+public class FieldCodeParser {
+
+ private static final Pattern HYPERLINK_PATTERN =
+ Pattern.compile("HYPERLINK\\s{1,100}\"([^\"]{1,10000})\"",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern INCLUDEPICTURE_PATTERN =
+ Pattern.compile("INCLUDEPICTURE\\s{1,100}\"([^\"]{1,10000})\"",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern INCLUDETEXT_PATTERN =
+ Pattern.compile("INCLUDETEXT\\s{1,100}\"([^\"]{1,10000})\"",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern IMPORT_PATTERN =
+ Pattern.compile("IMPORT\\s{1,100}\"([^\"]{1,10000})\"",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern LINK_PATTERN =
+ Pattern.compile(
+ "LINK\\s{1,100}[\\w.]{1,50}\\s{1,100}\"([^\"]{1,10000})\"",
+ Pattern.CASE_INSENSITIVE);
+
+ private FieldCodeParser() {
+ }
+
+ /**
+ * Parses a HYPERLINK URL from instrText field code content.
+ * Field codes like: {@code HYPERLINK "https://example.com"}
+ *
+ * @param instrText the accumulated instrText content
+ * @return the URL if found, or null
+ */
+ public static String parseHyperlinkFromInstrText(String instrText) {
+ if (instrText == null || instrText.isEmpty()) {
+ return null;
+ }
+ Matcher m = HYPERLINK_PATTERN.matcher(instrText.trim());
+ if (m.find()) {
+ return m.group(1);
+ }
+ return null;
+ }
+
+ /**
+ * Parses URLs from instrText field codes that reference external resources.
+ * This includes INCLUDEPICTURE, INCLUDETEXT, IMPORT, and LINK fields.
+ *
+ * @param instrText the accumulated instrText content
+ * @param fieldType output parameter - will contain the field type if found
+ * @return the URL if found, or null
+ */
+ public static String parseExternalRefFromInstrText(String instrText,
+ StringBuilder fieldType) {
+ if (instrText == null || instrText.isEmpty()) {
+ return null;
+ }
+ String trimmed = instrText.trim();
+
+ Matcher m = INCLUDEPICTURE_PATTERN.matcher(trimmed);
+ if (m.find()) {
+ fieldType.append("INCLUDEPICTURE");
+ return m.group(1);
+ }
+
+ m = INCLUDETEXT_PATTERN.matcher(trimmed);
+ if (m.find()) {
+ fieldType.append("INCLUDETEXT");
+ return m.group(1);
+ }
+
+ m = IMPORT_PATTERN.matcher(trimmed);
+ if (m.find()) {
+ fieldType.append("IMPORT");
+ return m.group(1);
+ }
+
+ m = LINK_PATTERN.matcher(trimmed);
+ if (m.find()) {
+ fieldType.append("LINK");
+ return m.group(1);
+ }
+
+ return null;
+ }
+}
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/InlineTagManager.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/InlineTagManager.java
new file mode 100644
index 0000000000..45eee33b57
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/InlineTagManager.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.sax.XHTMLContentHandler;
+
+/**
+ * Manages all inline XHTML elements (hyperlinks and formatting tags) as a
+ * unified state machine, ensuring proper nesting. The nesting order from
+ * outermost to innermost is:
+ * <pre>
+ * {@code <a> <b> <i> <s> <u> text </u> </s> </i> </b> </a>}
+ * </pre>
+ * <p>
+ * When a change occurs to an outer element, all inner elements are closed
+ * first, the change is applied, then inner elements are reopened as needed.
+ * This prevents generating malformed XHTML with overlapping or unbalanced tags.
+ * <p>
+ * This class replaces the separate {@code FormattingTagManager} and the
+ * {@code wroteHyperlinkStart} boolean that were previously tracked independently
+ * in {@link OOXMLTikaBodyPartHandler}.
+ */
+class InlineTagManager {
+
+ private final XHTMLContentHandler xhtml;
+
+ private boolean hyperlinkOpen = false;
+ private boolean isBold = false;
+ private boolean isItalics = false;
+ private boolean isStrikeThrough = false;
+ private boolean isUnderline = false;
+
+ InlineTagManager(XHTMLContentHandler xhtml) {
+ this.xhtml = xhtml;
+ }
+
+ /**
+ * Opens a hyperlink. Since {@code <a>} is the outermost inline element,
+ * any existing inline elements (including a prior hyperlink) are closed
+ * first.
+ *
+ * @param href the link target; if {@code null} this is a no-op
+ */
+ void openHyperlink(String href) throws SAXException {
+ if (href == null) {
+ return;
+ }
+ // Close everything — formatting then any existing hyperlink
+ closeAll();
+ xhtml.startElement("a", "href", href);
+ hyperlinkOpen = true;
+ }
+
+ /**
+ * Closes the current hyperlink and all formatting inside it.
+ * No-op if no hyperlink is open.
+ */
+ void closeHyperlink() throws SAXException {
+ if (!hyperlinkOpen) {
+ return;
+ }
+ closeFormatting();
+ xhtml.endElement("a");
+ hyperlinkOpen = false;
+ }
+
+ /**
+ * Returns {@code true} if a hyperlink is currently open.
+ */
+ boolean isHyperlinkOpen() {
+ return hyperlinkOpen;
+ }
+
+ /**
+ * Reconciles the current formatting state with the given run properties,
+ * opening and closing XHTML tags as needed to maintain proper nesting.
+ * The nesting order for formatting is: {@code <b> <i> <s> <u>}.
+ */
+ void applyFormatting(RunProperties runProperties) throws SAXException {
+ if (runProperties.isBold() != isBold) {
+ // Bold is outermost formatting — close everything inside it
+ if (isUnderline) {
+ xhtml.endElement("u");
+ isUnderline = false;
+ }
+ if (isStrikeThrough) {
+ xhtml.endElement("s");
+ isStrikeThrough = false;
+ }
+ if (isItalics) {
+ xhtml.endElement("i");
+ isItalics = false;
+ }
+ if (runProperties.isBold()) {
+ xhtml.startElement("b");
+ } else {
+ xhtml.endElement("b");
+ }
+ isBold = runProperties.isBold();
+ }
+
+ if (runProperties.isItalics() != isItalics) {
+ if (isUnderline) {
+ xhtml.endElement("u");
+ isUnderline = false;
+ }
+ if (isStrikeThrough) {
+ xhtml.endElement("s");
+ isStrikeThrough = false;
+ }
+ if (runProperties.isItalics()) {
+ xhtml.startElement("i");
+ } else {
+ xhtml.endElement("i");
+ }
+ isItalics = runProperties.isItalics();
+ }
+
+ if (runProperties.isStrikeThrough() != isStrikeThrough) {
+ if (isUnderline) {
+ xhtml.endElement("u");
+ isUnderline = false;
+ }
+ if (runProperties.isStrikeThrough()) {
+ xhtml.startElement("s");
+ } else {
+ xhtml.endElement("s");
+ }
+ isStrikeThrough = runProperties.isStrikeThrough();
+ }
+
+ boolean runIsUnderlined = runProperties.getUnderline() != UnderlinePatterns.NONE;
+ if (runIsUnderlined != isUnderline) {
+ if (runIsUnderlined) {
+ xhtml.startElement("u");
+ } else {
+ xhtml.endElement("u");
+ }
+ isUnderline = runIsUnderlined;
+ }
+ }
+
+ /**
+ * Closes all currently open formatting tags in proper nesting order
+ * (innermost first: u, s, i, b). Does NOT close the hyperlink.
+ */
+ void closeFormatting() throws SAXException {
+ if (isUnderline) {
+ xhtml.endElement("u");
+ isUnderline = false;
+ }
+ if (isStrikeThrough) {
+ xhtml.endElement("s");
+ isStrikeThrough = false;
+ }
+ if (isItalics) {
+ xhtml.endElement("i");
+ isItalics = false;
+ }
+ if (isBold) {
+ xhtml.endElement("b");
+ isBold = false;
+ }
+ }
+
+ /**
+ * Closes ALL open inline elements — formatting first, then hyperlink.
+ * This is the primary safety mechanism: call at every structural boundary
+ * (end of paragraph, table cell, table row, table, etc.) to guarantee
+ * well-formed XHTML.
+ */
+ void closeAll() throws SAXException {
+ closeFormatting();
+ if (hyperlinkOpen) {
+ xhtml.endElement("a");
+ hyperlinkOpen = false;
+ }
+ }
+}
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/OOXMLInlineBodyPartMap.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLInlineBodyPartMap.java
new file mode 100644
index 0000000000..0738e9939b
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLInlineBodyPartMap.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Holds pre-parsed XML content fragments for OOXML document parts that are
+ * referenced inline from the main document body. Each map stores
+ * ID → raw XML bytes for a specific part type.
+ * <p>
+ * Used for footnotes, endnotes, and comments so that their content can be
+ * inlined at the point of reference rather than dumped at the end.
+ */
+class OOXMLInlineBodyPartMap {
+
+ static final OOXMLInlineBodyPartMap EMPTY = new OOXMLInlineBodyPartMap(
+ Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(),
+ Collections.emptyMap());
+
+ private final Map<String, byte[]> footnotes;
+ private final Map<String, byte[]> endnotes;
+ private final Map<String, byte[]> comments;
+ private final Map<String, String> linkedRelationships;
+
+ OOXMLInlineBodyPartMap(Map<String, byte[]> footnotes,
+ Map<String, byte[]> endnotes,
+ Map<String, byte[]> comments,
+ Map<String, String> linkedRelationships) {
+ this.footnotes = footnotes;
+ this.endnotes = endnotes;
+ this.comments = comments;
+ this.linkedRelationships = linkedRelationships;
+ }
+
+ Map<String, String> getLinkedRelationships() {
+ return linkedRelationships;
+ }
+
+ byte[] getFootnote(String id) {
+ return footnotes.get(id);
+ }
+
+ byte[] getEndnote(String id) {
+ return endnotes.get(id);
+ }
+
+ byte[] getComment(String id) {
+ return comments.get(id);
+ }
+
+ boolean hasFootnotes() {
+ return !footnotes.isEmpty();
+ }
+
+ boolean hasEndnotes() {
+ return !endnotes.isEmpty();
+ }
+
+ boolean hasComments() {
+ return !comments.isEmpty();
+ }
+
+ Iterable<Map.Entry<String, byte[]>> getCommentEntries() {
+ return comments.entrySet();
+ }
+}
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/OOXMLPartContentCollector.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPartContentCollector.java
new file mode 100644
index 0000000000..4b21831638
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPartContentCollector.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Generic SAX handler that collects raw XML content by ID from OOXML part files.
+ * Works with any part that contains wrapper elements with {@code w:id} attributes
+ * containing body content (paragraphs, tables, formatting, etc.).
+ * <p>
+ * Used for:
+ * <ul>
+ * <li>footnotes.xml — wrapper element "footnote"</li>
+ * <li>endnotes.xml — wrapper element "endnote"</li>
+ * <li>comments.xml — wrapper element "comment"</li>
+ * </ul>
+ * <p>
+ * IDs "0" and "-1" are skipped (these are separator/continuation elements in
+ * footnotes/endnotes).
+ */
+class OOXMLPartContentCollector extends DefaultHandler {
+
+ private static final String W_NS =
+ "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+
+ private final Set<String> wrapperElementNames;
+ private final Set<String> skipIds;
+ private final Map<String, byte[]> contentMap = new HashMap<>();
+ private final Map<String, String> namespaceMappings = new HashMap<>();
+
+ private String currentId = null;
+ private ByteArrayOutputStream buffer = null;
+ private int depth = 0;
+ // Prefix mappings that fired since the last startElement — need to be
+ // emitted as xmlns declarations on the next element inside a collected fragment.
+ private final java.util.List<String[]> pendingPrefixMappings = new java.util.ArrayList<>();
+
+ /**
+ * @param wrapperElementNames local names of wrapper elements to collect
+ * (e.g., "footnote", "endnote", "comment")
+ */
+ OOXMLPartContentCollector(Set<String> wrapperElementNames) {
+ this(wrapperElementNames, Set.of("0", "-1"));
+ }
+
+ /**
+ * @param wrapperElementNames local names of wrapper elements to collect
+ * @param skipIds IDs to skip (e.g., "0", "-1" for footnote
+ * separator/continuation elements)
+ */
+ OOXMLPartContentCollector(Set<String> wrapperElementNames, Set<String> skipIds) {
+ this.wrapperElementNames = wrapperElementNames;
+ this.skipIds = skipIds;
+ }
+
+ @Override
+ public void startPrefixMapping(String prefix, String uri) {
+ namespaceMappings.put(prefix, uri);
+ // Track prefix mappings that fire within a collected fragment —
+ // these need to be emitted as xmlns declarations on the next
+ // startElement so that re-parsed fragments have valid namespace bindings.
+ if (currentId != null) {
+ pendingPrefixMappings.add(new String[]{prefix, uri});
+ }
+ }
+
+ Map<String, byte[]> getContentMap() {
+ return contentMap;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ if (currentId != null) {
+ depth++;
+ appendStartTag(localName, qName, atts);
+ return;
+ }
+
+ if (wrapperElementNames.contains(localName)) {
+ String id = atts.getValue(W_NS, "id");
+ if (id != null && !skipIds.contains(id)) {
+ currentId = id;
+ buffer = new ByteArrayOutputStream();
+ writeString(buildWrapperOpenTag());
+ depth = 0;
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ if (currentId == null) {
+ return;
+ }
+
+ if (depth == 0) {
+ writeString("</w:body>");
+ contentMap.put(currentId, buffer.toByteArray());
+ currentId = null;
+ buffer = null;
+ return;
+ }
+
+ depth--;
+ if (qName != null && !qName.isEmpty()) {
+ writeString("</" + qName + ">");
+ } else {
+ writeString("</" + localName + ">");
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (currentId != null) {
+ writeString(escape(new String(ch, start, length)));
+ }
+ }
+
+ private String buildWrapperOpenTag() {
+ StringBuilder sb = new StringBuilder("<w:body");
+ // include all namespace declarations from the source document
+ for (Map.Entry<String, String> entry : namespaceMappings.entrySet()) {
+ String prefix = entry.getKey();
+ String nsUri = entry.getValue();
+ if (prefix == null || prefix.isEmpty()) {
+ sb.append(" xmlns=\"").append(escape(nsUri)).append("\"");
+ } else {
+ sb.append(" xmlns:").append(prefix).append("=\"")
+ .append(escape(nsUri)).append("\"");
+ }
+ }
+ // ensure w namespace is present
+ if (!namespaceMappings.containsKey("w")) {
+ sb.append(" xmlns:w=\"").append(W_NS).append("\"");
+ }
+ sb.append(">");
+ return sb.toString();
+ }
+
+ private void appendStartTag(String localName, String qName, Attributes atts) {
+ String tagName = (qName != null && !qName.isEmpty()) ? qName : localName;
+ StringBuilder sb = new StringBuilder();
+ sb.append('<').append(tagName);
+ // Emit any namespace declarations that fired since the last element.
+ // In namespace-aware SAX, xmlns:prefix attributes are reported as
+ // startPrefixMapping events, NOT as attributes — so they must be
+ // re-serialized explicitly for the fragment to be re-parseable.
+ if (!pendingPrefixMappings.isEmpty()) {
+ for (String[] mapping : pendingPrefixMappings) {
+ String prefix = mapping[0];
+ String nsUri = mapping[1];
+ if (prefix == null || prefix.isEmpty()) {
+ sb.append(" xmlns=\"").append(escape(nsUri)).append("\"");
+ } else {
+ sb.append(" xmlns:").append(prefix).append("=\"")
+ .append(escape(nsUri)).append("\"");
+ }
+ }
+ pendingPrefixMappings.clear();
+ }
+ for (int i = 0; i < atts.getLength(); i++) {
+ String attName = atts.getQName(i);
+ if (attName == null || attName.isEmpty()) {
+ attName = atts.getLocalName(i);
+ }
+ sb.append(' ').append(attName).append("=\"");
+ sb.append(escape(atts.getValue(i)));
+ sb.append('"');
+ }
+ sb.append('>');
+ writeString(sb.toString());
+ }
+
+ private void writeString(String s) {
+ byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
+ buffer.write(bytes, 0, bytes.length);
+ }
+
+ static String escape(String s) {
+ if (s == null) {
+ return "";
+ }
+ StringBuilder sb = null;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ String replacement = null;
+ switch (c) {
+ case '&':
+ replacement = "&";
+ break;
+ case '<':
+ replacement = "<";
+ break;
+ case '>':
+ replacement = ">";
+ break;
+ case '"':
+ replacement = """;
+ break;
+ default:
+ if (sb != null) {
+ sb.append(c);
+ }
+ continue;
+ }
+ if (sb == null) {
+ sb = new StringBuilder(s.length() + 16);
+ sb.append(s, 0, i);
+ }
+ sb.append(replacement);
+ }
+ return sb != null ? sb.toString() : 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/OOXMLPictureTracker.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPictureTracker.java
new file mode 100644
index 0000000000..f6a98ecacf
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPictureTracker.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.util.Map;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Tracks the lifecycle of picture elements (PIC, PICT, BLIP, IMAGEDATA, cNvPr)
+ * during OOXML SAX parsing and emits embeddedPicRef callbacks when the picture
+ * scope closes.
+ * <p>
+ * This class has no Tika dependencies and could be contributed to POI.
+ */
+class OOXMLPictureTracker {
+
+ private final Map<String, String> linkedRelationships;
+ private final XWPFBodyContentsHandler bodyContentsHandler;
+
+ private boolean inPic = false;
+ private boolean inPict = false;
+ private String picDescription = null;
+ private String picRId = null;
+ private String lastImageDataRId = null;
+
+ OOXMLPictureTracker(Map<String, String> linkedRelationships,
+ XWPFBodyContentsHandler bodyContentsHandler) {
+ this.linkedRelationships = linkedRelationships;
+ this.bodyContentsHandler = bodyContentsHandler;
+ }
+
+ boolean isInPic() {
+ return inPic;
+ }
+
+ boolean isInPict() {
+ return inPict;
+ }
+
+ void startPic() {
+ inPic = true;
+ }
+
+ void startPict() {
+ inPict = true;
+ }
+
+ void setBlipRId(String rId) {
+ picRId = rId;
+ }
+
+ void setDescription(String description) {
+ picDescription = description;
+ }
+
+ void setImageDataRId(String rId) {
+ picRId = rId;
+ lastImageDataRId = rId;
+ }
+
+ String getImageDataRId() {
+ return lastImageDataRId;
+ }
+
+ void setImageDataDescription(String description) {
+ picDescription = description;
+ }
+
+ /**
+ * Called at end of PIC or PICT element. Resolves the filename from
+ * the relationship map and emits the embeddedPicRef callback.
+ */
+ void endPicture() throws SAXException {
+ String picFileName = null;
+ if (picRId != null) {
+ picFileName = linkedRelationships.get(picRId);
+ }
+ bodyContentsHandler.embeddedPicRef(picFileName, picDescription);
+ picDescription = null;
+ picRId = null;
+ inPic = false;
+ inPict = false;
+ }
+}
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 4bc445fb5e..ac46090d1c 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
@@ -17,20 +17,29 @@
package org.apache.tika.parser.microsoft.ooxml;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
-import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.microsoft.OfficeParserConfig;
import org.apache.tika.parser.microsoft.WordExtractor;
import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFStylesShim;
+import org.apache.tika.sax.EmbeddedContentHandler;
import org.apache.tika.sax.XHTMLContentHandler;
+import org.apache.tika.utils.XMLReaderUtils;
public class OOXMLTikaBodyPartHandler
- implements OOXMLWordAndPowerPointTextHandler.XWPFBodyContentsHandler {
+ implements XWPFBodyContentsHandler {
private static final String P = "p";
@@ -41,15 +50,12 @@ public class OOXMLTikaBodyPartHandler
private final boolean includeDeletedText;
private final boolean includeMoveFromText;
private final XWPFStylesShim styles;
+ private final Metadata metadata;
private int pDepth = 0; //paragraph depth
private int tableDepth = 0;//table depth
private int sdtDepth = 0;//
- private boolean isItalics = false;
- private boolean isBold = false;
- private boolean isUnderline = false;
- private boolean isStrikeThrough = false;
- private boolean wroteHyperlinkStart = false;
+ private final InlineTagManager inlineTags;
//TODO: fix this
//pWithinCell should be an array/stack of given cell depths
@@ -62,9 +68,26 @@ public class OOXMLTikaBodyPartHandler
//will need to replace this with a stack
//if we're marking more that the first level <p/> element
private String paragraphTag = null;
+ private boolean pendingParagraph = false;
+ private boolean paragraphTagOpen = false;
+ private ParagraphProperties pendingParagraphProperties = null;
+ private String pendingHyperlinkHref = null;
+
+ private OOXMLInlineBodyPartMap inlinePartMap = OOXMLInlineBodyPartMap.EMPTY;
+ private ParseContext parseContext = null;
+ private final java.util.List<String[]> pendingNoteIds = new java.util.ArrayList<>();
+ private final java.util.List<String> pendingCommentIds = new java.util.ArrayList<>();
+ private final java.util.Set<String> emittedCommentIds = new java.util.HashSet<>();
+ private final Map<String, EmbeddedPartMetadata> embeddedPartMetadataMap = new HashMap<>();
public OOXMLTikaBodyPartHandler(XHTMLContentHandler xhtml) {
+ this(xhtml, null);
+ }
+
+ public OOXMLTikaBodyPartHandler(XHTMLContentHandler xhtml, Metadata metadata) {
this.xhtml = xhtml;
+ this.metadata = metadata;
+ this.inlineTags = new InlineTagManager(xhtml);
this.styles = XWPFStylesShim.EMPTY_STYLES;
this.listManager = XWPFListManager.EMPTY_LIST;
this.includeDeletedText = false;
@@ -72,115 +95,130 @@ public class OOXMLTikaBodyPartHandler
}
public OOXMLTikaBodyPartHandler(XHTMLContentHandler xhtml, XWPFStylesShim styles,
- XWPFListManager listManager, OfficeParserConfig parserConfig) {
+ XWPFListManager listManager,
+ OfficeParserConfig parserConfig) {
+ this(xhtml, styles, listManager, parserConfig, null);
+ }
+
+ public OOXMLTikaBodyPartHandler(XHTMLContentHandler xhtml, XWPFStylesShim styles,
+ XWPFListManager listManager,
+ OfficeParserConfig parserConfig, Metadata metadata) {
this.xhtml = xhtml;
+ this.metadata = metadata;
+ this.inlineTags = new InlineTagManager(xhtml);
this.styles = styles;
this.listManager = listManager;
this.includeDeletedText = parserConfig.isIncludeDeletedContent();
this.includeMoveFromText = parserConfig.isIncludeMoveFromContent();
}
+ /**
+ * Sets pre-parsed inline body part content (footnotes, endnotes, comments)
+ * so that references encountered during main document parsing can be
+ * resolved inline.
+ */
+ public void setInlineBodyPartMap(OOXMLInlineBodyPartMap inlinePartMap,
+ ParseContext parseContext) {
+ this.inlinePartMap = inlinePartMap != null ? inlinePartMap : OOXMLInlineBodyPartMap.EMPTY;
+ this.parseContext = parseContext;
+ }
+
@Override
public void run(RunProperties runProperties, String contents) throws SAXException {
-
- // True if we are currently in the named style tag:
- if (runProperties.isBold() != isBold) {
- if (isStrikeThrough) {
- xhtml.endElement("strike");
- isStrikeThrough = false;
- }
- if (isUnderline) {
- xhtml.endElement("u");
- isUnderline = false;
- }
- if (isItalics) {
- xhtml.endElement("i");
- isItalics = false;
- }
- if (runProperties.isBold()) {
- xhtml.startElement("b");
- } else {
- xhtml.endElement("b");
- }
- isBold = runProperties.isBold();
- }
-
- if (runProperties.isItalics() != isItalics) {
- if (isStrikeThrough) {
- xhtml.endElement("strike");
- isStrikeThrough = false;
- }
- if (isUnderline) {
- xhtml.endElement("u");
- isUnderline = false;
- }
- if (runProperties.isItalics()) {
- xhtml.startElement("i");
- } else {
- xhtml.endElement("i");
- }
- isItalics = runProperties.isItalics();
- }
-
- if (runProperties.isStrikeThrough() != isStrikeThrough) {
- if (isUnderline) {
- xhtml.endElement("u");
- isUnderline = false;
- }
- if (runProperties.isStrikeThrough()) {
- xhtml.startElement("strike");
- } else {
- xhtml.endElement("strike");
- }
- isStrikeThrough = runProperties.isStrikeThrough();
- }
-
- boolean runIsUnderlined = runProperties.getUnderline() != UnderlinePatterns.NONE;
- if (runIsUnderlined != isUnderline) {
- if (runIsUnderlined) {
- xhtml.startElement("u");
- } else {
- xhtml.endElement("u");
- }
- isUnderline = runIsUnderlined;
- }
-
+ ensureParagraphOpen();
+ flushPendingHyperlink();
+ inlineTags.applyFormatting(runProperties);
xhtml.characters(contents);
+ }
+ private void flushPendingHyperlink() throws SAXException {
+ if (pendingHyperlinkHref != null) {
+ inlineTags.openHyperlink(pendingHyperlinkHref);
+ pendingHyperlinkHref = null;
+ }
}
@Override
public void hyperlinkStart(String link) throws SAXException {
- if (link != null) {
- xhtml.startElement("a", "href", link);
- wroteHyperlinkStart = true;
+ // Defer hyperlink opening if no paragraph is open yet.
+ // Shape-level hyperlinks (cNvPr/hlinkClick) fire before any <p>,
+ // so we store the link and open it when the paragraph opens.
+ if (pendingParagraph || pDepth == 0) {
+ pendingHyperlinkHref = link;
+ } else {
+ inlineTags.openHyperlink(link);
}
}
@Override
public void hyperlinkEnd() throws SAXException {
- if (wroteHyperlinkStart) {
- closeStyleTags();
- wroteHyperlinkStart = false;
- xhtml.endElement("a");
+ if (pendingHyperlinkHref != null) {
+ pendingHyperlinkHref = null;
+ } else {
+ inlineTags.closeHyperlink();
}
}
+ /**
+ * Closes any open inline elements (hyperlinks, formatting tags) in
+ * the correct nesting order. Called before closing any structural
+ * element (paragraph, table cell, table row, table, etc.) to ensure
+ * well-formed XHTML.
+ */
+ void closeInlineElements() throws SAXException {
+ inlineTags.closeAll();
+ }
+
+
@Override
public void startParagraph(ParagraphProperties paragraphProperties) throws SAXException {
-
//if you're in a table cell and your after the first paragraph
//make sure to prepend a \n
if (tableCellDepth > 0 && pWithinCell > 0) {
xhtml.characters(NEWLINE, 0, 1);
}
+ // If we're about to nest a paragraph (e.g. inside a text box / shape),
+ // force-open the outer paragraph first so that inner content ends up
+ // inside the outer <p> tag rather than floating as raw text.
+ if (pendingParagraph && pDepth > 0) {
+ ensureParagraphOpen();
+ }
+ // Record the paragraph as pending — don't emit <p> yet.
+ // We defer opening until the first content arrives (via ensureParagraphOpen)
+ // so that style info from pPr is available.
+ pendingParagraph = true;
+ pendingParagraphProperties = paragraphProperties;
+ pDepth++;
+ }
+
+ @Override
+ public void setParagraphProperties(ParagraphProperties paragraphProperties)
+ throws SAXException {
+ // Copy the properties — the caller may reset the object after this call.
+ // The <p> tag hasn't been emitted yet, so this style will be applied when it opens.
+ if (pendingParagraph) {
+ pendingParagraphProperties = new ParagraphProperties(paragraphProperties);
+ }
+ }
- if (pDepth == 0 && tableDepth == 0 && sdtDepth == 0) {
+ /**
+ * Ensures the current paragraph's XHTML tag is open. Called before any
+ * content is written (runs, hyperlinks, etc.) so that the deferred
+ * {@code <p>} tag is emitted with the correct style.
+ */
+ private void ensureParagraphOpen() throws SAXException {
+ if (!pendingParagraph) {
+ return;
+ }
+ pendingParagraph = false;
+
+ if (pDepth == 1 && tableDepth == 0 && sdtDepth == 0) {
paragraphTag = P;
String styleClass = null;
+ ParagraphProperties pp = pendingParagraphProperties;
//TIKA-2144 check that styles is not null
- if (paragraphProperties.getStyleID() != null && styles != null) {
- String styleName = styles.getStyleName(paragraphProperties.getStyleID());
+ if (pp != null && pp.getStyleID() != null && styles != null) {
+ String styleName = styles.getStyleName(pp.getStyleID());
if (styleName != null) {
WordExtractor.TagAndStyle tas =
WordExtractor.buildParagraphTagAndStyle(styleName, false);
@@ -189,40 +227,98 @@ public class OOXMLTikaBodyPartHandler
}
}
-
if (styleClass == null) {
xhtml.startElement(paragraphTag);
} else {
xhtml.startElement(paragraphTag, "class", styleClass);
}
+ paragraphTagOpen = true;
}
- writeParagraphNumber(paragraphProperties.getNumId(), paragraphProperties.getIlvl(),
- listManager, xhtml);
- pDepth++;
+ if (pendingParagraphProperties != null) {
+ writeParagraphNumber(pendingParagraphProperties.getNumId(),
+ pendingParagraphProperties.getIlvl(), listManager, xhtml);
+ }
+ pendingParagraphProperties = null;
}
-
@Override
public void endParagraph() throws SAXException {
- closeStyleTags();
- if (pDepth == 1 && tableDepth == 0) {
+ ensureParagraphOpen();
+ closeInlineElements();
+ if (paragraphTagOpen) {
xhtml.endElement(paragraphTag);
+ paragraphTagOpen = false;
} else if (tableCellDepth > 0 && pWithinCell > 0) {
xhtml.characters(NEWLINE, 0, 1);
} else if (tableCellDepth == 0) {
xhtml.characters(NEWLINE, 0, 1);
}
+ // Emit any pending footnote/endnote and comment content after the
+ // paragraph closes. Inlining mid-paragraph would create <div> inside
+ // <p>, and the inner handler's endParagraph() would close the outer
+ // <p> tag, corrupting state.
+ emitPendingNotes();
+ emitPendingComments();
+
if (tableCellDepth > 0) {
pWithinCell++;
}
pDepth--;
}
+ private void emitPendingNotes() throws SAXException {
+ if (pendingNoteIds.isEmpty()) {
+ return;
+ }
+ for (String[] noteTypeAndId : pendingNoteIds) {
+ String noteType = noteTypeAndId[0];
+ String id = noteTypeAndId[1];
+ byte[] xml = "footnote".equals(noteType)
+ ? inlinePartMap.getFootnote(id)
+ : inlinePartMap.getEndnote(id);
+ if (xml != null) {
+ inlineNoteContent(xml, noteType);
+ } else {
+ xhtml.characters("[");
+ xhtml.characters(id);
+ xhtml.characters("]");
+ }
+ }
+ pendingNoteIds.clear();
+ }
+
+ private void emitPendingComments() throws SAXException {
+ if (pendingCommentIds.isEmpty()) {
+ return;
+ }
+ for (String id : pendingCommentIds) {
+ byte[] xml = inlinePartMap.getComment(id);
+ if (xml != null) {
+ inlineNoteContent(xml, "comment");
+ emittedCommentIds.add(id);
+ }
+ }
+ pendingCommentIds.clear();
+ }
+
+ /**
+ * Returns the set of comment IDs that were inlined during parsing.
+ * Used by the decorator to skip these when dumping remaining comments.
+ */
+ public java.util.Set<String> getEmittedCommentIds() {
+ return emittedCommentIds;
+ }
+
@Override
public void startTable() throws SAXException {
-
+ // Close any open paragraph — <table> can't nest inside <p> in XHTML
+ closeInlineElements();
+ if (paragraphTagOpen) {
+ xhtml.endElement(paragraphTag);
+ paragraphTagOpen = false;
+ }
xhtml.startElement("table");
tableDepth++;
@@ -230,7 +326,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void endTable() throws SAXException {
-
+ closeInlineElements();
xhtml.endElement("table");
tableDepth--;
@@ -243,6 +339,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void endTableRow() throws SAXException {
+ closeInlineElements();
xhtml.endElement("tr");
}
@@ -254,6 +351,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void endTableCell() throws SAXException {
+ closeInlineElements();
xhtml.endElement("td");
pWithinCell = 0;
tableCellDepth--;
@@ -261,7 +359,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void startSDT() throws SAXException {
- closeStyleTags();
+ inlineTags.closeAll();
sdtDepth++;
}
@@ -272,7 +370,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void startEditedSection(String editor, Date date,
- OOXMLWordAndPowerPointTextHandler.EditType editType) {
+ EditType editType) {
//no-op
}
@@ -288,32 +386,73 @@ public class OOXMLTikaBodyPartHandler
@Override
public void footnoteReference(String id) throws SAXException {
- if (id != null) {
- xhtml.characters("[");
- xhtml.characters(id);
- xhtml.characters("]");
+ if (id == null) {
+ return;
}
+ // Defer footnote emission to after the paragraph closes.
+ // Inlining mid-paragraph creates <div> inside <p>, and the inner
+ // handler's endParagraph() closes the outer <p> tag, corrupting state.
+ pendingNoteIds.add(new String[]{"footnote", id});
}
@Override
public void endnoteReference(String id) throws SAXException {
+ if (id == null) {
+ return;
+ }
+ pendingNoteIds.add(new String[]{"endnote", id});
+ }
+
+ @Override
+ public void commentReference(String id) throws SAXException {
if (id != null) {
- xhtml.characters("[");
- xhtml.characters(id);
- xhtml.characters("]");
+ pendingCommentIds.add(id);
}
}
+ private void inlineNoteContent(byte[] xml, String cssClass) throws SAXException {
+ // Close any open inline elements before inlining note content
+ // to ensure the <div> nests correctly
+ closeInlineElements();
+ // Use the inline part map's relationship map which includes relationships
+ // from the footnote/endnote parts (needed for picture resolution)
+ Map<String, String> noteRelationships = inlinePartMap.getLinkedRelationships();
+ xhtml.startElement("div", "class", cssClass);
+ OOXMLTikaBodyPartHandler innerHandler = new OOXMLTikaBodyPartHandler(xhtml);
+ try {
+ XMLReaderUtils.parseSAX(new ByteArrayInputStream(xml),
+ new EmbeddedContentHandler(
+ new OOXMLWordAndPowerPointTextHandler(
+ innerHandler,
+ noteRelationships)),
+ parseContext);
+ } catch (TikaException | IOException e) {
+ xhtml.characters("[" + cssClass + " parse error]");
+ } finally {
+ innerHandler.closeInlineElements();
+ }
+ xhtml.endElement("div");
+ }
+
@Override
public boolean isIncludeMoveFromText() {
return includeMoveFromText;
}
@Override
- public void embeddedOLERef(String relId) throws SAXException {
+ public void embeddedOLERef(String relId, String progId, String emfImageRId)
+ throws SAXException {
if (relId == null) {
return;
}
+ if ((progId != null && !progId.isEmpty()) ||
+ (emfImageRId != null && !emfImageRId.isEmpty())) {
+ EmbeddedPartMetadata epm = new EmbeddedPartMetadata(emfImageRId);
+ if (progId != null && !progId.isEmpty()) {
+ epm.setProgId(progId);
+ }
+ embeddedPartMetadataMap.put(relId, epm);
+ }
AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute("", "class", "class", "CDATA", "embedded");
attributes.addAttribute("", "id", "id", "CDATA", relId);
@@ -321,11 +460,18 @@ public class OOXMLTikaBodyPartHandler
xhtml.endElement("div");
}
+ public Map<String, EmbeddedPartMetadata> getEmbeddedPartMetadataMap() {
+ return embeddedPartMetadataMap;
+ }
+
@Override
public void linkedOLERef(String relId) throws SAXException {
if (relId == null) {
return;
}
+ if (metadata != null) {
+ metadata.set(Office.HAS_LINKED_OLE_OBJECTS, true);
+ }
// Emit as an external reference anchor - linked OLE objects reference external files
AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute("", "class", "class", "CDATA", "external-ref-linkedOle");
@@ -351,11 +497,28 @@ public class OOXMLTikaBodyPartHandler
}
+ @Override
+ public void fieldCodeHyperlinkStart(String link) throws SAXException {
+ if (metadata != null) {
+ metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
+ }
+ hyperlinkStart(link);
+ }
+
@Override
public void externalRef(String fieldType, String url) throws SAXException {
if (url == null || url.isEmpty()) {
return;
}
+ if (metadata != null) {
+ if ("hlinkHover".equals(fieldType)) {
+ metadata.set(Office.HAS_HOVER_HYPERLINKS, true);
+ } else if ("vml-shape-href".equals(fieldType)) {
+ metadata.set(Office.HAS_VML_HYPERLINKS, true);
+ } else {
+ metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
+ }
+ }
AttributesImpl attr = new AttributesImpl();
attr.addAttribute("", "class", "class", "CDATA", "external-ref-" + fieldType);
attr.addAttribute("", "href", "href", "CDATA", url);
@@ -366,7 +529,7 @@ public class OOXMLTikaBodyPartHandler
@Override
public void startBookmark(String id, String name) throws SAXException {
//skip bookmarks within hyperlinks
- if (name != null && !wroteHyperlinkStart) {
+ if (name != null && !inlineTags.isHyperlinkOpen()) {
xhtml.startElement("a", "name", name);
xhtml.endElement("a");
}
@@ -377,29 +540,6 @@ public class OOXMLTikaBodyPartHandler
//no-op
}
- private void closeStyleTags() throws SAXException {
-
- if (isStrikeThrough) {
- xhtml.endElement("strike");
- isStrikeThrough = false;
- }
-
- if (isUnderline) {
- xhtml.endElement("u");
- isUnderline = false;
- }
-
- if (isItalics) {
- xhtml.endElement("i");
- isItalics = false;
- }
-
- if (isBold) {
- xhtml.endElement("b");
- isBold = false;
- }
- }
-
private void writeParagraphNumber(int numId, int ilvl, XWPFListManager listManager,
XHTMLContentHandler xhtml) 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 3569398a28..00f1ff6c4a 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
@@ -19,16 +19,12 @@ package org.apache.tika.parser.microsoft.ooxml;
import java.util.Date;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
-import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.Office;
import org.apache.tika.utils.DateUtils;
/**
@@ -90,6 +86,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
private static final String VAL = "val";
private static final String SLIDE = "sld";
private static final String SHOW = "show";
+ private static final String TIMING = "timing"; // p:timing — slide animations
private final static String MC_NS =
"http://schemas.openxmlformats.org/markup-compatibility/2006";
private final static String O_NS = "urn:schemas-microsoft-com:office:office";
@@ -111,7 +108,13 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
private final static String MOVE_FROM = "moveFrom";
private final static String MOVE_TO = "moveTo";
private final static String ENDNOTE_REFERENCE = "endnoteReference";
+ private final static String COMMENT_REFERENCE = "commentReference";
private static final String TEXTBOX = "textbox";
+ private static final String TXBX = "txbx"; // DrawingML text box (wps:txbx in mc:Choice)
+ private static final String SDT = "sdt";
+ private static final String SDT_PR = "sdtPr";
+ private static final String SDT_CONTENT = "sdtContent";
+ private static final String SHOWING_PLCHDR = "showingPlcHdr";
private final static String FLD_CHAR = "fldChar";
private final static String INSTR_TEXT = "instrText";
private final static String FLD_CHAR_TYPE = "fldCharType";
@@ -122,24 +125,14 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
private final static String SHAPE = "shape";
private final static String HREF = "href";
- // Patterns for extracting URLs from field codes
- private static final Pattern HYPERLINK_PATTERN =
- Pattern.compile("HYPERLINK\\s{1,100}\"([^\"]{1,10000})\"", Pattern.CASE_INSENSITIVE);
- private static final Pattern INCLUDEPICTURE_PATTERN =
- Pattern.compile("INCLUDEPICTURE\\s{1,100}\"([^\"]{1,10000})\"", Pattern.CASE_INSENSITIVE);
- private static final Pattern INCLUDETEXT_PATTERN =
- Pattern.compile("INCLUDETEXT\\s{1,100}\"([^\"]{1,10000})\"", Pattern.CASE_INSENSITIVE);
- private static final Pattern IMPORT_PATTERN =
- Pattern.compile("IMPORT\\s{1,100}\"([^\"]{1,10000})\"", Pattern.CASE_INSENSITIVE);
- private static final Pattern LINK_PATTERN =
- Pattern.compile("LINK\\s{1,100}[\\w.]{1,50}\\s{1,100}\"([^\"]{1,10000})\"", Pattern.CASE_INSENSITIVE);
private final XWPFBodyContentsHandler bodyContentsHandler;
private final Map<String, String> linkedRelationships;
+ private final OOXMLPictureTracker pictureTracker;
private final RunProperties currRunProperties = new RunProperties();
private final ParagraphProperties currPProperties = new ParagraphProperties();
private final boolean includeTextBox;
private final boolean concatenatePhoneticRuns;
- private final Metadata metadata;
+ private final boolean preferACChoice;
private final StringBuilder runBuffer = new StringBuilder();
private final StringBuilder rubyBuffer = new StringBuilder();
private boolean inR = false;
@@ -148,22 +141,12 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
private boolean inRPr = false;
private boolean inNumPr = false;
private boolean inRt = false;
- private boolean inPic = false;
- private boolean inPict = false;
- private String picDescription = null;
- private String picRId = null;
- private String picFilename = null;
- //mechanism used to determine when to
- //signal the start of the p, and still
- //handle p with pPr and those without
- private boolean lastStartElementWasP = false;
- //have we signaled the start of a p?
- //pPr can happen multiple times within a p
- //<p><pPr/><r><t>text</t></r><pPr></p>
- private boolean pStarted = false;
+ private boolean inPPr = false;
//alternate content can be embedded in itself.
//need to track depth.
- //if in alternate, choose fallback, maybe make this configurable?
+ //preferACChoice controls which branch is processed:
+ // true -> process Choice, skip Fallback (richer content)
+ // false -> process Fallback, skip Choice (legacy behavior)
private int inACChoiceDepth = 0;
private int inACFallbackDepth = 0;
private boolean inDelText = false;
@@ -176,31 +159,38 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
private boolean inInstrText = false;
private boolean inFieldHyperlink = false;
private final StringBuilder instrTextBuffer = new StringBuilder();
- private OOXMLWordAndPowerPointTextHandler.EditType editType =
- OOXMLWordAndPowerPointTextHandler.EditType.NONE;
+ private EditType editType =
+ EditType.NONE;
private DateUtils dateUtils = new DateUtils();
private boolean hiddenSlide = false;
+ // SDT (structured document tag) placeholder tracking
+ private boolean inSdtPr = false;
+ private boolean sdtIsPlaceholder = false;
+ private int sdtPlaceholderDepth = 0;
+ private boolean hasAnimations = false;
public OOXMLWordAndPowerPointTextHandler(XWPFBodyContentsHandler bodyContentsHandler,
Map<String, String> hyperlinks) {
- this(bodyContentsHandler, hyperlinks, true, true, null);
+ this(bodyContentsHandler, hyperlinks, true, true, true);
}
public OOXMLWordAndPowerPointTextHandler(XWPFBodyContentsHandler bodyContentsHandler,
Map<String, String> hyperlinks, boolean includeTextBox,
boolean concatenatePhoneticRuns) {
- this(bodyContentsHandler, hyperlinks, includeTextBox, concatenatePhoneticRuns, null);
+ this(bodyContentsHandler, hyperlinks, includeTextBox, concatenatePhoneticRuns, true);
}
public OOXMLWordAndPowerPointTextHandler(XWPFBodyContentsHandler bodyContentsHandler,
Map<String, String> hyperlinks, boolean includeTextBox,
- boolean concatenatePhoneticRuns, Metadata metadata) {
+ boolean concatenatePhoneticRuns,
+ boolean preferACChoice) {
this.bodyContentsHandler = bodyContentsHandler;
this.linkedRelationships = hyperlinks;
+ this.pictureTracker = new OOXMLPictureTracker(hyperlinks, bodyContentsHandler);
this.includeTextBox = includeTextBox;
this.concatenatePhoneticRuns = concatenatePhoneticRuns;
- this.metadata = metadata;
+ this.preferACChoice = preferACChoice;
}
@Override
@@ -219,17 +209,23 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
public void endPrefixMapping(String prefix) throws SAXException {
}
+ /**
+ * Returns true if content should be skipped due to AlternateContent handling.
+ * When preferACChoice is true, skip Fallback; when false, skip Choice.
+ */
+ private boolean inSkippedAlternateContent() {
+ if (preferACChoice) {
+ return inACFallbackDepth > 0;
+ } else {
+ return inACChoiceDepth > 0;
+ }
+ }
+
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
//TODO: checkBox, textBox, sym, headerReference, footerReference, commentRangeEnd
- if (lastStartElementWasP && !PPR.equals(localName)) {
- bodyContentsHandler.startParagraph(currPProperties);
- }
-
- lastStartElementWasP = false;
-
if (uri != null && uri.equals(MC_NS)) {
if (CHOICE.equals(localName)) {
inACChoiceDepth++;
@@ -238,11 +234,11 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
}
}
- if (inACChoiceDepth > 0) {
+ if (inSkippedAlternateContent()) {
return;
}
- if (!includeTextBox && localName.equals(TEXTBOX)) {
+ if (!includeTextBox && (localName.equals(TEXTBOX) || localName.equals(TXBX))) {
inTextBox = true;
return;
}
@@ -258,7 +254,9 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (TAB.equals(localName)) {
runBuffer.append(TAB_CHAR);
} else if (P.equals(localName)) {
- lastStartElementWasP = true;
+ bodyContentsHandler.startParagraph(currPProperties);
+ } else if (PPR.equals(localName)) {
+ inPPr = true;
} else if (B.equals(localName)) { //TODO: add bCs
if (inR && inRPr) {
currRunProperties.setBold(true);
@@ -326,18 +324,30 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (TBL.equals(localName)) {
bodyContentsHandler.startTable();
} else if (BLIP.equals(localName)) { //check for DRAWING_NS
- picRId = atts.getValue(OFFICE_DOC_RELATIONSHIP_NS, "embed");
+ pictureTracker.setBlipRId(atts.getValue(OFFICE_DOC_RELATIONSHIP_NS, "embed"));
} else if ("cNvPr".equals(localName)) { //check for PIC_NS?
- picDescription = atts.getValue("", "descr");
+ pictureTracker.setDescription(atts.getValue("", "descr"));
} else if (PIC.equals(localName)) {
- inPic = true; //check for PIC_NS?
- } //TODO: add sdt, sdtPr, sdtContent goes here statistically
- else if (FOOTNOTE_REFERENCE.equals(localName)) {
+ pictureTracker.startPic(); //check for PIC_NS?
+ } else if (SDT.equals(localName)) {
+ // SDTs can nest; only track placeholder at outermost level
+ if (sdtPlaceholderDepth == 0) {
+ sdtIsPlaceholder = false;
+ }
+ } else if (SDT_PR.equals(localName)) {
+ inSdtPr = true;
+ } else if (SHOWING_PLCHDR.equals(localName) && inSdtPr) {
+ sdtIsPlaceholder = true;
+ } else if (SDT_CONTENT.equals(localName)) {
+ if (sdtIsPlaceholder) {
+ sdtPlaceholderDepth++;
+ }
+ } else if (FOOTNOTE_REFERENCE.equals(localName)) {
String id = atts.getValue(W_NS, "id");
bodyContentsHandler.footnoteReference(id);
} else if (IMAGEDATA.equals(localName)) {
- picRId = atts.getValue(OFFICE_DOC_RELATIONSHIP_NS, "id");
- picDescription = atts.getValue(O_NS, "title");
+ pictureTracker.setImageDataRId(atts.getValue(OFFICE_DOC_RELATIONSHIP_NS, "id"));
+ pictureTracker.setImageDataDescription(atts.getValue(O_NS, "title"));
} else if (INS.equals(localName)) {
startEditedSection(editType.INSERT, atts);
} else if (DEL_TEXT.equals(localName)) {
@@ -351,7 +361,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (OLE_OBJECT.equals(localName)) { //check for O_NS?
String type = null;
String refId = null;
- //TODO: clean this up and ...want to get ProgID?
+ String progId = null;
for (int i = 0; i < atts.getLength(); i++) {
String attLocalName = atts.getLocalName(i);
String attValue = atts.getValue(i);
@@ -360,22 +370,24 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (OFFICE_DOC_RELATIONSHIP_NS.equals(atts.getURI(i)) &&
attLocalName.equals("id")) {
refId = attValue;
+ } else if ("ProgID".equals(attLocalName)) {
+ progId = attValue;
}
}
if ("Embed".equals(type)) {
- bodyContentsHandler.embeddedOLERef(refId);
+ String emfRId = pictureTracker.getImageDataRId();
+ bodyContentsHandler.embeddedOLERef(refId, progId, emfRId);
} else if ("Link".equals(type)) {
- // Linked OLE object - references external file
bodyContentsHandler.linkedOLERef(refId);
- if (metadata != null) {
- metadata.set(Office.HAS_LINKED_OLE_OBJECTS, true);
- }
}
} else if (CR.equals(localName)) {
runBuffer.append(NEWLINE);
} else if (ENDNOTE_REFERENCE.equals(localName)) {
String id = atts.getValue(W_NS, "id");
bodyContentsHandler.endnoteReference(id);
+ } else if (COMMENT_REFERENCE.equals(localName)) {
+ String id = atts.getValue(W_NS, "id");
+ bodyContentsHandler.commentReference(id);
} else if (V.equals(localName) && C_NS.equals(uri)) { // in value in a chart
inV = true;
} else if (RT.equals(localName)) {
@@ -385,6 +397,8 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
if ("0".equals(val) || "false".equals(val)) {
hiddenSlide = true;
}
+ } else if (TIMING.equals(localName)) {
+ hasAnimations = true;
} else if (FLD_CHAR.equals(localName)) {
String fldCharType = atts.getValue(W_NS, FLD_CHAR_TYPE);
if ("begin".equals(fldCharType)) {
@@ -392,22 +406,17 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
instrTextBuffer.setLength(0);
} else if ("separate".equals(fldCharType)) {
// Parse instrText for HYPERLINK
- String url = parseHyperlinkFromInstrText(instrTextBuffer.toString());
+ String url = FieldCodeParser.parseHyperlinkFromInstrText(instrTextBuffer.toString());
if (url != null) {
- bodyContentsHandler.hyperlinkStart(url);
+ bodyContentsHandler.fieldCodeHyperlinkStart(url);
inFieldHyperlink = true;
- if (metadata != null) {
- metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
- }
} else {
// Check for external reference fields (INCLUDEPICTURE, INCLUDETEXT, etc.)
StringBuilder fieldType = new StringBuilder();
- String extUrl = parseExternalRefFromInstrText(instrTextBuffer.toString(), fieldType);
+ String extUrl = FieldCodeParser.parseExternalRefFromInstrText(
+ instrTextBuffer.toString(), fieldType);
if (extUrl != null) {
bodyContentsHandler.externalRef(fieldType.toString(), extUrl);
- if (metadata != null) {
- metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
- }
}
}
} else if ("end".equals(fldCharType)) {
@@ -427,9 +436,6 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
String hyperlink = linkedRelationships.get(hyperlinkId);
if (hyperlink != null) {
bodyContentsHandler.externalRef("hlinkHover", hyperlink);
- if (metadata != null) {
- metadata.set(Office.HAS_HOVER_HYPERLINKS, true);
- }
}
}
} else if (SHAPE.equals(localName) && V_NS.equals(uri)) {
@@ -440,9 +446,6 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
}
if (href != null && !href.isEmpty()) {
bodyContentsHandler.externalRef("vml-shape-href", href);
- if (metadata != null) {
- metadata.set(Office.HAS_VML_HYPERLINKS, true);
- }
}
}
@@ -479,65 +482,6 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
return -1;
}
- /**
- * Parses a HYPERLINK URL from instrText field code content.
- * Field codes like: HYPERLINK "https://example.com"
- *
- * @param instrText the accumulated instrText content
- * @return the URL if found, or null
- */
- private String parseHyperlinkFromInstrText(String instrText) {
- if (instrText == null || instrText.isEmpty()) {
- return null;
- }
- Matcher m = HYPERLINK_PATTERN.matcher(instrText.trim());
- if (m.find()) {
- return m.group(1);
- }
- return null;
- }
-
- /**
- * Parses URLs from instrText field codes that reference external resources.
- * This includes INCLUDEPICTURE, INCLUDETEXT, IMPORT, and LINK fields.
- *
- * @param instrText the accumulated instrText content
- * @param fieldType output parameter - will contain the field type if found
- * @return the URL if found, or null
- */
- private String parseExternalRefFromInstrText(String instrText, StringBuilder fieldType) {
- if (instrText == null || instrText.isEmpty()) {
- return null;
- }
- String trimmed = instrText.trim();
-
- Matcher m = INCLUDEPICTURE_PATTERN.matcher(trimmed);
- if (m.find()) {
- fieldType.append("INCLUDEPICTURE");
- return m.group(1);
- }
-
- m = INCLUDETEXT_PATTERN.matcher(trimmed);
- if (m.find()) {
- fieldType.append("INCLUDETEXT");
- return m.group(1);
- }
-
- m = IMPORT_PATTERN.matcher(trimmed);
- if (m.find()) {
- fieldType.append("IMPORT");
- return m.group(1);
- }
-
- m = LINK_PATTERN.matcher(trimmed);
- if (m.find()) {
- fieldType.append("LINK");
- return m.group(1);
- }
-
- return null;
- }
-
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
@@ -546,17 +490,20 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (FALLBACK.equals(localName)) {
inACFallbackDepth--;
}
- if (inACChoiceDepth > 0) {
+ if (inSkippedAlternateContent()) {
return;
}
- if (!includeTextBox && localName.equals(TEXTBOX)) {
+ if (!includeTextBox && (localName.equals(TEXTBOX) || localName.equals(TXBX))) {
inTextBox = false;
return;
}
if (PIC.equals(localName)) { //PIC_NS
- handlePict();
- inPic = false;
+ pictureTracker.endPicture();
+ if (inHlinkClick) {
+ bodyContentsHandler.hyperlinkEnd();
+ inHlinkClick = false;
+ }
return;
} else if (RPR.equals(localName)) {
inRPr = false;
@@ -565,10 +512,8 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
} else if (T.equals(localName)) {
inT = false;
} else if (PPR.equals(localName)) {
- if (!pStarted) {
- bodyContentsHandler.startParagraph(currPProperties);
- pStarted = true;
- }
+ inPPr = false;
+ bodyContentsHandler.setParagraphProperties(currPProperties);
currPProperties.reset();
} else if (P.equals(localName)) {
if (runBuffer.length() > 0) {
@@ -577,7 +522,6 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
bodyContentsHandler.run(currRunProperties, runBuffer.toString());
runBuffer.setLength(0);
}
- pStarted = false;
bodyContentsHandler.endParagraph();
} else if (TC.equals(localName)) {
bodyContentsHandler.endTableCell();
@@ -594,8 +538,16 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
editType = EditType.NONE;
} else if (HYPERLINK.equals(localName)) {
bodyContentsHandler.hyperlinkEnd();
+ } else if (SDT_PR.equals(localName)) {
+ inSdtPr = false;
+ } else if (SDT_CONTENT.equals(localName)) {
+ if (sdtPlaceholderDepth > 0) {
+ sdtPlaceholderDepth--;
+ }
+ } else if (SDT.equals(localName)) {
+ sdtIsPlaceholder = false;
} else if (PICT.equals(localName)) {
- handlePict();
+ pictureTracker.endPicture();
} else if (V.equals(localName) && C_NS.equals(uri)) { // in value in a chart
inV = false;
handleEndOfRun();
@@ -631,24 +583,15 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
currRunProperties.setUnderline(UnderlinePatterns.NONE.name());
}
- private void handlePict() throws SAXException {
- String picFileName = null;
- if (picRId != null) {
- picFileName = linkedRelationships.get(picRId);
- }
- bodyContentsHandler.embeddedPicRef(picFileName, picDescription);
- picDescription = null;
- picRId = null;
- inPic = false;
- }
-
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
- if (inACChoiceDepth > 0) {
+ if (inSkippedAlternateContent()) {
return;
} else if (!includeTextBox && inTextBox) {
return;
+ } else if (sdtPlaceholderDepth > 0) {
+ return;
}
if (editType.equals(EditType.MOVE_FROM) && inT) {
@@ -670,10 +613,12 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
- if (inACChoiceDepth > 0) {
+ if (inSkippedAlternateContent()) {
return;
} else if (!includeTextBox && inTextBox) {
return;
+ } else if (sdtPlaceholderDepth > 0) {
+ return;
}
if (inT) {
@@ -691,81 +636,11 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
}
}
- public enum EditType {
- NONE, INSERT, DELETE, MOVE_TO, MOVE_FROM
- }
-
- public interface XWPFBodyContentsHandler {
-
- void run(RunProperties runProperties, String contents) throws SAXException;
-
- /**
- * @param link the link; can be null
- */
- void hyperlinkStart(String link) throws SAXException;
-
- void hyperlinkEnd() throws SAXException;
-
- void startParagraph(ParagraphProperties paragraphProperties) throws SAXException;
-
- void endParagraph() throws SAXException;
-
- void startTable() throws SAXException;
-
- void endTable() throws SAXException;
-
- void startTableRow() throws SAXException;
-
- void endTableRow() throws SAXException;
-
- void startTableCell() throws SAXException;
-
- void endTableCell() throws SAXException;
-
- void startSDT() throws SAXException;
-
- void endSDT() throws SAXException;
-
- void startEditedSection(String editor, Date date, EditType editType) throws SAXException;
-
- void endEditedSection() throws SAXException;
-
- boolean isIncludeDeletedText() throws SAXException;
-
- void footnoteReference(String id) throws SAXException;
-
- void endnoteReference(String id) throws SAXException;
-
- boolean isIncludeMoveFromText() throws SAXException;
-
- void embeddedOLERef(String refId) throws SAXException;
-
- /**
- * Called when a linked (vs embedded) OLE object is found.
- * These reference external files and are a security concern.
- */
- void linkedOLERef(String refId) throws SAXException;
-
- void embeddedPicRef(String picFileName, String picDescription) throws SAXException;
-
- void startBookmark(String id, String name) throws SAXException;
-
- void endBookmark(String id) throws SAXException;
-
- /**
- * Called when an external reference URL is found in a field code.
- * This includes INCLUDEPICTURE, INCLUDETEXT, IMPORT, LINK fields,
- * and DrawingML/VML hyperlinks on shapes.
- *
- * @param fieldType the type of field (e.g., "INCLUDEPICTURE", "hlinkHover", "vml-href")
- * @param url the external URL
- */
- default void externalRef(String fieldType, String url) throws SAXException {
- // Default no-op implementation for backward compatibility
- }
- }
-
public boolean isHiddenSlide() {
return hiddenSlide;
}
+
+ public boolean hasAnimations() {
+ return hasAnimations;
+ }
}
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/ParagraphProperties.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
index 80c004bea2..45fe1dfd05 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
@@ -23,6 +23,15 @@ public class ParagraphProperties {
private int ilvl = -1;
private int numId = -1;
+ public ParagraphProperties() {
+ }
+
+ public ParagraphProperties(ParagraphProperties other) {
+ this.styleId = other.styleId;
+ this.ilvl = other.ilvl;
+ this.numId = other.numId;
+ }
+
public String getStyleID() {
return styleId;
}
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/PlaceHolderSkipper.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/PlaceHolderSkipper.java
new file mode 100644
index 0000000000..bad784967b
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/PlaceHolderSkipper.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SAX handler wrapper that skips content inside placeholder ({@code <ph>})
+ * elements in PPTX slide masters and layouts. Delegates all non-placeholder
+ * events to the wrapped handler.
+ */
+class PlaceHolderSkipper extends DefaultHandler {
+
+ private final ContentHandler wrappedHandler;
+ private boolean inPH = false;
+
+ PlaceHolderSkipper(ContentHandler wrappedHandler) {
+ this.wrappedHandler = wrappedHandler;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts)
+ throws SAXException {
+ if ("ph".equals(localName)) {
+ inPH = true;
+ }
+ if (!inPH) {
+ wrappedHandler.startElement(uri, localName, qName, atts);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (!inPH) {
+ wrappedHandler.endElement(uri, localName, qName);
+ }
+ if ("sp".equals(localName)) {
+ inPH = false;
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (!inPH) {
+ wrappedHandler.characters(ch, start, length);
+ }
+ }
+
+ @Override
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ if (!inPH) {
+ wrappedHandler.characters(ch, start, length);
+ }
+ }
+}
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/SAXBasedMetadataExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SAXBasedMetadataExtractor.java
new file mode 100644
index 0000000000..7307e35437
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SAXBasedMetadataExtractor.java
@@ -0,0 +1,457 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Optional;
+
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageProperties;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.DublinCore;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.OfficeOpenXMLExtended;
+import org.apache.tika.metadata.PagedText;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.SummaryExtractor;
+import org.apache.tika.utils.XMLReaderUtils;
+
+/**
+ * SAX-based metadata extractor for OOXML documents that reads document properties
+ * directly from the OPC package without needing POIXMLProperties or ooxml-lite schemas.
+ * <p>
+ * Core properties are read from {@link PackagePropertiesPart} (OPC level).
+ * Extended properties (app.xml) and custom properties (custom.xml) are parsed with SAX.
+ */
+class SAXBasedMetadataExtractor extends MetadataExtractor {
+
+ private static final String EXTENDED_PROPERTIES_REL =
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
+ private static final String CUSTOM_PROPERTIES_REL =
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
+
+ private final OPCPackage opcPackage;
+ private final ParseContext parseContext;
+
+ SAXBasedMetadataExtractor(OPCPackage opcPackage, ParseContext parseContext) {
+ super(null);
+ this.opcPackage = opcPackage;
+ this.parseContext = parseContext;
+ }
+
+ @Override
+ public void extract(Metadata metadata) throws TikaException {
+ extractCoreProperties(metadata);
+ extractExtendedProperties(metadata);
+ extractCustomProperties(metadata);
+ }
+
+ private void extractCoreProperties(Metadata metadata) {
+ try {
+ PackageProperties props = opcPackage.getPackageProperties();
+ if (props == null) {
+ return;
+ }
+ setProperty(metadata, OfficeOpenXMLCore.CATEGORY, props.getCategoryProperty());
+ setProperty(metadata, OfficeOpenXMLCore.CONTENT_STATUS,
+ props.getContentStatusProperty());
+ setProperty(metadata, TikaCoreProperties.CREATED, props.getCreatedProperty());
+ addMultiProperty(metadata, TikaCoreProperties.CREATOR, props.getCreatorProperty());
+ setProperty(metadata, TikaCoreProperties.DESCRIPTION,
+ props.getDescriptionProperty());
+ setProperty(metadata, TikaCoreProperties.IDENTIFIER, props.getIdentifierProperty());
+ addProperty(metadata, DublinCore.SUBJECT, props.getSubjectProperty());
+ addProperty(metadata, Office.KEYWORDS, props.getKeywordsProperty());
+ setProperty(metadata, TikaCoreProperties.LANGUAGE, props.getLanguageProperty());
+ setProperty(metadata, TikaCoreProperties.MODIFIER,
+ props.getLastModifiedByProperty());
+ setProperty(metadata, TikaCoreProperties.PRINT_DATE,
+ props.getLastPrintedProperty());
+ setProperty(metadata, TikaCoreProperties.MODIFIED, props.getModifiedProperty());
+ setProperty(metadata, OfficeOpenXMLCore.REVISION, props.getRevisionProperty());
+ setProperty(metadata, TikaCoreProperties.TITLE, props.getTitleProperty());
+ setProperty(metadata, OfficeOpenXMLCore.VERSION, props.getVersionProperty());
+ } catch (Exception e) {
+ //swallow
+ }
+ }
+
+ private void extractExtendedProperties(Metadata metadata) {
+ try {
+ PackagePart extPart = getRelatedPart(EXTENDED_PROPERTIES_REL);
+ if (extPart == null) {
+ return;
+ }
+ ExtendedPropertiesHandler handler = new ExtendedPropertiesHandler();
+ try (InputStream is = extPart.getInputStream()) {
+ XMLReaderUtils.parseSAX(is, handler, parseContext);
+ }
+ handler.applyTo(metadata);
+ } catch (Exception e) {
+ //swallow
+ }
+ }
+
+ private void extractCustomProperties(Metadata metadata) {
+ try {
+ PackagePart custPart = getRelatedPart(CUSTOM_PROPERTIES_REL);
+ if (custPart == null) {
+ return;
+ }
+ CustomPropertiesHandler handler = new CustomPropertiesHandler();
+ try (InputStream is = custPart.getInputStream()) {
+ XMLReaderUtils.parseSAX(is, handler, parseContext);
+ }
+ handler.applyTo(metadata);
+ } catch (Exception e) {
+ //swallow
+ }
+ }
+
+ private PackagePart getRelatedPart(String relationshipType) {
+ try {
+ PackageRelationshipCollection rels =
+ opcPackage.getRelationshipsByType(relationshipType);
+ if (rels == null || rels.size() == 0) {
+ return null;
+ }
+ PackageRelationship rel = rels.getRelationship(0);
+ if (rel == null) {
+ return null;
+ }
+ return opcPackage.getPart(rel);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private <T> void setProperty(Metadata metadata, Property property,
+ Optional<T> optionalValue) {
+ if (!optionalValue.isPresent()) {
+ return;
+ }
+ T value = optionalValue.get();
+ if (value instanceof Date) {
+ metadata.set(property, (Date) value);
+ } else if (value instanceof String) {
+ metadata.set(property, (String) value);
+ } else if (value instanceof Integer) {
+ metadata.set(property, (Integer) value);
+ } else if (value instanceof Double) {
+ metadata.set(property, (Double) value);
+ }
+ }
+
+ private <T> void addProperty(Metadata metadata, Property property,
+ Optional<T> optionalValue) {
+ if (!optionalValue.isPresent()) {
+ return;
+ }
+ T value = optionalValue.get();
+ if (value instanceof String) {
+ metadata.add(property, (String) value);
+ }
+ }
+
+ private void addMultiProperty(Metadata metadata, Property property,
+ Optional<String> value) {
+ if (!value.isPresent()) {
+ return;
+ }
+ SummaryExtractor.addMulti(metadata, property, value.get());
+ }
+
+ /**
+ * SAX handler for docProps/app.xml (extended properties).
+ */
+ private static class ExtendedPropertiesHandler extends DefaultHandler {
+
+ private String application;
+ private String appVersion;
+ private String company;
+ private String manager;
+ private String notes;
+ private String presentationFormat;
+ private String template;
+ private int totalTime;
+ private int docSecurity;
+ private int pages;
+ private int slides;
+ private int paragraphs;
+ private int lines;
+ private int words;
+ private int characters;
+ private int charactersWithSpaces;
+
+ private String currentElement;
+ private final StringBuilder textBuffer = new StringBuilder();
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts) {
+ currentElement = localName;
+ textBuffer.setLength(0);
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) {
+ textBuffer.append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if (!localName.equals(currentElement)) {
+ return;
+ }
+ String val = textBuffer.toString().trim();
+ if (val.isEmpty()) {
+ currentElement = null;
+ return;
+ }
+ switch (localName) {
+ case "Application":
+ application = val;
+ break;
+ case "AppVersion":
+ appVersion = val;
+ break;
+ case "Company":
+ company = val;
+ break;
+ case "Manager":
+ manager = val;
+ break;
+ case "Notes":
+ notes = val;
+ break;
+ case "PresentationFormat":
+ presentationFormat = val;
+ break;
+ case "Template":
+ template = val;
+ break;
+ case "TotalTime":
+ totalTime = safeParseInt(val);
+ break;
+ case "DocSecurity":
+ docSecurity = safeParseInt(val);
+ break;
+ case "Pages":
+ pages = safeParseInt(val);
+ break;
+ case "Slides":
+ slides = safeParseInt(val);
+ break;
+ case "Paragraphs":
+ paragraphs = safeParseInt(val);
+ break;
+ case "Lines":
+ lines = safeParseInt(val);
+ break;
+ case "Words":
+ words = safeParseInt(val);
+ break;
+ case "Characters":
+ characters = safeParseInt(val);
+ break;
+ case "CharactersWithSpaces":
+ charactersWithSpaces = safeParseInt(val);
+ break;
+ default:
+ break;
+ }
+ currentElement = null;
+ }
+
+ private int safeParseInt(String val) {
+ try {
+ // Handle unsigned int overflow (TIKA-2055)
+ long l = Long.parseLong(val);
+ if (l > Integer.MAX_VALUE || l < 0) {
+ return 0;
+ }
+ return (int) l;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ void applyTo(Metadata metadata) {
+ setIfNotNull(metadata, OfficeOpenXMLExtended.APPLICATION, application);
+ setIfNotNull(metadata, OfficeOpenXMLExtended.APP_VERSION, appVersion);
+ setIfNotNull(metadata, TikaCoreProperties.PUBLISHER, company);
+ setIfNotNull(metadata, OfficeOpenXMLExtended.COMPANY, company);
+ if (manager != null) {
+ SummaryExtractor.addMulti(metadata, OfficeOpenXMLExtended.MANAGER, manager);
+ }
+ setIfNotNull(metadata, OfficeOpenXMLExtended.NOTES, notes);
+ setIfNotNull(metadata, OfficeOpenXMLExtended.PRESENTATION_FORMAT, presentationFormat);
+ setIfNotNull(metadata, OfficeOpenXMLExtended.TEMPLATE, template);
+ setIfPositive(metadata, OfficeOpenXMLExtended.TOTAL_TIME, totalTime);
+ setIfPositive(metadata, OfficeOpenXMLExtended.DOC_SECURITY, docSecurity);
+ metadata.set(OfficeOpenXMLExtended.DOC_SECURITY_STRING,
+ getDocSecurityString(docSecurity));
+
+ if (pages > 0) {
+ metadata.set(PagedText.N_PAGES, pages);
+ } else if (slides > 0) {
+ metadata.set(PagedText.N_PAGES, slides);
+ }
+
+ setIfPositive(metadata, Office.PAGE_COUNT, pages);
+ setIfPositive(metadata, Office.SLIDE_COUNT, slides);
+ setIfPositive(metadata, Office.PARAGRAPH_COUNT, paragraphs);
+ setIfPositive(metadata, Office.LINE_COUNT, lines);
+ setIfPositive(metadata, Office.WORD_COUNT, words);
+ setIfPositive(metadata, Office.CHARACTER_COUNT, characters);
+ setIfPositive(metadata, Office.CHARACTER_COUNT_WITH_SPACES, charactersWithSpaces);
+ }
+
+ private void setIfNotNull(Metadata metadata, Property property, String value) {
+ if (value != null) {
+ metadata.set(property, value);
+ }
+ }
+
+ private void setIfPositive(Metadata metadata, Property property, int value) {
+ if (value > 0) {
+ metadata.set(property, value);
+ }
+ }
+
+ private String getDocSecurityString(int flag) {
+ switch (flag) {
+ case 0:
+ return OfficeOpenXMLExtended.SECURITY_NONE;
+ case 1:
+ return OfficeOpenXMLExtended.SECURITY_PASSWORD_PROTECTED;
+ case 2:
+ return OfficeOpenXMLExtended.SECURITY_READ_ONLY_RECOMMENDED;
+ case 4:
+ return OfficeOpenXMLExtended.SECURITY_READ_ONLY_ENFORCED;
+ case 8:
+ return OfficeOpenXMLExtended.SECURITY_LOCKED_FOR_ANNOTATIONS;
+ default:
+ return OfficeOpenXMLExtended.SECURITY_UNKNOWN;
+ }
+ }
+ }
+
+ /**
+ * SAX handler for docProps/custom.xml (custom properties).
+ */
+ private static class CustomPropertiesHandler extends DefaultHandler {
+
+ private static final String VT_NS =
+ "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
+
+ private final Metadata customMetadata = new Metadata();
+ private String currentPropertyName;
+ private String currentValueType;
+ private final StringBuilder textBuffer = new StringBuilder();
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts) {
+ if ("property".equals(localName)) {
+ currentPropertyName = atts.getValue("name");
+ currentValueType = null;
+ } else if (VT_NS.equals(uri) && currentPropertyName != null) {
+ currentValueType = localName;
+ textBuffer.setLength(0);
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) {
+ textBuffer.append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if (VT_NS.equals(uri) && currentValueType != null &&
+ localName.equals(currentValueType) && currentPropertyName != null) {
+ String val = textBuffer.toString().trim();
+ String propName = "custom:" + currentPropertyName;
+ switch (currentValueType) {
+ case "lpwstr":
+ case "lpstr":
+ case "bstr":
+ customMetadata.set(propName, val);
+ break;
+ case "filetime":
+ case "date":
+ Property tikaProp = Property.externalDate(propName);
+ customMetadata.set(tikaProp, val);
+ break;
+ case "bool":
+ customMetadata.set(propName, val);
+ break;
+ case "i1":
+ case "i2":
+ case "i4":
+ case "int":
+ case "ui1":
+ case "ui2":
+ customMetadata.set(propName, val);
+ break;
+ case "i8":
+ case "ui4":
+ case "ui8":
+ case "uint":
+ customMetadata.set(propName, val);
+ break;
+ case "r4":
+ case "r8":
+ customMetadata.set(propName, val);
+ break;
+ case "decimal":
+ try {
+ BigDecimal d = new BigDecimal(val);
+ customMetadata.set(propName, d.toPlainString());
+ } catch (NumberFormatException e) {
+ //swallow
+ }
+ break;
+ default:
+ break;
+ }
+ currentValueType = null;
+ } else if ("property".equals(localName)) {
+ currentPropertyName = null;
+ }
+ }
+
+ void applyTo(Metadata metadata) {
+ for (String name : customMetadata.names()) {
+ for (String value : customMetadata.getValues(name)) {
+ metadata.add(name, value);
+ }
+ }
+ }
+ }
+}
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/SXSLFPowerPointExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXSLFPowerPointExtractorDecorator.java
index 45f4657f75..c16b75fd8c 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXSLFPowerPointExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXSLFPowerPointExtractorDecorator.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipException;
@@ -34,7 +35,6 @@ import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.xslf.usermodel.XSLFRelation;
import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
@@ -99,39 +99,27 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
protected void buildXHTML(XHTMLContentHandler xhtml) throws SAXException, IOException {
loadCommentAuthors();
+ addCommentAuthorMetadata();
+ List<PackagePart> orderedSlides = getOrderedSlideParts();
- PackageRelationshipCollection slidesPRC = null;
- try {
- slidesPRC = mainDocument.getRelationshipsByType(XSLFRelation.SLIDE.getRelation());
- } catch (InvalidFormatException e) {
- metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
- ExceptionUtils.getStackTrace(e));
- }
-
- if (slidesPRC != null && slidesPRC.size() > 0) {
- for (int i = 0; i < slidesPRC.size(); i++) {
- try {
- handleSlidePart(mainDocument.getRelatedPart(slidesPRC.getRelationship(i)),
- xhtml);
- } catch (InvalidFormatException | ZipException e) {
- metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
- ExceptionUtils.getStackTrace(e));
- }
+ int hiddenSlideCount = 0;
+ for (PackagePart slidePart : orderedSlides) {
+ try {
+ hiddenSlideCount += handleSlidePart(slidePart, xhtml);
+ } catch (ZipException e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
}
}
+ if (hiddenSlideCount > 0) {
+ metadata.set(Office.NUM_HIDDEN_SLIDES, hiddenSlideCount);
+ }
if (config.isIncludeSlideMasterContent()) {
- handleGeneralTextContainingPart(XSLFRelation.SLIDE_MASTER.getRelation(), "slide-master",
- mainDocument, metadata, new PlaceHolderSkipper(
- new OOXMLWordAndPowerPointTextHandler(
- new OOXMLTikaBodyPartHandler(xhtml),
- new HashMap<>())));
-
- handleGeneralTextContainingPart(HANDOUT_MASTER, "slide-handout-master", mainDocument,
- metadata,
- new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
- new HashMap<>()));
+ // Handout master is presentation-level, not per-slide
+ handleTextPartWithCleanup(HANDOUT_MASTER, "slide-handout-master", mainDocument,
+ xhtml, new HashMap<>(), false);
}
}
@@ -160,7 +148,7 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
}
try (InputStream stream = commentAuthorsPart.getInputStream()) {
XMLReaderUtils.parseSAX(stream,
- new XSLFCommentAuthorHandler(), context);
+ new XSLFCommentAuthorHandler(commentAuthors), context);
} catch (TikaException | SAXException | IOException e) {
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
@@ -170,58 +158,194 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
}
- private void handleSlidePart(PackagePart slidePart, XHTMLContentHandler xhtml)
+ private void addCommentAuthorMetadata() {
+ for (String name : commentAuthors.nameMap.values()) {
+ if (name != null && !name.isBlank()) {
+ metadata.add(Office.COMMENT_PERSONS, name);
+ }
+ }
+ }
+
+ /**
+ * Returns the first related part for the given relationship type,
+ * or null if none found.
+ */
+ private PackagePart getRelatedPartByType(PackagePart source, String relationType) {
+ try {
+ PackageRelationshipCollection prc = source.getRelationshipsByType(relationType);
+ if (prc != null && prc.size() > 0) {
+ return source.getRelatedPart(prc.getRelationship(0));
+ }
+ } catch (InvalidFormatException | IllegalArgumentException e) {
+ // missing part
+ }
+ return null;
+ }
+
+ /**
+ * Returns slide parts in presentation order by parsing the sldIdLst
+ * from presentation.xml. Any slides found in .rels but not in
+ * the sldIdLst are appended at the end.
+ */
+ private List<PackagePart> getOrderedSlideParts() {
+ // Step 1: parse presentation.xml to get ordered rIds from sldIdLst
+ List<String> orderedRIds = new ArrayList<>();
+ try (InputStream is = mainDocument.getInputStream()) {
+ XMLReaderUtils.parseSAX(is, new DefaultHandler() {
+ private boolean inSldIdLst = false;
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) {
+ if ("sldIdLst".equals(localName)) {
+ inSldIdLst = true;
+ } else if (inSldIdLst && "sldId".equals(localName)) {
+ String rId = atts.getValue(
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
+ "id");
+ if (rId != null) {
+ orderedRIds.add(rId);
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if ("sldIdLst".equals(localName)) {
+ inSldIdLst = false;
+ }
+ }
+ }, context);
+ } catch (Exception e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
+ }
+
+ // Step 2: build rId -> PackagePart map from relationships
+ Map<String, PackagePart> rIdToSlide = new LinkedHashMap<>();
+ try {
+ PackageRelationshipCollection slidesPRC =
+ mainDocument.getRelationshipsByType(XSLFRelation.SLIDE.getRelation());
+ if (slidesPRC != null) {
+ for (int i = 0; i < slidesPRC.size(); i++) {
+ PackageRelationship rel = slidesPRC.getRelationship(i);
+ try {
+ PackagePart part = mainDocument.getRelatedPart(rel);
+ if (part != null) {
+ rIdToSlide.put(rel.getId(), part);
+ }
+ } catch (InvalidFormatException | IllegalArgumentException e) {
+ // skip missing parts
+ }
+ }
+ }
+ } catch (InvalidFormatException e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
+ }
+
+ // Step 3: assemble in presentation order, then append orphans
+ List<PackagePart> result = new ArrayList<>();
+ for (String rId : orderedRIds) {
+ PackagePart part = rIdToSlide.remove(rId);
+ if (part != null) {
+ result.add(part);
+ }
+ }
+ // append any slides in .rels but not in sldIdLst
+ if (!rIdToSlide.isEmpty()) {
+ metadata.set(Office.NUM_UNLISTED_SLIDES, rIdToSlide.size());
+ for (PackagePart part : rIdToSlide.values()) {
+ metadata.add(Office.UNLISTED_SLIDE_NAMES, part.getPartName().getName());
+ }
+ result.addAll(rIdToSlide.values());
+ }
+ return result;
+ }
+
+ /**
+ * @return 1 if the slide is hidden, 0 otherwise
+ */
+ private int handleSlidePart(PackagePart slidePart, XHTMLContentHandler xhtml)
throws IOException, SAXException {
Map<String, String> linkedRelationships =
loadLinkedRelationships(slidePart, false, metadata);
-// Map<String, String> hyperlinks = loadHyperlinkRelationships(packagePart);
+ int hidden = 0;
xhtml.startElement("div", "class", "slide-content");
+ OOXMLTikaBodyPartHandler bodyHandler = new OOXMLTikaBodyPartHandler(xhtml, metadata);
try (InputStream stream = slidePart.getInputStream()) {
OOXMLWordAndPowerPointTextHandler wordAndPPTHandler = new OOXMLWordAndPowerPointTextHandler(
- new OOXMLTikaBodyPartHandler(xhtml), linkedRelationships);
+ bodyHandler, linkedRelationships);
XMLReaderUtils.parseSAX(stream,
new EmbeddedContentHandler(wordAndPPTHandler), context);
if (wordAndPPTHandler.isHiddenSlide()) {
metadata.set(Office.HAS_HIDDEN_SLIDES, true);
+ hidden = 1;
+ }
+ if (wordAndPPTHandler.hasAnimations()) {
+ metadata.set(Office.HAS_ANIMATIONS, true);
}
} catch (TikaException | IOException e) {
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
ExceptionUtils.getStackTrace(e));
}
-
+ bodyHandler.closeInlineElements();
xhtml.endElement("div");
if (config.isIncludeSlideMasterContent()) {
- handleGeneralTextContainingPart(XSLFRelation.SLIDE_LAYOUT.getRelation(),
- "slide-master-content", slidePart, metadata, new PlaceHolderSkipper(
- new OOXMLWordAndPowerPointTextHandler(
- new OOXMLTikaBodyPartHandler(xhtml), linkedRelationships)));
+ // Extract the slide layout (per-slide)
+ PackagePart layoutPart = getRelatedPartByType(slidePart,
+ XSLFRelation.SLIDE_LAYOUT.getRelation());
+ if (layoutPart != null) {
+ handleTextPartWithCleanup(XSLFRelation.SLIDE_LAYOUT.getRelation(),
+ "slide-master-content", slidePart, xhtml, linkedRelationships, true);
+ // Follow layout → slide master chain
+ handleTextPartWithCleanup(XSLFRelation.SLIDE_MASTER.getRelation(),
+ "slide-master-content", layoutPart, xhtml, linkedRelationships, true);
+ }
}
if (config.isIncludeSlideNotes()) {
- handleGeneralTextContainingPart(XSLFRelation.NOTES.getRelation(), "slide-notes",
- slidePart, metadata,
- new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
- linkedRelationships));
+ handleTextPartWithCleanup(XSLFRelation.NOTES.getRelation(), "slide-notes",
+ slidePart, xhtml, linkedRelationships, false);
if (config.isIncludeSlideMasterContent()) {
- handleGeneralTextContainingPart(XSLFRelation.NOTES_MASTER.getRelation(),
- "slide-notes-master", slidePart, metadata,
- new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
- linkedRelationships));
-
+ handleTextPartWithCleanup(XSLFRelation.NOTES_MASTER.getRelation(),
+ "slide-notes-master", slidePart, xhtml, linkedRelationships, false);
}
}
handleGeneralTextContainingPart(XSLFRelation.COMMENTS.getRelation(), null, slidePart,
- metadata, new XSLFCommentsHandler(xhtml));
+ metadata, new XSLFCommentsHandler(xhtml, commentAuthors));
- handleGeneralTextContainingPart(AbstractOOXMLExtractor.RELATION_DIAGRAM_DATA,
- "diagram-data", slidePart, metadata,
- new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
- linkedRelationships));
+ handleTextPartWithCleanup(AbstractOOXMLExtractor.RELATION_DIAGRAM_DATA,
+ "diagram-data", slidePart, xhtml, linkedRelationships, false);
- handleGeneralTextContainingPart(XSLFRelation.CHART.getRelation(), "chart", slidePart,
- metadata, new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
- linkedRelationships));
+ handleTextPartWithCleanup(XSLFRelation.CHART.getRelation(), "chart", slidePart,
+ xhtml, linkedRelationships, false);
+ return hidden;
+ }
+
+ /**
+ * Handles a text-containing part with guaranteed inline element cleanup.
+ * Creates an OOXMLTikaBodyPartHandler, parses the part, then calls
+ * closeInlineElements() to ensure no unclosed tags leak into subsequent output.
+ *
+ * @param usePlaceholderSkipper if true, wraps the handler in a PlaceHolderSkipper
+ */
+ private void handleTextPartWithCleanup(String contentType, String xhtmlClassLabel,
+ PackagePart parentPart, XHTMLContentHandler xhtml,
+ Map<String, String> linkedRelationships,
+ boolean usePlaceholderSkipper) throws SAXException {
+ OOXMLTikaBodyPartHandler bodyHandler = new OOXMLTikaBodyPartHandler(xhtml);
+ OOXMLWordAndPowerPointTextHandler textHandler =
+ new OOXMLWordAndPowerPointTextHandler(bodyHandler, linkedRelationships);
+ DefaultHandler handler = usePlaceholderSkipper
+ ? new PlaceHolderSkipper(textHandler) : textHandler;
+ try {
+ handleGeneralTextContainingPart(contentType, xhtmlClassLabel, parentPart,
+ metadata, handler);
+ } finally {
+ bodyHandler.closeInlineElements();
+ }
}
/**
@@ -287,7 +411,8 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
for (String relation : new String[]{XSLFRelation.VML_DRAWING.getRelation(),
XSLFRelation.SLIDE_LAYOUT.getRelation(), XSLFRelation.NOTES_MASTER.getRelation(),
- XSLFRelation.NOTES.getRelation()}) {
+ XSLFRelation.NOTES.getRelation(), XSLFRelation.CHART.getRelation(),
+ XSLFRelation.DIAGRAM_DRAWING.getRelation()}) {
PackageRelationshipCollection prc = null;
try {
prc = slidePart.getRelationshipsByType(relation);
@@ -318,170 +443,4 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
}
- private static class PlaceHolderSkipper extends DefaultHandler {
-
- private final ContentHandler wrappedHandler;
- boolean inPH = false;
-
- PlaceHolderSkipper(ContentHandler wrappedHandler) {
- this.wrappedHandler = wrappedHandler;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes atts)
- throws SAXException {
- if ("ph".equals(localName)) {
- inPH = true;
- }
- if (!inPH) {
- wrappedHandler.startElement(uri, localName, qName, atts);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
-
- if (!inPH) {
- wrappedHandler.endElement(uri, localName, qName);
- }
- if ("sp".equals(localName)) {
- inPH = false;
- }
- }
-
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- if (!inPH) {
- wrappedHandler.characters(ch, start, length);
- }
- }
-
- @Override
- public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
- if (!inPH) {
- wrappedHandler.characters(ch, start, length);
- }
- }
-
-
- }
-
- private class XSLFCommentsHandler extends DefaultHandler {
-
- private String commentAuthorId = null;
- private StringBuilder commentBuffer = new StringBuilder();
- private XHTMLContentHandler xhtml;
-
- XSLFCommentsHandler(XHTMLContentHandler xhtml) {
- this.xhtml = xhtml;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes atts)
- throws SAXException {
- if ("cm".equals(localName)) {
- commentAuthorId = atts.getValue("", "authorId");
- //get date (dt)?
- }
- }
-
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- //TODO: require that we're in <p:text>?
- commentBuffer.append(ch, start, length);
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- if ("cm".equals(localName)) {
-
- xhtml.startElement("p", "class", "slide-comment");
-
- String authorString = commentAuthors.getName(commentAuthorId);
- String authorInitials = commentAuthors.getInitials(commentAuthorId);
- if (authorString != null || authorInitials != null) {
- xhtml.startElement("b");
- boolean authorExists = false;
- if (authorString != null) {
- xhtml.characters(authorString);
- authorExists = true;
- }
- if (authorExists && authorInitials != null) {
- xhtml.characters(" (");
- }
- if (authorInitials != null) {
- xhtml.characters(authorInitials);
- }
- if (authorExists && authorInitials != null) {
- xhtml.characters(")");
- }
- xhtml.endElement("b");
- }
- xhtml.characters(commentBuffer.toString());
- xhtml.endElement("p");
-
- commentBuffer.setLength(0);
- commentAuthorId = null;
- }
- }
- }
-
- private class XSLFCommentAuthorHandler extends DefaultHandler {
- String id = null;
- String name = null;
- String initials = null;
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes atts)
- throws SAXException {
- if ("cmAuthor".equals(localName)) {
- for (int i = 0; i < atts.getLength(); i++) {
- if ("id".equals(atts.getLocalName(i))) {
- id = atts.getValue(i);
- } else if ("name".equals(atts.getLocalName(i))) {
- name = atts.getValue(i);
- } else if ("initials".equals(atts.getLocalName(i))) {
- initials = atts.getValue(i);
- }
- }
- commentAuthors.add(id, name, initials);
- //clear out
- id = null;
- name = null;
- initials = null;
- }
- }
-
- }
-
- private static class CommentAuthors {
- Map<String, String> nameMap = new HashMap<>();
- Map<String, String> initialMap = new HashMap<>();
-
- void add(String id, String name, String initials) {
- if (id == null) {
- return;
- }
- if (name != null) {
- nameMap.put(id, name);
- }
- if (initials != null) {
- initialMap.put(id, initials);
- }
- }
-
- String getName(String id) {
- if (id == null) {
- return null;
- }
- return nameMap.get(id);
- }
-
- String getInitials(String id) {
- if (id == null) {
- return null;
- }
- return initialMap.get(id);
- }
- }
}
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/SXWPFWordExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
index 60eb91ec94..5fc7d9a846 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/SXWPFWordExtractorDecorator.java
@@ -19,19 +19,20 @@ package org.apache.tika.parser.microsoft.ooxml;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.zip.ZipException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.xssf.usermodel.XSSFRelation;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.xmlbeans.XmlException;
import org.xml.sax.Attributes;
@@ -39,11 +40,14 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.tika.exception.TikaException;
+import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.EMFParser;
import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor;
+import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFFeatureExtractor;
import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFNumberingShim;
import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFStylesShim;
import org.apache.tika.sax.EmbeddedContentHandler;
@@ -70,7 +74,8 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
new String[]{XWPFRelation.HEADER.getRelation(), XWPFRelation.FOOTER.getRelation(),
XWPFRelation.FOOTNOTE.getRelation(),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes",
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"};
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
+ AbstractOOXMLExtractor.RELATION_DIAGRAM_DATA};
// Relationship types for Word settings
private static final String SETTINGS_RELATION =
@@ -94,6 +99,7 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
private final OPCPackage opcPackage;
private final ParseContext context;
private final Metadata metadata;
+ private final Map<String, EmbeddedPartMetadata> embeddedPartMetadataMap = new HashMap<>();
public SXWPFWordExtractorDecorator(Metadata metadata, ParseContext context,
@@ -104,6 +110,10 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
this.opcPackage = extractor.getPackage();
}
+ @Override
+ public MetadataExtractor getMetadataExtractor() {
+ return new SAXBasedMetadataExtractor(opcPackage, context);
+ }
@Override
protected void buildXHTML(XHTMLContentHandler xhtml)
@@ -143,6 +153,9 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
* subdocuments, and framesets.
*/
private void detectSecurityFeatures(PackagePart documentPart, XHTMLContentHandler xhtml) {
+ // Extract document features (hidden text, track changes, comments, comment persons)
+ new XWPFFeatureExtractor().process(documentPart, metadata, context);
+
// Check for attached template (external template reference)
try {
PackageRelationshipCollection templateRels =
@@ -180,7 +193,7 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
PackageRelationshipCollection settingsRels =
documentPart.getRelationshipsByType(SETTINGS_RELATION);
if (settingsRels != null && settingsRels.size() > 0) {
- PackagePart settingsPart = documentPart.getRelatedPart(settingsRels.getRelationship(0));
+ PackagePart settingsPart = safeGetRelatedPart(documentPart, settingsRels.getRelationship(0));
if (settingsPart != null) {
try (InputStream is = settingsPart.getInputStream()) {
WordSettingsHandler handler = new WordSettingsHandler(xhtml);
@@ -200,7 +213,7 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
PackageRelationshipCollection webSettingsRels =
documentPart.getRelationshipsByType(WEB_SETTINGS_RELATION);
if (webSettingsRels != null && webSettingsRels.size() > 0) {
- PackagePart webSettingsPart = documentPart.getRelatedPart(webSettingsRels.getRelationship(0));
+ PackagePart webSettingsPart = safeGetRelatedPart(documentPart, webSettingsRels.getRelationship(0));
if (webSettingsPart != null) {
try (InputStream is = webSettingsPart.getInputStream()) {
WebSettingsHandler handler = new WebSettingsHandler(xhtml);
@@ -211,8 +224,23 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
}
}
}
- } catch (InvalidFormatException | IOException | TikaException | SAXException e) {
- // swallow
+ } catch (InvalidFormatException | IOException | TikaException | SAXException |
+ IllegalArgumentException e) {
+ // swallow -- POI throws IllegalArgumentException when
+ // a relationship references a part missing from the package
+ }
+ }
+
+ /**
+ * Safely resolves a related part from a relationship. Returns {@code null}
+ * instead of throwing {@link IllegalArgumentException} when the target
+ * part is missing from the package (e.g. truncated / salvaged zips).
+ */
+ private static PackagePart safeGetRelatedPart(PackagePart source, PackageRelationship rel) {
+ try {
+ return source.getRelatedPart(rel);
+ } catch (InvalidFormatException | IllegalArgumentException e) {
+ return null;
}
}
@@ -234,8 +262,9 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
private void handleDocumentPart(PackagePart documentPart, XHTMLContentHandler xhtml)
throws IOException, SAXException {
//load the numbering/list manager and styles from the main document part
- XWPFNumbering numbering = loadNumbering(documentPart);
- XWPFListManager listManager = new XWPFListManager(numbering);
+ XWPFNumberingShim numbering = loadNumbering(documentPart);
+ XWPFListManager listManager = new XWPFListManager(
+ numbering != null ? numbering : XWPFNumberingShim.EMPTY);
XWPFStylesShim styles = null;
try {
styles = loadStyles(documentPart);
@@ -247,6 +276,9 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
}
if (config.isIncludeHeadersAndFooters()) {
+ //TODO: the DOM extractor handles per-section headers/footers by detecting
+ // sectPr within paragraphs. We extract all headers/footers at the document level,
+ // which is fine for text extraction since OOXML is flow-based, not page-based.
//headers
try {
PackageRelationshipCollection headersPRC =
@@ -254,8 +286,11 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
if (headersPRC != null) {
for (int i = 0; i < headersPRC.size(); i++) {
PackagePart header =
- documentPart.getRelatedPart(headersPRC.getRelationship(i));
- handlePart(header, styles, listManager, xhtml);
+ safeGetRelatedPart(documentPart, headersPRC.getRelationship(i));
+ if (header != null) {
+ handlePart(header, styles, listManager, xhtml,
+ OOXMLInlineBodyPartMap.EMPTY);
+ }
}
}
} catch (InvalidFormatException | ZipException e) {
@@ -264,18 +299,24 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
}
}
- //main document
+ // Pre-collect footnotes, endnotes, and comments so they can be
+ // inlined at the point of reference in the main document
+ OOXMLInlineBodyPartMap inlinePartMap = collectInlineParts(documentPart);
+
+ //main document — keep reference to body handler for emitted comment tracking
+ java.util.Set<String> emittedCommentIds = java.util.Collections.emptySet();
try {
- handlePart(documentPart, styles, listManager, xhtml);
+ OOXMLTikaBodyPartHandler mainBodyHandler =
+ handlePart(documentPart, styles, listManager, xhtml, inlinePartMap);
+ emittedCommentIds = mainBodyHandler.getEmittedCommentIds();
} catch (ZipException e) {
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
ExceptionUtils.getStackTrace(e));
}
- //for now, just dump other components at end
+ //dump remaining components at end (diagrams, charts, footers)
for (String rel : new String[]{AbstractOOXMLExtractor.RELATION_DIAGRAM_DATA,
- XSSFRelation.CHART.getRelation(), XWPFRelation.FOOTNOTE.getRelation(),
- XWPFRelation.COMMENT.getRelation(), XWPFRelation.FOOTER.getRelation(),
- XWPFRelation.ENDNOTE.getRelation(),}) {
+ XSSFRelation.CHART.getRelation(),
+ XWPFRelation.FOOTER.getRelation()}) {
//skip footers if we shouldn't extract them
if (!config.isIncludeHeadersAndFooters() &&
rel.equals(XWPFRelation.FOOTER.getRelation())) {
@@ -286,8 +327,11 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
if (prc != null) {
for (int i = 0; i < prc.size(); i++) {
PackagePart packagePart =
- documentPart.getRelatedPart(prc.getRelationship(i));
- handlePart(packagePart, styles, listManager, xhtml);
+ safeGetRelatedPart(documentPart, prc.getRelationship(i));
+ if (packagePart != null) {
+ handlePart(packagePart, styles, listManager, xhtml,
+ OOXMLInlineBodyPartMap.EMPTY);
+ }
}
}
} catch (InvalidFormatException | ZipException e) {
@@ -295,25 +339,160 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
ExceptionUtils.getStackTrace(e));
}
}
+ //dump any comments that were NOT inlined via commentReference
+ handleUnreferencedComments(documentPart, styles, listManager, xhtml,
+ inlinePartMap, emittedCommentIds);
}
- private void handlePart(PackagePart packagePart, XWPFStylesShim styles,
- XWPFListManager listManager, XHTMLContentHandler xhtml)
+ private void handleUnreferencedComments(PackagePart documentPart,
+ XWPFStylesShim styles, XWPFListManager listManager,
+ XHTMLContentHandler xhtml, OOXMLInlineBodyPartMap inlinePartMap,
+ java.util.Set<String> emittedCommentIds) {
+ if (!inlinePartMap.hasComments()) {
+ return;
+ }
+ Map<String, String> linkedRelationships = inlinePartMap.getLinkedRelationships();
+ for (Map.Entry<String, byte[]> entry :
+ inlinePartMap.getCommentEntries()) {
+ if (emittedCommentIds.contains(entry.getKey())) {
+ continue;
+ }
+ try {
+ xhtml.startElement("div", "class", "comment");
+ XMLReaderUtils.parseSAX(
+ new java.io.ByteArrayInputStream(entry.getValue()),
+ new EmbeddedContentHandler(
+ new OOXMLWordAndPowerPointTextHandler(
+ new OOXMLTikaBodyPartHandler(xhtml),
+ linkedRelationships)),
+ context);
+ xhtml.endElement("div");
+ } catch (TikaException | IOException | SAXException e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
+ }
+ }
+ }
+
+ private OOXMLTikaBodyPartHandler handlePart(PackagePart packagePart,
+ XWPFStylesShim styles,
+ XWPFListManager listManager, XHTMLContentHandler xhtml,
+ OOXMLInlineBodyPartMap inlinePartMap)
throws IOException, SAXException {
Map<String, String> linkedRelationships =
loadLinkedRelationships(packagePart, true, metadata);
+ OOXMLTikaBodyPartHandler bodyHandler =
+ new OOXMLTikaBodyPartHandler(xhtml, styles, listManager, config, metadata);
+ bodyHandler.setInlineBodyPartMap(inlinePartMap, context);
try (InputStream stream = packagePart.getInputStream()) {
XMLReaderUtils.parseSAX(stream,
new EmbeddedContentHandler(new OOXMLWordAndPowerPointTextHandler(
- new OOXMLTikaBodyPartHandler(xhtml, styles, listManager, config),
+ bodyHandler,
linkedRelationships, config.isIncludeShapeBasedContent(),
- config.isConcatenatePhoneticRuns(), metadata)), context);
- } catch (TikaException | IOException e) {
+ config.isConcatenatePhoneticRuns(),
+ config.isPreferAlternateContentChoice())), context);
+ } catch (TikaException | IOException | SAXException e) {
metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
ExceptionUtils.getStackTrace(e));
}
+ bodyHandler.closeInlineElements();
+ Map<String, EmbeddedPartMetadata> partMetadata = bodyHandler.getEmbeddedPartMetadataMap();
+ resolveEmfNames(packagePart, partMetadata);
+ embeddedPartMetadataMap.putAll(partMetadata);
+ return bodyHandler;
+ }
+ private void resolveEmfNames(PackagePart documentPart,
+ Map<String, EmbeddedPartMetadata> metadataMap) {
+ for (EmbeddedPartMetadata epm : metadataMap.values()) {
+ String emfRId = epm.getEmfRelationshipId();
+ if (emfRId == null || emfRId.isEmpty()) {
+ continue;
+ }
+ try {
+ PackagePart emfPart = documentPart.getRelatedPart(
+ documentPart.getRelationship(emfRId));
+ if (emfPart == null || emfPart.getContentType() == null) {
+ continue;
+ }
+ if ("image/x-emf".equals(emfPart.getContentType())) {
+ try (TikaInputStream tis = TikaInputStream.get(emfPart.getInputStream())) {
+ EMFParser p = new EMFParser();
+ Metadata m = Metadata.newInstance(context);
+ p.parse(tis, new org.apache.tika.sax.ToTextContentHandler(), m, context);
+ epm.setFullName(m.get(EMFParser.EMF_ICON_STRING));
+ }
+ }
+ } catch (Exception e) {
+ //swallow
+ }
+ }
+ }
+
+ @Override
+ protected Map<String, EmbeddedPartMetadata> getEmbeddedPartMetadataMap() {
+ return embeddedPartMetadataMap;
+ }
+
+ private OOXMLInlineBodyPartMap collectInlineParts(PackagePart documentPart) {
+ Map<String, String> allRelationships = new java.util.HashMap<>();
+ Map<String, byte[]> footnoteMap = collectPartContent(documentPart,
+ XWPFRelation.FOOTNOTE.getRelation(), Set.of("footnote"),
+ allRelationships);
+ String endnoteRel =
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes";
+ Map<String, byte[]> endnoteMap = collectPartContent(documentPart,
+ endnoteRel, Set.of("endnote"), allRelationships);
+ String commentsRel =
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
+ Map<String, byte[]> commentMap = collectPartContent(documentPart,
+ commentsRel, Set.of("comment"),
+ allRelationships, Collections.emptySet());
+ return new OOXMLInlineBodyPartMap(footnoteMap, endnoteMap,
+ commentMap, allRelationships);
+ }
+
+ private Map<String, byte[]> collectPartContent(PackagePart documentPart,
+ String relationshipType, Set<String> wrapperElements,
+ Map<String, String> allRelationships) {
+ return collectPartContent(documentPart, relationshipType, wrapperElements,
+ allRelationships, Set.of("0", "-1"));
+ }
+
+ private Map<String, byte[]> collectPartContent(PackagePart documentPart,
+ String relationshipType, Set<String> wrapperElements,
+ Map<String, String> allRelationships, Set<String> skipIds) {
+ try {
+ PackageRelationshipCollection prc =
+ documentPart.getRelationshipsByType(relationshipType);
+ if (prc == null || prc.size() == 0) {
+ return Collections.emptyMap();
+ }
+ OOXMLPartContentCollector collector =
+ new OOXMLPartContentCollector(wrapperElements, skipIds);
+ for (int i = 0; i < prc.size(); i++) {
+ try {
+ PackagePart part = documentPart.getRelatedPart(prc.getRelationship(i));
+ // collect the part's linked relationships (for picture resolution)
+ Map<String, String> partRels =
+ loadLinkedRelationships(part, true, metadata);
+ allRelationships.putAll(partRels);
+ try (InputStream stream = part.getInputStream()) {
+ XMLReaderUtils.parseSAX(stream, collector, context);
+ }
+ } catch (InvalidFormatException | IOException | TikaException |
+ SAXException e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
+ }
+ }
+ return collector.getContentMap();
+ } catch (InvalidFormatException e) {
+ metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+ ExceptionUtils.getStackTrace(e));
+ return Collections.emptyMap();
+ }
}
@@ -326,7 +505,7 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
if (stylesRelationShip == null) {
return null;
}
- PackagePart stylesPart = packagePart.getRelatedPart(stylesRelationShip);
+ PackagePart stylesPart = safeGetRelatedPart(packagePart, stylesRelationShip);
if (stylesPart == null) {
return null;
}
@@ -337,7 +516,7 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
}
- private XWPFNumbering loadNumbering(PackagePart packagePart) {
+ private XWPFNumberingShim loadNumbering(PackagePart packagePart) {
try {
PackageRelationshipCollection numberingParts =
packagePart.getRelationshipsByType(XWPFRelation.NUMBERING.getRelation());
@@ -346,13 +525,13 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
if (numberingRelationShip == null) {
return null;
}
- PackagePart numberingPart = packagePart.getRelatedPart(numberingRelationShip);
+ PackagePart numberingPart = safeGetRelatedPart(packagePart, numberingRelationShip);
if (numberingPart == null) {
return null;
}
- return new XWPFNumberingShim(numberingPart);
+ return new XWPFNumberingShim(numberingPart, context);
}
- } catch (IOException | OpenXML4JException e) {
+ } catch (Exception e) {
//swallow
}
return null;
@@ -387,8 +566,10 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
if (prc != null) {
for (int i = 0; i < prc.size(); i++) {
PackagePart packagePart =
- documentPart.getRelatedPart(prc.getRelationship(i));
- relatedParts.add(packagePart);
+ safeGetRelatedPart(documentPart, prc.getRelationship(i));
+ if (packagePart != null) {
+ relatedParts.add(packagePart);
+ }
}
}
} catch (InvalidFormatException e) {
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/XSLFCommentAuthorHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFCommentAuthorHandler.java
new file mode 100644
index 0000000000..5b0d055472
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFCommentAuthorHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SAX handler that parses PPTX commentAuthors.xml and populates
+ * a {@link CommentAuthors} map with author names and initials by ID.
+ */
+class XSLFCommentAuthorHandler extends DefaultHandler {
+
+ private final CommentAuthors commentAuthors;
+
+ XSLFCommentAuthorHandler(CommentAuthors commentAuthors) {
+ this.commentAuthors = commentAuthors;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts)
+ throws SAXException {
+ if ("cmAuthor".equals(localName)) {
+ String id = null;
+ String name = null;
+ String initials = null;
+ for (int i = 0; i < atts.getLength(); i++) {
+ if ("id".equals(atts.getLocalName(i))) {
+ id = atts.getValue(i);
+ } else if ("name".equals(atts.getLocalName(i))) {
+ name = atts.getValue(i);
+ } else if ("initials".equals(atts.getLocalName(i))) {
+ initials = atts.getValue(i);
+ }
+ }
+ commentAuthors.add(id, name, initials);
+ }
+ }
+}
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/XSLFCommentsHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFCommentsHandler.java
new file mode 100644
index 0000000000..51a3d4c862
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFCommentsHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.sax.XHTMLContentHandler;
+
+/**
+ * SAX handler that parses PPTX comments XML and emits comment content
+ * with author attribution to an XHTML stream.
+ */
+class XSLFCommentsHandler extends DefaultHandler {
+
+ private final XHTMLContentHandler xhtml;
+ private final CommentAuthors commentAuthors;
+
+ private String commentAuthorId = null;
+ private final StringBuilder commentBuffer = new StringBuilder();
+
+ XSLFCommentsHandler(XHTMLContentHandler xhtml, CommentAuthors commentAuthors) {
+ this.xhtml = xhtml;
+ this.commentAuthors = commentAuthors;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts)
+ throws SAXException {
+ if ("cm".equals(localName)) {
+ commentAuthorId = atts.getValue("", "authorId");
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ //TODO: require that we're in <p:text>?
+ commentBuffer.append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if ("cm".equals(localName)) {
+ xhtml.startElement("p", "class", "slide-comment");
+
+ String authorString = commentAuthors.getName(commentAuthorId);
+ String authorInitials = commentAuthors.getInitials(commentAuthorId);
+ if (authorString != null || authorInitials != null) {
+ xhtml.startElement("b");
+ boolean authorExists = false;
+ if (authorString != null) {
+ xhtml.characters(authorString);
+ authorExists = true;
+ }
+ if (authorExists && authorInitials != null) {
+ xhtml.characters(" (");
+ }
+ if (authorInitials != null) {
+ xhtml.characters(authorInitials);
+ }
+ if (authorExists && authorInitials != null) {
+ xhtml.characters(")");
+ }
+ xhtml.endElement("b");
+ }
+ xhtml.characters(commentBuffer.toString());
+ xhtml.endElement("p");
+
+ commentBuffer.setLength(0);
+ commentAuthorId = null;
+ }
+ }
+}
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
new file mode 100644
index 0000000000..dbd6996886
--- /dev/null
+++ 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
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import java.util.Date;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Callback interface for receiving structured document events from the
+ * OOXML SAX dispatcher. Implementations convert these events into output
+ * formats (e.g., XHTML, Markdown, plain text).
+ */
+public interface XWPFBodyContentsHandler {
+
+ void run(RunProperties runProperties, String contents) throws SAXException;
+
+ /**
+ * @param link the link; can be null
+ */
+ void hyperlinkStart(String link) throws SAXException;
+
+ /**
+ * Called when a hyperlink is found via a field code (instrText HYPERLINK).
+ * Distinct from relationship-based hyperlinks for security tracking purposes.
+ *
+ * @param link the link URL
+ */
+ default void fieldCodeHyperlinkStart(String link) throws SAXException {
+ hyperlinkStart(link);
+ }
+
+ void hyperlinkEnd() throws SAXException;
+
+ void startParagraph(ParagraphProperties paragraphProperties) throws SAXException;
+
+ /**
+ * Updates the properties (style, numbering) for the current pending paragraph.
+ * Called when {@code </pPr>} is encountered, after {@link #startParagraph} but
+ * before any content. The body handler defers opening the XHTML {@code <p>}
+ * tag until the first content arrives, so this style info will be available.
+ */
+ default void setParagraphProperties(ParagraphProperties paragraphProperties)
+ throws SAXException {
+ // Default no-op for backward compatibility
+ }
+
+ void endParagraph() throws SAXException;
+
+ void startTable() throws SAXException;
+
+ void endTable() throws SAXException;
+
+ void startTableRow() throws SAXException;
+
+ void endTableRow() throws SAXException;
+
+ void startTableCell() throws SAXException;
+
+ void endTableCell() throws SAXException;
+
+ void startSDT() throws SAXException;
+
+ void endSDT() throws SAXException;
+
+ void startEditedSection(String editor, Date date, EditType editType) throws SAXException;
+
+ void endEditedSection() throws SAXException;
+
+ boolean isIncludeDeletedText() throws SAXException;
+
+ void footnoteReference(String id) throws SAXException;
+
+ void endnoteReference(String id) throws SAXException;
+
+ /**
+ * Called when a comment reference is encountered in the document body.
+ *
+ * @param id the comment ID
+ */
+ void commentReference(String id) throws SAXException;
+
+ boolean isIncludeMoveFromText() throws SAXException;
+
+ void embeddedOLERef(String refId, String progId, String emfImageRId) throws SAXException;
+
+ /**
+ * Called when a linked (vs embedded) OLE object is found.
+ * These reference external files and are a security concern.
+ */
+ void linkedOLERef(String refId) throws SAXException;
+
+ void embeddedPicRef(String picFileName, String picDescription) throws SAXException;
+
+ void startBookmark(String id, String name) throws SAXException;
+
+ void endBookmark(String id) throws SAXException;
+
+ /**
+ * Called when an external reference URL is found in a field code.
+ * This includes INCLUDEPICTURE, INCLUDETEXT, IMPORT, LINK fields,
+ * and DrawingML/VML hyperlinks on shapes.
+ *
+ * @param fieldType the type of field (e.g., "INCLUDEPICTURE", "hlinkHover", "vml-href")
+ * @param url the external URL
+ */
+ default void externalRef(String fieldType, String url) throws SAXException {
+ // Default no-op implementation for backward compatibility
+ }
+}
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/XWPFListManager.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFListManager.java
index 383c31f0f7..b397a20333 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFListManager.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFListManager.java
@@ -18,17 +18,10 @@ package org.apache.tika.parser.microsoft.ooxml;
import java.math.BigInteger;
-import org.apache.poi.xwpf.usermodel.XWPFAbstractNum;
-import org.apache.poi.xwpf.usermodel.XWPFNum;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNum;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumLvl;
import org.apache.tika.parser.microsoft.AbstractListManager;
+import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFNumberingShim;
public class XWPFListManager extends AbstractListManager {
@@ -38,26 +31,10 @@ public class XWPFListManager extends AbstractListManager {
* Always returns empty string.
*/
public final static XWPFListManager EMPTY_LIST = new EmptyListManager();
- private final static boolean OVERRIDE_AVAILABLE;
- private final static String SKIP_FORMAT = Character.toString((char) 61623);
-//if this shows up as the lvlText, don't show a number
-
- static {
- boolean b = false;
- try {
- Class.forName("org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumLvl");
- b = true;
- } catch (ClassNotFoundException e) {
- //swallow
- }
- b = OVERRIDE_AVAILABLE = false;
-
- }
- private final XWPFNumbering numbering;
+ private final XWPFNumberingShim numbering;
- //map of numId (which paragraph series is this a member of?), levelcounts
- public XWPFListManager(XWPFNumbering numbering) {
+ public XWPFListManager(XWPFNumberingShim numbering) {
this.numbering = numbering;
}
@@ -76,23 +53,21 @@ public class XWPFListManager extends AbstractListManager {
}
int currNumId = numId.intValue();
-
- XWPFNum xwpfNum = numbering.getNum(numId);
-
- if (xwpfNum == null) {
+ int currAbNumId = numbering.getAbstractNumId(currNumId);
+ if (currAbNumId < 0) {
return "";
}
- CTNum ctNum = xwpfNum.getCTNum();
- CTDecimalNumber abNum = ctNum.getAbstractNumId();
- int currAbNumId = abNum.getVal().intValue();
ParagraphLevelCounter lc = listLevelMap.get(currAbNumId);
LevelTuple[] overrideTuples = overrideTupleMap.get(currNumId);
if (lc == null) {
- lc = loadLevelTuples(abNum);
+ lc = loadLevelTuples(currAbNumId);
+ if (lc == null) {
+ return "";
+ }
}
if (overrideTuples == null) {
- overrideTuples = loadOverrideTuples(ctNum, lc.getNumberOfLevels());
+ overrideTuples = numbering.getOverrideLevels(currNumId, lc.getNumberOfLevels());
}
String formattedString = lc.incrementLevel(iLvl, overrideTuples);
@@ -104,85 +79,14 @@ public class XWPFListManager extends AbstractListManager {
}
- private LevelTuple[] loadOverrideTuples(CTNum ctNum, int length) {
- LevelTuple[] levelTuples = new LevelTuple[length];
- int overrideLength = ctNum.sizeOfLvlOverrideArray();
- if (overrideLength == 0) {
+ private ParagraphLevelCounter loadLevelTuples(int abstractNumId) {
+ LevelTuple[] levels = numbering.getAbstractNumLevels(abstractNumId);
+ if (levels == null) {
return null;
}
- for (int i = 0; i < length; i++) {
- LevelTuple tuple;
- if (i >= overrideLength) {
- tuple = new LevelTuple("%" + i + ".");
- } else {
- CTNumLvl ctNumLvl = ctNum.getLvlOverrideArray(i);
- if (ctNumLvl != null) {
- tuple = buildTuple(i, ctNumLvl.getLvl());
- } else {
- tuple = new LevelTuple("%" + i + ".");
- }
- }
- levelTuples[i] = tuple;
- }
- return levelTuples;
- }
-
-
- private ParagraphLevelCounter loadLevelTuples(CTDecimalNumber abNum) {
- //Unfortunately, we need to go this far into the underlying structure
- //to get the abstract num information for the edge case where
- //someone skips a level and the format is not context-free, e.g. "1.B.i".
- XWPFAbstractNum abstractNum = numbering.getAbstractNum(abNum.getVal());
- CTAbstractNum ctAbstractNum = abstractNum.getCTAbstractNum();
-
- LevelTuple[] levels = new LevelTuple[ctAbstractNum.sizeOfLvlArray()];
- for (int i = 0; i < levels.length; i++) {
- levels[i] = buildTuple(i, ctAbstractNum.getLvlArray(i));
- }
return new ParagraphLevelCounter(levels);
}
- private LevelTuple buildTuple(int level, CTLvl ctLvl) {
- boolean isLegal = false;
- int start = 1;
- int restart = -1;
- String lvlText = "%" + level + ".";
- String numFmt = "decimal";
-
-
- if (ctLvl != null && ctLvl.getIsLgl() != null) {
- isLegal = true;
- }
-
- if (ctLvl != null && ctLvl.getNumFmt() != null && ctLvl.getNumFmt().getVal() != null) {
- numFmt = ctLvl.getNumFmt().getVal().toString();
- }
- if (ctLvl != null && ctLvl.getLvlRestart() != null &&
- ctLvl.getLvlRestart().getVal() != null) {
- restart = ctLvl.getLvlRestart().getVal().intValue();
- }
- if (ctLvl != null && ctLvl.getStart() != null && ctLvl.getStart().getVal() != null) {
- start = ctLvl.getStart().getVal().intValue();
- } else {
-
- //this is a hack. Currently, this gets the lowest possible
- //start for a given numFmt. We should probably try to grab the
- //restartNumberingAfterBreak value in
- //e.g. <w:abstractNum w:abstractNumId="12" w15:restartNumberingAfterBreak="0">???
- if ("decimal".equals(numFmt) || "ordinal".equals(numFmt) ||
- "decimalZero".equals(numFmt)) {
- start = 0;
- } else {
- start = 1;
- }
- }
- if (ctLvl != null && ctLvl.getLvlText() != null && ctLvl.getLvlText().getVal() != null) {
- lvlText = ctLvl.getLvlText().getVal();
- }
- return new LevelTuple(start, restart, lvlText, numFmt, isLegal);
- }
-
-
private static class EmptyListManager extends XWPFListManager {
EmptyListManager() {
super(null);
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/XWPFWordExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFWordExtractorDecorator.java
index f40ee517e2..2dd8af7afc 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFWordExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XWPFWordExtractorDecorator.java
@@ -29,6 +29,7 @@ import com.microsoft.schemas.vml.impl.CTShapeImpl;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
@@ -81,6 +82,7 @@ import org.apache.tika.parser.microsoft.FormattingUtils;
import org.apache.tika.parser.microsoft.WordExtractor;
import org.apache.tika.parser.microsoft.WordExtractor.TagAndStyle;
import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFFeatureExtractor;
+import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFNumberingShim;
import org.apache.tika.sax.ToTextContentHandler;
import org.apache.tika.sax.XHTMLContentHandler;
import org.apache.tika.utils.StringUtils;
@@ -124,7 +126,7 @@ public class XWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
protected void buildXHTML(XHTMLContentHandler xhtml)
throws SAXException, XmlException, IOException {
XWPFHeaderFooterPolicy hfPolicy = document.getHeaderFooterPolicy();
- XWPFListManager listManager = new XWPFListManager(document.getNumbering());
+ XWPFListManager listManager = new XWPFListManager(loadNumbering());
// headers
if (hfPolicy != null && config.isIncludeHeadersAndFooters()) {
extractHeaders(xhtml, hfPolicy, listManager);
@@ -153,6 +155,26 @@ public class XWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
}
}
+ private XWPFNumberingShim loadNumbering() {
+ try {
+ PackageRelationshipCollection numberingParts =
+ document.getPackagePart().getRelationshipsByType(
+ XWPFRelation.NUMBERING.getRelation());
+ if (numberingParts.size() > 0) {
+ PackageRelationship rel = numberingParts.getRelationship(0);
+ if (rel != null) {
+ PackagePart numberingPart = document.getPackagePart().getRelatedPart(rel);
+ if (numberingPart != null) {
+ return new XWPFNumberingShim(numberingPart, getParseContext());
+ }
+ }
+ }
+ } catch (Exception e) {
+ //swallow
+ }
+ return XWPFNumberingShim.EMPTY;
+ }
+
private void extractFeatures(XWPFDocument document, Metadata metadata) {
XWPFFeatureExtractor ex = new XWPFFeatureExtractor();
ex.process(document, metadata, getParseContext());
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/xslf/XSLFEventBasedPowerPointExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xslf/XSLFEventBasedPowerPointExtractor.java
index 2950e46be3..193f649a4e 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xslf/XSLFEventBasedPowerPointExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xslf/XSLFEventBasedPowerPointExtractor.java
@@ -27,9 +27,10 @@ import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.xmlbeans.XmlException;
-import org.apache.tika.parser.microsoft.ooxml.OOXMLWordAndPowerPointTextHandler;
+import org.apache.tika.parser.microsoft.ooxml.EditType;
import org.apache.tika.parser.microsoft.ooxml.ParagraphProperties;
import org.apache.tika.parser.microsoft.ooxml.RunProperties;
+import org.apache.tika.parser.microsoft.ooxml.XWPFBodyContentsHandler;
public class XSLFEventBasedPowerPointExtractor implements POIXMLTextExtractor {
@@ -92,7 +93,7 @@ public class XSLFEventBasedPowerPointExtractor implements POIXMLTextExtractor {
}
private static class XSLFToTextContentHandler
- implements OOXMLWordAndPowerPointTextHandler.XWPFBodyContentsHandler {
+ implements XWPFBodyContentsHandler {
private final StringBuilder buffer;
public XSLFToTextContentHandler(StringBuilder buffer) {
@@ -166,7 +167,7 @@ public class XSLFEventBasedPowerPointExtractor implements POIXMLTextExtractor {
@Override
public void startEditedSection(String editor, Date date,
- OOXMLWordAndPowerPointTextHandler.EditType editType) {
+ EditType editType) {
}
@@ -190,6 +191,11 @@ public class XSLFEventBasedPowerPointExtractor implements POIXMLTextExtractor {
}
+ @Override
+ public void commentReference(String id) {
+
+ }
+
@Override
public boolean isIncludeMoveFromText() {
return false;
@@ -197,7 +203,7 @@ public class XSLFEventBasedPowerPointExtractor implements POIXMLTextExtractor {
@Override
- public void embeddedOLERef(String refId) {
+ public void embeddedOLERef(String refId, String progId, String emfImageRId) {
//no-op
}
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/xwpf/XWPFEventBasedWordExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFEventBasedWordExtractor.java
index 8056f26bfb..2bb53a3c69 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFEventBasedWordExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFEventBasedWordExtractor.java
@@ -33,7 +33,6 @@ import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.xmlbeans.XmlException;
import org.slf4j.Logger;
@@ -44,9 +43,11 @@ import org.apache.tika.exception.RuntimeSAXException;
import org.apache.tika.exception.TikaException;
import org.apache.tika.exception.WriteLimitReachedException;
import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.ooxml.EditType;
import org.apache.tika.parser.microsoft.ooxml.OOXMLWordAndPowerPointTextHandler;
import org.apache.tika.parser.microsoft.ooxml.ParagraphProperties;
import org.apache.tika.parser.microsoft.ooxml.RunProperties;
+import org.apache.tika.parser.microsoft.ooxml.XWPFBodyContentsHandler;
import org.apache.tika.parser.microsoft.ooxml.XWPFListManager;
import org.apache.tika.utils.XMLReaderUtils;
@@ -65,7 +66,19 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
public XWPFEventBasedWordExtractor(OPCPackage container)
throws XmlException, OpenXML4JException, IOException {
this.container = container;
- this.properties = new POIXMLProperties(container);
+ // Properties are lazily initialized to avoid requiring ooxml-lite
+ // when SAXBasedMetadataExtractor is used instead
+ }
+
+ private POIXMLProperties getOrCreateProperties() {
+ if (properties == null) {
+ try {
+ properties = new POIXMLProperties(container);
+ } catch (Exception e) {
+ LOG.warn("Couldn't load properties", e);
+ }
+ }
+ return properties;
}
public OPCPackage getPackage() {
@@ -73,15 +86,18 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
}
public POIXMLProperties.CoreProperties getCoreProperties() {
- return this.properties.getCoreProperties();
+ POIXMLProperties props = getOrCreateProperties();
+ return props != null ? props.getCoreProperties() : null;
}
public POIXMLProperties.ExtendedProperties getExtendedProperties() {
- return this.properties.getExtendedProperties();
+ POIXMLProperties props = getOrCreateProperties();
+ return props != null ? props.getExtendedProperties() : null;
}
public POIXMLProperties.CustomProperties getCustomProperties() {
- return this.properties.getCustomProperties();
+ POIXMLProperties props = getOrCreateProperties();
+ return props != null ? props.getCustomProperties() : null;
}
@Override
@@ -158,8 +174,9 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
private void handleDocumentPart(PackagePart documentPart, StringBuilder sb)
throws IOException, SAXException, TikaException {
//load the numbering/list manager and styles from the main document part
- XWPFNumbering numbering = loadNumbering(documentPart);
- XWPFListManager xwpfListManager = new XWPFListManager(numbering);
+ XWPFNumberingShim numbering = loadNumbering(documentPart);
+ XWPFListManager xwpfListManager = new XWPFListManager(
+ numbering != null ? numbering : XWPFNumberingShim.EMPTY);
//TODO: XWPFStyles styles = loadStyles(documentPart);
//headers
@@ -232,7 +249,7 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
return hyperlinks;
}
- private XWPFNumbering loadNumbering(PackagePart packagePart) throws IOException {
+ private XWPFNumberingShim loadNumbering(PackagePart packagePart) {
try {
PackageRelationshipCollection numberingParts =
packagePart.getRelationshipsByType(XWPFRelation.NUMBERING.getRelation());
@@ -245,16 +262,16 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
if (numberingPart == null) {
return null;
}
- return new XWPFNumbering(numberingPart);
+ return new XWPFNumberingShim(numberingPart, new ParseContext());
}
- } catch (OpenXML4JException e) {
+ } catch (Exception e) {
LOG.warn("Couldn't load numbering", e);
}
return null;
}
private static class XWPFToTextContentHandler
- implements OOXMLWordAndPowerPointTextHandler.XWPFBodyContentsHandler {
+ implements XWPFBodyContentsHandler {
private final StringBuilder buffer;
public XWPFToTextContentHandler(StringBuilder buffer) {
@@ -328,7 +345,7 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
@Override
public void startEditedSection(String editor, Date date,
- OOXMLWordAndPowerPointTextHandler.EditType editType) {
+ EditType editType) {
}
@@ -352,13 +369,18 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
}
+ @Override
+ public void commentReference(String id) {
+
+ }
+
@Override
public boolean isIncludeMoveFromText() {
return false;
}
@Override
- public void embeddedOLERef(String refId) {
+ public void embeddedOLERef(String refId, String progId, String emfImageRId) {
//no-op
}
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/xwpf/XWPFFeatureExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFFeatureExtractor.java
index 06e45afb72..3cb1935cc6 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFFeatureExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFFeatureExtractor.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
+import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
@@ -42,8 +43,11 @@ import org.apache.tika.utils.XMLReaderUtils;
public class XWPFFeatureExtractor {
public void process(XWPFDocument xwpfDocument, Metadata metadata, ParseContext parseContext) {
- try (InputStream is = xwpfDocument.getPackagePart()
- .getInputStream()) {
+ process(xwpfDocument.getPackagePart(), metadata, parseContext);
+ }
+
+ public void process(PackagePart packagePart, Metadata metadata, ParseContext parseContext) {
+ try (InputStream is = packagePart.getInputStream()) {
FeatureHandler featureHandler = new FeatureHandler();
XMLReaderUtils.parseSAX(is, featureHandler, parseContext);
if (featureHandler.hasComments) {
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/xwpf/XWPFNumberingShim.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
index f563223964..7d8d15fc7f 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
@@ -17,20 +17,274 @@
package org.apache.tika.parser.microsoft.ooxml.xwpf;
import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.AbstractListManager.LevelTuple;
+import org.apache.tika.parser.microsoft.ooxml.OOXMLWordAndPowerPointTextHandler;
+import org.apache.tika.utils.XMLReaderUtils;
/**
- * Stub class of POI's XWPFNumbering because onDocumentRead() is protected
+ * SAX-based parser for numbering.xml that replaces the XMLBeans-dependent
+ * POI XWPFNumbering. This eliminates the need for ooxml-lite schema classes
+ * in the SAX parsing chain.
+ * <p>
+ * Follows the same pattern as {@link XWPFStylesShim}.
*/
-public class XWPFNumberingShim extends XWPFNumbering {
+public class XWPFNumberingShim {
+
+ public static final XWPFNumberingShim EMPTY = new EmptyNumberingShim();
- public XWPFNumberingShim(PackagePart part) throws IOException, OpenXML4JException {
- super(part);
- onDocumentRead();
+ // abstractNumId -> list of LevelTuples (indexed by ilvl)
+ private final Map<Integer, LevelTuple[]> abstractNumLevels = new HashMap<>();
+ // numId -> abstractNumId
+ private final Map<Integer, Integer> numToAbstractNum = new HashMap<>();
+ // numId -> override LevelTuples (indexed by ilvl), null entries for non-overridden levels
+ private final Map<Integer, Map<Integer, LevelTuple>> overrideLevels = new HashMap<>();
+
+ private XWPFNumberingShim() {
}
+ public XWPFNumberingShim(PackagePart part, ParseContext parseContext)
+ throws IOException, TikaException, SAXException {
+ try (InputStream is = part.getInputStream()) {
+ XMLReaderUtils.parseSAX(is, new NumberingHandler(), parseContext);
+ }
+ }
+ /**
+ * @return the abstractNumId for the given numId, or -1 if not found
+ */
+ public int getAbstractNumId(int numId) {
+ Integer id = numToAbstractNum.get(numId);
+ return id != null ? id : -1;
+ }
+
+ /**
+ * @return the level tuples for the given abstractNumId, or null if not found
+ */
+ public LevelTuple[] getAbstractNumLevels(int abstractNumId) {
+ return abstractNumLevels.get(abstractNumId);
+ }
+
+ /**
+ * Build override level tuples array for a given numId with the specified length.
+ * Returns null if there are no overrides for this numId.
+ */
+ public LevelTuple[] getOverrideLevels(int numId, int length) {
+ Map<Integer, LevelTuple> overrides = overrideLevels.get(numId);
+ if (overrides == null || overrides.isEmpty()) {
+ return null;
+ }
+ LevelTuple[] result = new LevelTuple[length];
+ for (int i = 0; i < length; i++) {
+ LevelTuple override = overrides.get(i);
+ if (override != null) {
+ result[i] = override;
+ } else {
+ result[i] = new LevelTuple("%" + i + ".");
+ }
+ }
+ return result;
+ }
+
+ private static class EmptyNumberingShim extends XWPFNumberingShim {
+ @Override
+ public int getAbstractNumId(int numId) {
+ return -1;
+ }
+
+ @Override
+ public LevelTuple[] getAbstractNumLevels(int abstractNumId) {
+ return null;
+ }
+
+ @Override
+ public LevelTuple[] getOverrideLevels(int numId, int length) {
+ return null;
+ }
+ }
+
+ private class NumberingHandler extends DefaultHandler {
+
+ private static final String W_NS = OOXMLWordAndPowerPointTextHandler.W_NS;
+
+ // Current context
+ private boolean inAbstractNum = false;
+ private int currentAbstractNumId = -1;
+ private boolean inNum = false;
+ private int currentNumId = -1;
+ private boolean inLvl = false;
+ private boolean inLvlOverride = false;
+ private int currentIlvl = -1;
+
+ // Level accumulators (reset for each lvl element)
+ private int lvlStart = -1;
+ private int lvlRestart = -1;
+ private String lvlText = null;
+ private String lvlNumFmt = null;
+ private boolean lvlIsLegal = false;
+
+ // Collecting levels for current abstractNum
+ private final Map<Integer, LevelTuple> currentAbstractLevels = new HashMap<>();
+ // Collecting overrides for current num
+ private final Map<Integer, LevelTuple> currentOverrides = new HashMap<>();
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes atts)
+ throws SAXException {
+ if (!W_NS.equals(uri)) {
+ return;
+ }
+
+ switch (localName) {
+ case "abstractNum":
+ inAbstractNum = true;
+ currentAbstractNumId = getIntAttr(atts, W_NS, "abstractNumId", -1);
+ currentAbstractLevels.clear();
+ break;
+ case "num":
+ inNum = true;
+ currentNumId = getIntAttr(atts, W_NS, "numId", -1);
+ currentOverrides.clear();
+ break;
+ case "lvlOverride":
+ if (inNum) {
+ inLvlOverride = true;
+ currentIlvl = getIntAttr(atts, W_NS, "ilvl", -1);
+ }
+ break;
+ case "lvl":
+ inLvl = true;
+ currentIlvl = getIntAttr(atts, W_NS, "ilvl", -1);
+ // Reset accumulators
+ lvlStart = -1;
+ lvlRestart = -1;
+ lvlText = null;
+ lvlNumFmt = null;
+ lvlIsLegal = false;
+ break;
+ case "start":
+ if (inLvl) {
+ lvlStart = getIntAttr(atts, W_NS, "val", -1);
+ }
+ break;
+ case "numFmt":
+ if (inLvl) {
+ lvlNumFmt = atts.getValue(W_NS, "val");
+ }
+ break;
+ case "lvlText":
+ if (inLvl) {
+ lvlText = atts.getValue(W_NS, "val");
+ }
+ break;
+ case "lvlRestart":
+ if (inLvl) {
+ lvlRestart = getIntAttr(atts, W_NS, "val", -1);
+ }
+ break;
+ case "isLgl":
+ if (inLvl) {
+ lvlIsLegal = true;
+ }
+ break;
+ case "abstractNumId":
+ if (inNum && !inLvl) {
+ int absId = getIntAttr(atts, W_NS, "val", -1);
+ if (currentNumId >= 0 && absId >= 0) {
+ numToAbstractNum.put(currentNumId, absId);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (!W_NS.equals(uri)) {
+ return;
+ }
+ switch (localName) {
+ case "lvl":
+ if (inLvl && currentIlvl >= 0) {
+ LevelTuple tuple = buildLevelTuple(currentIlvl);
+ if (inLvlOverride && inNum) {
+ currentOverrides.put(currentIlvl, tuple);
+ } else if (inAbstractNum) {
+ currentAbstractLevels.put(currentIlvl, tuple);
+ }
+ }
+ inLvl = false;
+ break;
+ case "lvlOverride":
+ inLvlOverride = false;
+ break;
+ case "abstractNum":
+ if (inAbstractNum && currentAbstractNumId >= 0 &&
+ !currentAbstractLevels.isEmpty()) {
+ int maxLevel = currentAbstractLevels.keySet().stream()
+ .mapToInt(Integer::intValue).max().orElse(-1);
+ LevelTuple[] levels = new LevelTuple[maxLevel + 1];
+ for (int i = 0; i <= maxLevel; i++) {
+ LevelTuple t = currentAbstractLevels.get(i);
+ levels[i] = t != null ? t : new LevelTuple("%" + i + ".");
+ }
+ abstractNumLevels.put(currentAbstractNumId, levels);
+ }
+ inAbstractNum = false;
+ currentAbstractNumId = -1;
+ break;
+ case "num":
+ if (inNum && currentNumId >= 0 && !currentOverrides.isEmpty()) {
+ overrideLevels.put(currentNumId, new HashMap<>(currentOverrides));
+ }
+ inNum = false;
+ currentNumId = -1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ private LevelTuple buildLevelTuple(int level) {
+ int start = lvlStart;
+ int restart = lvlRestart;
+ String text = lvlText != null ? lvlText : "%" + level + ".";
+ String numFmt = lvlNumFmt != null ? lvlNumFmt : "decimal";
+
+ if (start < 0) {
+ // Same hack as XWPFListManager.buildTuple
+ if ("decimal".equals(numFmt) || "ordinal".equals(numFmt) ||
+ "decimalZero".equals(numFmt)) {
+ start = 0;
+ } else {
+ start = 1;
+ }
+ }
+ return new LevelTuple(start, restart, text, numFmt, lvlIsLegal);
+ }
+
+ private int getIntAttr(Attributes atts, String ns, String localName, int defaultVal) {
+ String val = atts.getValue(ns, localName);
+ if (val == null) {
+ return defaultVal;
+ }
+ try {
+ return Integer.parseInt(val);
+ } catch (NumberFormatException e) {
+ return defaultVal;
+ }
+ }
+ }
}
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/SXWPFExtractorTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxTest.java
similarity index 50%
rename from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
rename to tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxTest.java
index 1ec8be3242..7407e169c6 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxTest.java
@@ -19,167 +19,35 @@ package org.apache.tika.parser.microsoft.ooxml;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.apache.tika.TikaTest;
-import org.apache.tika.config.loader.TikaLoader;
-import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.metadata.DublinCore;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
-import org.apache.tika.metadata.OfficeOpenXMLCore;
import org.apache.tika.metadata.OfficeOpenXMLExtended;
import org.apache.tika.metadata.TikaCoreProperties;
-import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.PasswordProvider;
import org.apache.tika.parser.microsoft.OfficeParserConfig;
+public abstract class AbstractOOXMLDocxTest extends TikaTest {
-public class SXWPFExtractorTest extends TikaTest {
-
- private ParseContext parseContext;
-
- @BeforeEach
- public void setUp() {
- parseContext = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setUseSAXDocxExtractor(true);
- officeParserConfig.setUseSAXPptxExtractor(true);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
-
- }
-
- @Test
- public void basicTest() throws Exception {
-
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_2006ml.docx", parseContext);
-
- assertEquals(8, metadataList.size());
- Metadata m = metadataList.get(0);
-
- assertEquals("2016-11-29T00:58:00Z", m.get(TikaCoreProperties.CREATED));
- assertEquals("2016-11-29T17:54:00Z", m.get(TikaCoreProperties.MODIFIED));
- assertEquals("My Document Title", m.get(TikaCoreProperties.TITLE));
- assertEquals("This is the Author", m.get(TikaCoreProperties.CREATOR));
- assertEquals("3", m.get(OfficeOpenXMLCore.REVISION));
- assertEquals("Allison, Timothy B.", m.get(TikaCoreProperties.MODIFIER));
- //assertEquals("0", m.get(OfficeOpenXMLExtended.DOC_SECURITY));
- assertEquals("260", m.get(Office.WORD_COUNT));
- assertEquals("3", m.get(Office.PARAGRAPH_COUNT));
- assertEquals("1742", m.get(Office.CHARACTER_COUNT_WITH_SPACES));
- assertEquals("12", m.get(Office.LINE_COUNT));
- assertEquals("16.0000", m.get(OfficeOpenXMLExtended.APP_VERSION));
-
-
- String content = m.get(TikaCoreProperties.TIKA_CONTENT);
-
- assertContainsCount("engaging title page", content, 1);
- //need \n to differentiate from metadata values
- assertContainsCount("This is the Author\n", content, 1);
- assertContainsCount("This is an engaging title page", content, 1);
-
- assertContains("My Document Title", content);
- assertContains("My Document Subtitle", content);
-
- assertContains("<p class=\"toc_1\">\t<a href=\"#_Toc467647605\">Heading1\t3</a></p>",
- content);
-
-
- assertContains("2. Really basic 2.", content);
-
- assertContainsCount("This is a text box", content, 1);
-
- assertContains("<p>This is a hyperlink: <a href=\"http://tika.apache.org\">tika</a></p>",
- content);
-
- assertContains(
- "<p>This is a link to a local file: <a href=\"file:///C:/data/test.png\">test.png</a></p>",
- content);
-
- assertContains("<p>This is 10 spaces</p>", content);
-
- //caption
- assertContains(
- "<p class=\"table_of_figures\">\t<a href=\"#_Toc467647797\">Table 1: Table1 Caption\t2</a></p>",
- content);
-
- //embedded table
- //TODO: figure out how to handle embedded tables in html
- assertContains("<td>Embedded table r1c1", content);
-
- //shape
- assertContainsCount("<p>This is text within a shape", content, 1);
-
- //sdt rich text
- assertContains("<p>Rich text content control", content);
-
- //sdt simple text
- assertContains("<p>Simple text content control", content);
-
- //sdt repeating
- assertContains("Repeating content", content);
-
- //sdt dropdown
- //TODO: get options for dropdown
- assertContains("Drop down1", content);
-
- //sdt date
- assertContains("<p>11/16/2016</p>", content);
-
- //test that <tab/> works
- assertContains("tab\ttab", content);
-
- assertContainsCount("serious word art", content, 1);
- assertContainsCount("Wordartr1c1", content, 1);
-
- //glossary document contents
- assertContains("Click or tap to enter a date", content);
-
- //basic b/i tags...make sure not to overlap!
- assertContains("<p>The <i>quick</i> brown <b>fox </b>j<i>um</i><b><i>ped</i></b> over",
- content);
-
- assertContains("This is a comment", content);
-
- assertContains("This is an endnote", content);
-
- assertContains("this is the footnote", content);
-
- assertContains("First page header", content);
-
- assertContains("Even page header", content);
-
- assertContains("Odd page header", content);
-
- assertContains("First page footer", content);
-
- assertContains("Even page footer", content);
-
- assertContains("Odd page footer", content);
-
- //test default does not include deleted
- assertNotContained("frog", content);
-
- assertContains("Mattmann", content);
-
- //TODO: extract chart text
-// assertContains("This is the chart title", content);
-
- //TODO: add chart parsing
-// assertContains("This is the chart", content);
+ abstract ParseContext getParseContext();
+ protected OfficeParserConfig getOrCreateOfficeParserConfig(ParseContext parseContext) {
+ OfficeParserConfig config = parseContext.get(OfficeParserConfig.class);
+ if (config == null) {
+ config = new OfficeParserConfig();
+ parseContext.set(OfficeParserConfig.class, config);
+ }
+ return config;
}
/**
@@ -190,7 +58,7 @@ public class SXWPFExtractorTest extends TikaTest {
@Test
public void testWord() throws Exception {
- XMLResult xmlResult = getXML("testWORD.docx", parseContext);
+ XMLResult xmlResult = getXML("testWORD.docx", getParseContext());
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
xmlResult.metadata.get(Metadata.CONTENT_TYPE));
assertEquals("Sample Word Document", xmlResult.metadata.get(TikaCoreProperties.TITLE));
@@ -206,16 +74,22 @@ public class SXWPFExtractorTest extends TikaTest {
*/
@Test
public void testWordFootnote() throws Exception {
- XMLResult xmlResult = getXML("footnotes.docx", parseContext);
+ XMLResult xmlResult = getXML("footnotes.docx", getParseContext());
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
xmlResult.metadata.get(Metadata.CONTENT_TYPE));
assertTrue(xmlResult.xml.contains("snoska"));
+ //TIKA-4657 -- footnote content should be in a div with class "footnote"
+ // and should not be nested inside the paragraph
+ assertContains("<div class=\"footnote\">", xmlResult.xml);
+ assertNotContained("<p><div class=\"footnote\">", xmlResult.xml);
}
@Test
public void testEndnoteWithTable() throws Exception {
- XMLResult xmlResult = getXML("testWORD_endnote_table.docx", parseContext);
+ XMLResult xmlResult = getXML("testWORD_endnote_table.docx", getParseContext());
assertContains("Cat Property Act", xmlResult.xml);
+ //TIKA-4657 -- endnote content should be in a div with class "endnote"
+ assertContains("<div class=\"endnote\">", xmlResult.xml);
}
/**
@@ -224,7 +98,7 @@ public class SXWPFExtractorTest extends TikaTest {
*/
@Test
public void testWordHTML() throws Exception {
- XMLResult result = getXML("testWORD.docx", parseContext);
+ XMLResult result = getXML("testWORD.docx", getParseContext());
String xml = result.xml;
Metadata metadata = result.metadata;
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -239,9 +113,9 @@ public class SXWPFExtractorTest extends TikaTest {
assertContains("<h1>Heading Level 1</h1>", xml);
assertTrue(xml.contains("<h2>Heading Level 2</h2>"));
// Headings with anchor tags in them
- //TODO: still not getting bookmarks
- assertTrue(xml.contains("<h3>Heading Level 3<a name=\"OnLevel3\" /></h3>"));
-// assertTrue(xml.contains("<h3>Heading Level 3</h3>"));
+ // Bookmark position relative to text differs between DOM and SAX
+ assertContains("Heading Level 3", xml);
+ assertContains("<a name=\"OnLevel3\" />", xml);
// Bold and italic
assertTrue(xml.contains("<b>BOLD</b>"));
assertTrue(xml.contains("<i>ITALIC</i>"));
@@ -255,7 +129,7 @@ public class SXWPFExtractorTest extends TikaTest {
// Paragraphs with other styles
assertTrue(xml.contains("<p class=\"signature\">This one"));
- result = getXML("testWORD_3imgs.docx", parseContext);
+ result = getXML("testWORD_3imgs.docx", getParseContext());
xml = result.xml;
// Images 2-4 (there is no 1!)
@@ -270,33 +144,12 @@ public class SXWPFExtractorTest extends TikaTest {
assertTrue(xml.contains("<p>The end!</p>"));
}
- @Test
- public void testContiguousHTMLFormatting() throws Exception {
- // TIKA-692: test document containing multiple
- // character runs within a bold tag:
- String xml = getXML("testWORD_bold_character_runs.docx", parseContext).xml;
-
- // Make sure bold text arrived as single
- // contiguous string even though Word parser
- // handled this as 3 character runs
- assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"), "Bold text wasn't contiguous: " + xml);
-
- // TIKA-692: test document containing multiple
- // character runs within a bold tag:
- xml = getXML("testWORD_bold_character_runs2.docx", parseContext).xml;
-
- // Make sure bold text arrived as single
- // contiguous string even though Word parser
- // handled this as 3 character runs
- assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"), "Bold text wasn't contiguous: " + xml);
- }
-
/**
* Test that we can extract image from docx header
*/
@Test
public void testWordPicturesInHeader() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("headerPic.docx", parseContext);
+ List<Metadata> metadataList = getRecursiveMetadata("headerPic.docx", getParseContext());
assertEquals(2, metadataList.size());
Metadata m = metadataList.get(0);
String mainContent = m.get(TikaCoreProperties.TIKA_CONTENT);
@@ -306,108 +159,89 @@ public class SXWPFExtractorTest extends TikaTest {
assertTrue(mainContent.contains("<img"));
}
- @Test
- public void testPicturesInVariousPlaces() throws Exception {
- //test that images are actually extracted from
- //headers, footers, comments, endnotes, footnotes
- List<Metadata> metadataList =
- getRecursiveMetadata("testWORD_embedded_pics.docx", parseContext);
-
- //only process embedded resources once
- assertEquals(3, metadataList.size());
- String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
- for (int i = 1; i < 4; i++) {
- assertContains("header" + i + "_pic", content);
- assertContains("footer" + i + "_pic", content);
- }
- assertContains("body_pic.jpg", content);
- assertContains("sdt_pic.jpg", content);
- assertContains("deeply_embedded_pic", content);
- assertContains("deleted_pic", content);//TODO: don't extract this
- assertContains("footnotes_pic", content);
- assertContains("comments_pic", content);
- assertContains("endnotes_pic", content);
-// assertContains("sdt2_pic.jpg", content);//name of file is not stored in image-sdt
-
- assertContainsCount("<img src=", content, 14);
- }
-
/**
* Test docx without headers
* TIKA-633
*/
@Test
public void testNullHeaders() throws Exception {
- XMLResult xmlResult = getXML("NullHeader.docx", parseContext);
+ XMLResult xmlResult = getXML("NullHeader.docx", getParseContext());
assertEquals(false, xmlResult.xml.isEmpty(), "Should have found some text");
}
+ /**
+ * Test for missing text described in
+ * <a href="https://issues.apache.org/jira/browse/TIKA-1130">TIKA-1130</a>.
+ * and TIKA-1317
+ */
@Test
- public void testVarious() throws Exception {
- Metadata metadata = new Metadata();
- String content = getText("testWORD_various.docx", metadata, parseContext);
- //content = content.replaceAll("\\s+"," ");
- assertContains("Footnote appears here", content);
- assertContains("This is a footnote.", content);
- assertContains("This is the header text.", content);
- assertContains("This is the footer text.", content);
- assertContains("Here is a text box", content);
- assertContains("Bold", content);
- assertContains("italic", content);
- assertContains("underline", content);
- assertContains("superscript", content);
- assertContains("subscript", content);
- assertContains("Here is a citation:", content);
- assertContains("Figure 1 This is a caption for Figure 1", content);
- assertContains("(Kramer)", content);
- assertContains("Row 1 Col 1 Row 1 Col 2 Row 1 Col 3 Row 2 Col 1 Row 2 Col 2 Row 2 Col 3",
- content.replaceAll("\\s+", " "));
- assertContains("Row 1 column 1 Row 2 column 1 Row 1 column 2 Row 2 column 2",
- content.replaceAll("\\s+", " "));
- assertContains("This is a hyperlink", content);
- assertContains("Here is a list:", content);
- for (int row = 1; row <= 3; row++) {
- //assertContains("·\tBullet " + row, content);
- //assertContains("\u00b7\tBullet " + row, content);
- assertContains("Bullet " + row, content);
- }
- assertContains("Here is a numbered list:", content);
- for (int row = 1; row <= 3; row++) {
- //assertContains(row + ")\tNumber bullet " + row, content);
- //assertContains(row + ") Number bullet " + row, content);
- // TODO: OOXMLExtractor fails to number the bullets:
- assertContains("Number bullet " + row, content);
- }
+ public void testMissingText() throws Exception {
- for (int row = 1; row <= 2; row++) {
- for (int col = 1; col <= 3; col++) {
- assertContains("Row " + row + " Col " + col, content);
- }
- }
+ XMLResult xmlResult = getXML("testWORD_missing_text.docx", getParseContext());
+ assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ xmlResult.metadata.get(Metadata.CONTENT_TYPE));
+ assertContains("BigCompany", xmlResult.xml);
+ assertContains("Seasoned", xmlResult.xml);
+ assertContains("Rich_text_in_cell", xmlResult.xml);
- assertContains("Keyword1 Keyword2", content);
- assertEquals("Keyword1 Keyword2", metadata.get(Office.KEYWORDS));
+ }
- assertContains("Subject is here", content);
- assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
+ @Test
+ public void testBoldHyperlink() throws Exception {
+ //TIKA-1255
+ String xml = getXML("testWORD_boldHyperlink.docx", getParseContext()).xml;
+ xml = xml.replaceAll("\\s+", " ");
+ assertContains("<a href=\"http://tika.apache.org/\">hyper <b>link</b></a>", xml);
+ assertContains("<a href=\"http://tika.apache.org/\"><b>hyper</b> link</a>; bold", xml);
+ }
- assertContains("Suddenly some Japanese text:", content);
- // Special version of (GHQ)
- assertContains("\uff08\uff27\uff28\uff31\uff09", content);
- // 6 other characters
- assertContains("\u30be\u30eb\u30b2\u3068\u5c3e\u5d0e\u3001\u6de1\u3005\u3068\u6700\u671f",
- content);
+ @Test
+ public void testLongForIntExceptionInSummaryDetails() throws Exception {
+ //TIKA-2055
+ assertContains("bold",
+ getXML("testWORD_totalTimeOutOfRange.docx", getParseContext()).xml);
+ }
- assertContains("And then some Gothic text:", content);
- assertContains("\uD800\uDF32\uD800\uDF3f\uD800\uDF44\uD800\uDF39\uD800\uDF43\uD800\uDF3A",
- content);
+ @Test
+ public void testMultiAuthorsManagers() throws Exception {
+ XMLResult r = getXML("testWORD_multi_authors.docx", getParseContext());
+ String[] authors = r.metadata.getValues(TikaCoreProperties.CREATOR);
+ assertEquals(3, authors.length);
+ assertEquals("author2", authors[1]);
+
+ String[] managers = r.metadata.getValues(OfficeOpenXMLExtended.MANAGER);
+ assertEquals(2, managers.length);
+ assertEquals("manager1", managers[0]);
+ assertEquals("manager2", managers[1]);
+ }
+
+ @Test
+ public void testOrigSourcePath() throws Exception {
+ Metadata embed1_zip_metadata =
+ getRecursiveMetadata("test_recursive_embedded.docx", getParseContext()).get(2);
+ assertContains("C:\\Users\\tallison\\AppData\\Local\\Temp\\embed1.zip", Arrays.asList(
+ embed1_zip_metadata.getValues(TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
+ assertContains("C:\\Users\\tallison\\Desktop\\tmp\\New folder (2)\\embed1.zip",
+ Arrays.asList(
+ embed1_zip_metadata.getValues(
+ TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
+ }
+
+ @Test
+ public void testDOCXThumbnail() throws Exception {
+ String xml = getXML("testDOCX_Thumbnail.docx", getParseContext()).xml;
+ int a = xml.indexOf("This file contains a thumbnail");
+ int b = xml.indexOf("<div class=\"embedded\" id=\"/docProps/thumbnail.emf\" />");
+ assertTrue(a != -1);
+ assertTrue(b != -1);
+ assertTrue(a < b);
}
@Test
public void testWordCustomProperties() throws Exception {
ParseContext context = new ParseContext();
context.set(Locale.class, Locale.US);
- Metadata metadata = getXML("testWORD_custom_props.docx", parseContext).metadata;
+ Metadata metadata = getXML("testWORD_custom_props.docx", getParseContext()).metadata;
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
metadata.get(Metadata.CONTENT_TYPE));
@@ -420,7 +254,8 @@ public class SXWPFExtractorTest extends TikaTest {
assertEquals("2", metadata.get(Office.WORD_COUNT));
assertEquals("My Title", metadata.get(TikaCoreProperties.TITLE));
assertEquals("My Keyword", metadata.get(Office.KEYWORDS));
- assertContains("My Keyword", Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
+ assertContains("My Keyword",
+ Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
assertEquals("Normal.dotm", metadata.get(OfficeOpenXMLExtended.TEMPLATE));
assertEquals("My subject", metadata.get(DublinCore.SUBJECT));
assertEquals("EDF-DIT", metadata.get(TikaCoreProperties.PUBLISHER));
@@ -434,7 +269,7 @@ public class SXWPFExtractorTest extends TikaTest {
// TIKA-989:
@Test
public void testEmbeddedPDF() throws Exception {
- String xml = getXML("testWORD_embedded_pdf.docx", parseContext).xml;
+ String xml = getXML("testWORD_embedded_pdf.docx", getParseContext()).xml;
int i = xml.indexOf("Here is the pdf file:");
int j = xml.indexOf("<div class=\"embedded\" id=\"rId5\" />");
int k = xml.indexOf("Bye Bye");
@@ -454,7 +289,7 @@ public class SXWPFExtractorTest extends TikaTest {
// TIKA-1006
@Test
public void testWordNullStyle() throws Exception {
- String xml = getXML("testWORD_null_style.docx").xml;
+ String xml = getXML("testWORD_null_style.docx", getParseContext()).xml;
assertContains("Test av styrt dokument", xml);
}
@@ -465,160 +300,30 @@ public class SXWPFExtractorTest extends TikaTest {
@Test
public void testNoFormat() throws Exception {
assertContains("This is a piece of text that causes an exception",
- getXML("testWORD_no_format.docx", parseContext).xml);
- }
-
- @Test
- public void testSkipDeleted() throws Exception {
- ParseContext pc = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setIncludeDeletedContent(true);
- officeParserConfig.setUseSAXDocxExtractor(true);
- officeParserConfig.setIncludeMoveFromContent(true);
- pc.set(OfficeParserConfig.class, officeParserConfig);
-
- XMLResult r = getXML("testWORD_2006ml.docx", pc);
- assertContains("frog", r.xml);
- assertContainsCount("Second paragraph", r.xml, 2);
-
+ getXML("testWORD_no_format.docx", getParseContext()).xml);
}
// TIKA-1005:
@Test
public void testTextInsideTextBox() throws Exception {
- String xml = getXML("testWORD_text_box.docx", parseContext).xml;
+ String xml = getXML("testWORD_text_box.docx", getParseContext()).xml;
assertContains("This text is directly in the body of the document.", xml);
assertContains("This text is inside of a text box in the body of the document.", xml);
assertContains("This text is inside of a text box in the header of the document.", xml);
assertContains("This text is inside of a text box in the footer of the document.", xml);
}
- //TIKA-2346
- @Test
- public void testTurningOffTextBoxExtraction() throws Exception {
- ParseContext pc = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setIncludeShapeBasedContent(false);
- officeParserConfig.setUseSAXDocxExtractor(true);
- pc.set(OfficeParserConfig.class, officeParserConfig);
- String xml = getXML("testWORD_text_box.docx", pc).xml;
- assertContains("This text is directly in the body of the document.", xml);
- assertNotContained("This text is inside of a text box in the body of the document.", xml);
- assertNotContained("This text is inside of a text box in the header of the document.", xml);
- assertNotContained("This text is inside of a text box in the footer of the document.", xml);
- }
-
- /**
- * Test for missing text described in
- * <a href="https://issues.apache.org/jira/browse/TIKA-1130">TIKA-1130</a>.
- * and TIKA-1317
- */
- @Test
- public void testMissingText() throws Exception {
-
- XMLResult xmlResult = getXML("testWORD_missing_text.docx", parseContext);
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- xmlResult.metadata.get(Metadata.CONTENT_TYPE));
- assertContains("BigCompany", xmlResult.xml);
- assertContains("Seasoned", xmlResult.xml);
- assertContains("Rich_text_in_cell", xmlResult.xml);
-
- }
-
- //TIKA-792; with room for future missing bean tests
- @Test
- public void testWordMissingOOXMLBeans() throws Exception {
- //If a bean is missing, POI prints stack trace to stderr
- String[] fileNames = new String[]{"testWORD_missing_ooxml_bean1.docx",//TIKA-792
- };
- PrintStream origErr = System.err;
- for (String fileName : fileNames) {
-
- //grab stderr
- ByteArrayOutputStream errContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errContent, true, UTF_8.name()));
- getXML(fileName, parseContext);
-
- //return stderr
- System.setErr(origErr);
-
- String err = errContent.toString(UTF_8.name());
- assertTrue(err.isEmpty(), "expected no error msg, but got >" +
- err + "<");
- }
- }
-
- @Test
- public void testDOCXThumbnail() throws Exception {
- String xml = getXML("testDOCX_Thumbnail.docx", parseContext).xml;
- int a = xml.indexOf("This file contains a thumbnail");
- int b = xml.indexOf("<div class=\"embedded\" id=\"/docProps/thumbnail.emf\" />");
- assertTrue(a != -1);
- assertTrue(b != -1);
- assertTrue(a < b);
- }
-
- @Test
- public void testEncrypted() throws Exception {
- Map<String, String> tests = new HashMap<>();
- tests.put("testWORD_protected_passtika.docx", "This is an encrypted Word 2007 File");
-
- Metadata m = new Metadata();
- PasswordProvider passwordProvider = metadata -> "tika";
-
- OfficeParserConfig opc = new OfficeParserConfig();
- opc.setUseSAXDocxExtractor(true);
- ParseContext passwordContext = new ParseContext();
- passwordContext.set(org.apache.tika.parser.PasswordProvider.class, passwordProvider);
- passwordContext.set(OfficeParserConfig.class, opc);
- for (Map.Entry<String, String> e : tests.entrySet()) {
- assertContains(e.getValue(), getXML(e.getKey(), passwordContext).xml);
- }
-
- //now try with no password
- for (Map.Entry<String, String> e : tests.entrySet()) {
- boolean exc = false;
- try {
- getXML(e.getKey(), parseContext);
- } catch (EncryptedDocumentException ex) {
- exc = true;
- }
- assertTrue(exc);
- }
-
- }
-
+ //TIKA-2807
@Test
- public void testDOCXParagraphNumbering() throws Exception {
- String xml = getXML("testWORD_numbered_list.docx", parseContext).xml;
- //SAX parser is getting this. DOM parser is not!
- assertContains("add a list here", xml);
- assertContains("1) This", xml);
- assertContains("a) Is", xml);
- assertContains("i) A multi", xml);
- assertContains("ii) Level", xml);
- assertContains("1. Within cell 1", xml);
- assertContains("b. Cell b", xml);
- assertContains("iii) List", xml);
- assertContains("2) foo", xml);
- assertContains("ii) baz", xml);
- assertContains("ii) foo", xml);
- assertContains("II. bar", xml);
- assertContains("6. six", xml);
- assertContains("7. seven", xml);
- assertContains("a. seven a", xml);
- assertContains("e. seven e", xml);
- assertContains("2. A ii 2", xml);
- assertContains("3. page break list 3", xml);
- assertContains("Some-1-CrazyFormat Greek numbering with crazy format - alpha", xml);
- assertContains("1.1.1. 1.1.1", xml);
- assertContains("1.1. 1.2->1.1 //set the value", xml);
-
+ public void testSDTInTextBox() throws Exception {
+ String xml = getXML("testWORD_sdtInTextBox.docx", getParseContext()).xml;
+ assertContains("rich-text-content-control_inside-text-box", xml);
+ assertContainsCount("inside-text", xml, 1);
}
@Test
public void testDOCXOverrideParagraphNumbering() throws Exception {
- String xml = getXML("testWORD_override_list_numbering.docx").xml;
+ String xml = getXML("testWORD_override_list_numbering.docx", getParseContext()).xml;
//Test 1
assertContains("<p>1.1.1.1...1 1.1.1.1...1</p>", xml);
@@ -660,197 +365,97 @@ public class SXWPFExtractorTest extends TikaTest {
assertContains(">02 02", xml);
}
- @Test
- public void testMultiAuthorsManagers() throws Exception {
- XMLResult r = getXML("testWORD_multi_authors.docx", parseContext);
- String[] authors = r.metadata.getValues(TikaCoreProperties.CREATOR);
- assertEquals(3, authors.length);
- assertEquals("author2", authors[1]);
-
- String[] managers = r.metadata.getValues(OfficeOpenXMLExtended.MANAGER);
- assertEquals(2, managers.length);
- assertEquals("manager1", managers[0]);
- assertEquals("manager2", managers[1]);
- }
-
- @Test
- public void testOrigSourcePath() throws Exception {
- Metadata embed1_zip_metadata =
- getRecursiveMetadata("test_recursive_embedded.docx", parseContext).get(2);
- assertContains("C:\\Users\\tallison\\AppData\\Local\\Temp\\embed1.zip", Arrays.asList(
- embed1_zip_metadata.getValues(TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
- assertContains("C:\\Users\\tallison\\Desktop\\tmp\\New folder (2)\\embed1.zip",
- Arrays.asList(
- embed1_zip_metadata.getValues(TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
- }
-
- @Test
- public void testBoldHyperlink() throws Exception {
- //TIKA-1255
- String xml = getXML("testWORD_boldHyperlink.docx", parseContext).xml;
- xml = xml.replaceAll("\\s+", " ");
- assertContains("<a href=\"http://tika.apache.org/\">hyper <b>link</b></a>", xml);
- assertContains("<a href=\"http://tika.apache.org/\"><b>hyper</b> link</a>; bold", xml);
- }
-
- @Test
- public void testLongForIntExceptionInSummaryDetails() throws Exception {
- //TIKA-2055
- assertContains("bold", getXML("testWORD_totalTimeOutOfRange.docx", parseContext).xml);
- }
-
- @Test
- public void testMacrosInDocm() throws Exception {
-
- Metadata parsedBy = new Metadata();
- parsedBy.add(TikaCoreProperties.TIKA_PARSED_BY,
- "org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor");
-
- //test default is "don't extract macros"
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_macros.docm", parseContext);
- for (Metadata metadata : metadataList) {
- if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
- fail("Shouldn't have extracted macros as default");
- }
- }
- assertContainsAtLeast(parsedBy, metadataList);
-
- //now test that they were extracted
- ParseContext context = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setExtractMacros(true);
- officeParserConfig.setUseSAXDocxExtractor(true);
- context.set(OfficeParserConfig.class, officeParserConfig);
-
- metadataList = getRecursiveMetadata("testWORD_macros.docm", context);
- //check that content came out of the .docm file
- assertContains("quick", metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
- assertContainsAtLeast(parsedBy, metadataList);
-
-
- Metadata minExpected = new Metadata();
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
- minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
- minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
- TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
-
- assertContainsAtLeast(minExpected, metadataList);
- assertContainsAtLeast(parsedBy, metadataList);
-
- //test configuring via config file
- AutoDetectParser parser = (AutoDetectParser) TikaLoader.load(
- getConfigPath(SXWPFExtractorTest.class, "tika-config-sax-macros.json"))
- .loadAutoDetectParser();
- metadataList = getRecursiveMetadata("testWORD_macros.docm", parser);
- assertContainsAtLeast(minExpected, metadataList);
- assertContainsAtLeast(parsedBy, metadataList);
- }
-
- @Test
- public void testEmbedded() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_embeded.docx", parseContext);
- Metadata main = metadataList.get(0);
- String content = main.get(TikaCoreProperties.TIKA_CONTENT);
- //make sure mark up is there
- assertContains("<img src=\"embedded:image2.jpeg\" alt=\"A description...\" />", content);
-
- assertContains("<div class=\"embedded\" id=\"rId8\" />", content);
-
- assertEquals(16, metadataList.size());
- }
-
- @Test
- public void testDotx() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_template.docx", parseContext);
- String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
- assertContains("Metallica", content);
- assertContains("Hetfield", content);
- assertContains("one eye open", content);
- assertContains("Getting the perfect", content);
- //from glossary document
- assertContains("table rows", content);
-
- metadataList = getRecursiveMetadata("testWORD_template.dotx", parseContext);
- content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
- //from glossary document
- assertContainsCount("ready to write", content, 2);
- }
-
@Test
public void testDiagramData() throws Exception {
- assertContains("From here", getXML("testWORD_diagramData.docx", parseContext).xml);
+ assertContains("From here",
+ getXML("testWORD_diagramData.docx", getParseContext()).xml);
}
@Test
public void testDOCXChartData() throws Exception {
- String xml = getXML("testWORD_charts.docx", parseContext).xml;
+ String xml = getXML("testWORD_charts.docx", getParseContext()).xml;
assertContains("peach", xml);
assertContains("March\tApril", xml);
assertNotContained("chartSpace", xml);
}
- @Test
- public void testHeaderFooterNotExtraction() throws Exception {
- ParseContext parseContext = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setIncludeHeadersAndFooters(false);
- officeParserConfig.setUseSAXDocxExtractor(true);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
- String xml = getXML("testWORD_various.docx", parseContext).xml;
- assertNotContained("This is the header text.", xml);
- assertNotContained("This is the footer text.", xml);
- }
-
@Test
public void testDOCXPhoneticStrings() throws Exception {
- OfficeParserConfig config = new OfficeParserConfig();
- config.setUseSAXDocxExtractor(true);
- ParseContext parseContext = new ParseContext();
- parseContext.set(OfficeParserConfig.class, config);
assertContains("\u6771\u4EAC (\u3068\u3046\u304D\u3087\u3046)",
- getXML("testWORD_phonetic.docx", parseContext).xml);
-
+ getXML("testWORD_phonetic.docx", getParseContext()).xml);
+ OfficeParserConfig config = new OfficeParserConfig();
config.setConcatenatePhoneticRuns(false);
+ ParseContext parseContext = getParseContext();
+ parseContext.set(OfficeParserConfig.class, config);
String xml = getXML("testWORD_phonetic.docx", parseContext).xml;
assertContains("\u6771\u4EAC", xml);
assertNotContained("\u3068", xml);
}
@Test
- public void testTextDecoration() throws Exception {
- String xml = getXML("testWORD_various.docx", parseContext).xml;
-
- assertContains("<b>Bold</b>", xml);
- assertContains("<i>italic</i>", xml);
- assertContains("<u>underline</u>", xml);
- assertContains("<strike>strikethrough</strike>", xml);
- }
+ public void testVarious() throws Exception {
+ Metadata metadata = new Metadata();
+ String content = getText("testWORD_various.docx", metadata, getParseContext());
+ //content = content.replaceAll("\\s+"," ");
+ assertContains("Footnote appears here", content);
+ assertContains("This is a footnote.", content);
+ assertContains("This is the header text.", content);
+ assertContains("This is the footer text.", content);
+ assertContains("Here is a text box", content);
+ assertContains("Bold", content);
+ assertContains("italic", content);
+ assertContains("underline", content);
+ assertContains("superscript", content);
+ assertContains("subscript", content);
+ assertContains("Here is a citation:", content);
+ assertContains("Figure 1 This is a caption for Figure 1", content);
+ assertContains("(Kramer)", content);
+ assertContains("Row 1 Col 1 Row 1 Col 2 Row 1 Col 3 Row 2 Col 1 Row 2 Col 2 Row 2 Col 3",
+ content.replaceAll("\\s+", " "));
+ assertContains("Row 1 column 1 Row 2 column 1 Row 1 column 2 Row 2 column 2",
+ content.replaceAll("\\s+", " "));
+ assertContains("This is a hyperlink", content);
+ assertContains("Here is a list:", content);
+ for (int row = 1; row <= 3; row++) {
+ //assertContains("·\tBullet " + row, content);
+ //assertContains("\u00b7\tBullet " + row, content);
+ assertContains("Bullet " + row, content);
+ }
+ assertContains("Here is a numbered list:", content);
+ for (int row = 1; row <= 3; row++) {
+ //assertContains(row + ")\tNumber bullet " + row, content);
+ //assertContains(row + ") Number bullet " + row, content);
+ // TODO: OOXMLExtractor fails to number the bullets:
+ assertContains("Number bullet " + row, content);
+ }
- @Test
- public void testTextDecorationNested() throws Exception {
- String xml = getXML("testWORD_various.docx", parseContext).xml;
+ for (int row = 1; row <= 2; row++) {
+ for (int col = 1; col <= 3; col++) {
+ assertContains("Row " + row + " Col " + col, content);
+ }
+ }
- assertContains("<i>ita<strike>li</strike>c</i>", xml);
- assertContains("<i>ita<strike>l<u>i</u></strike>c</i>", xml);
- assertContains("<i><u>unde</u><strike><u>r</u></strike><u>line</u></i>", xml);
+ assertContains("Keyword1 Keyword2", content);
+ assertEquals("Keyword1 Keyword2", metadata.get(Office.KEYWORDS));
+ assertContains("Keyword1 Keyword2",
+ Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
- //confirm that spaces aren't added for <strike/> and <u/>
- String txt = getText("testWORD_various.docx", new Metadata(), parseContext);
- assertContainsCount("italic", txt, 3);
- assertNotContained("ita ", txt);
+ assertContains("Subject is here", content);
+ assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
+ assertContains("Subject is here",
+ Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
- assertContainsCount("underline", txt, 2);
- assertNotContained("unde ", txt);
- }
+ assertContains("Suddenly some Japanese text:", content);
+ // Special version of (GHQ)
+ assertContains("\uff08\uff27\uff28\uff31\uff09", content);
+ // 6 other characters
+ assertContains("\u30be\u30eb\u30b2\u3068\u5c3e\u5d0e\u3001\u6de1\u3005\u3068\u6700\u671f",
+ content);
- //TIKA-2807
- @Test
- public void testSDTInTextBox() throws Exception {
- String xml = getXML("testWORD_sdtInTextBox.docx", parseContext).xml;
- assertContains("rich-text-content-control_inside-text-box", xml);
- assertContainsCount("inside-text", xml, 1);
+ assertContains("And then some Gothic text:", content);
+ assertContains("\uD800\uDF32\uD800\uDF3f\uD800\uDF44\uD800\uDF39\uD800\uDF43\uD800\uDF3A",
+ content);
}
/**
@@ -859,24 +464,26 @@ public class SXWPFExtractorTest extends TikaTest {
*/
@Test
public void testInstrTextHyperlink() throws Exception {
- String xml = getXML("testInstrLink.docx", parseContext).xml;
+ String xml = getXML("testInstrLink.docx", getParseContext()).xml;
// The document contains a HYPERLINK field code in instrText
assertContains("<a href=\"https://exmaple.com/file\">", xml);
assertContains("Access Document(s)", xml);
}
/**
- * Test extraction of external reference field codes (INCLUDEPICTURE, INCLUDETEXT, IMPORT, LINK).
+ * Test extraction of external reference field codes (INCLUDEPICTURE, INCLUDETEXT, IMPORT,
+ * LINK).
* These can be used to hide malicious URLs in documents.
*/
@Test
public void testExternalRefFieldCodes() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testExternalRefs.docx", parseContext);
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testExternalRefs.docx", getParseContext());
Metadata m = metadataList.get(0);
// Check metadata flag is set
assertEquals("true", m.get(Office.HAS_FIELD_HYPERLINKS));
- String xml = getXML("testExternalRefs.docx", parseContext).xml;
+ String xml = getXML("testExternalRefs.docx", getParseContext()).xml;
// Test INCLUDEPICTURE field code
assertContains("class=\"external-ref-INCLUDEPICTURE\"", xml);
assertContains("http://example.com/tracking.png", xml);
@@ -891,75 +498,104 @@ public class SXWPFExtractorTest extends TikaTest {
assertContains("http://test.invalid/cmd.docx", xml);
}
- /**
- * Test extraction of hlinkHover (hover hyperlinks) and VML shape hrefs.
- * These are sneaky ways to hide malicious URLs.
- */
@Test
- public void testHoverAndVmlHyperlinks() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testHoverAndVml.docx", parseContext);
- Metadata m = metadataList.get(0);
- // Check metadata flags are set
- assertEquals("true", m.get(Office.HAS_HOVER_HYPERLINKS));
- assertEquals("true", m.get(Office.HAS_VML_HYPERLINKS));
+ public void testTextDecoration() throws Exception {
+ String xml = getXML("testWORD_various.docx", getParseContext()).xml;
- String xml = getXML("testHoverAndVml.docx", parseContext).xml;
- // Test hlinkHover (activates on mouse hover, not click)
- assertContains("class=\"external-ref-hlinkHover\"", xml);
- assertContains("http://hover.example.com/phishing", xml);
- // Test VML shape href
- assertContains("class=\"external-ref-vml-shape-href\"", xml);
- assertContains("http://vml.example.org/shape-link", xml);
+ assertContains("<b>Bold</b>", xml);
+ assertContains("<i>italic</i>", xml);
+ assertContains("<u>underline</u>", xml);
+ assertContains("<s>strikethrough</s>", xml);
}
- /**
- * Test detection of mail merge in Word documents.
- * Mail merge can reference external data sources.
- */
@Test
- public void testMailMerge() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testMailMerge.docx", parseContext);
- Metadata m = metadataList.get(0);
- assertEquals("true", m.get(Office.HAS_MAIL_MERGE));
+ public void testTextDecorationNested() throws Exception {
+ String xml = getXML("testWORD_various.docx", getParseContext()).xml;
+
+ assertContains("<i>ita<s>li</s>c</i>", xml);
+ assertContains("<i>ita<s>l<u>i</u></s>c</i>", xml);
+
+ String txt = getText("testWORD_various.docx", new Metadata(), getParseContext());
+ assertContainsCount("italic", txt, 3);
+ assertNotContained("ita ", txt);
+
+ assertContainsCount("underline", txt, 2);
+ assertNotContained("unde ", txt);
}
- /**
- * Test detection of attached external template.
- * Templates can be fetched from malicious URLs.
- */
+ // TIKA-692
@Test
- public void testAttachedTemplate() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testAttachedTemplate.docx", parseContext);
- Metadata m = metadataList.get(0);
- assertEquals("true", m.get(Office.HAS_ATTACHED_TEMPLATE));
+ public void testContiguousHTMLFormatting() throws Exception {
+ String xml = getXML("testWORD_bold_character_runs.docx", getParseContext()).xml;
+ assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"),
+ "Bold text wasn't contiguous: " + xml);
- String xml = getXML("testAttachedTemplate.docx", parseContext).xml;
- assertContains("class=\"external-ref-attachedTemplate\"", xml);
- assertContains("example.com/templates", xml);
+ xml = getXML("testWORD_bold_character_runs2.docx", getParseContext()).xml;
+ assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"),
+ "Bold text wasn't contiguous: " + xml);
}
- /**
- * Test detection of subdocuments (master document linking external docs).
- */
+ //TIKA-2346
@Test
- public void testSubdocument() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testSubdocument.docx", parseContext);
- Metadata m = metadataList.get(0);
- assertEquals("true", m.get(Office.HAS_SUBDOCUMENTS));
+ public void testTurningOffTextBoxExtraction() throws Exception {
+ ParseContext pc = getParseContext();
+ OfficeParserConfig config = getOrCreateOfficeParserConfig(pc);
+ config.setIncludeShapeBasedContent(false);
+ String xml = getXML("testWORD_text_box.docx", pc).xml;
+ assertContains("This text is directly in the body of the document.", xml);
+ assertNotContained(
+ "This text is inside of a text box in the body of the document.", xml);
+ assertNotContained(
+ "This text is inside of a text box in the header of the document.", xml);
+ assertNotContained(
+ "This text is inside of a text box in the footer of the document.", xml);
+ }
+
+ @Test
+ public void testHeaderFooterNotExtraction() throws Exception {
+ ParseContext pc = getParseContext();
+ OfficeParserConfig config = getOrCreateOfficeParserConfig(pc);
+ config.setIncludeHeadersAndFooters(false);
+ String xml = getXML("testWORD_various.docx", pc).xml;
+ assertNotContained("This is the header text.", xml);
+ assertNotContained("This is the footer text.", xml);
+ }
- String xml = getXML("testSubdocument.docx", parseContext).xml;
- assertContains("class=\"external-ref-subDocument\"", xml);
- assertContains("example.org/chapters", xml);
+ @Test
+ public void testWordMissingOOXMLBeans() throws Exception {
+ String[] fileNames = new String[]{"testWORD_missing_ooxml_bean1.docx"};
+ PrintStream origErr = System.err;
+ for (String fileName : fileNames) {
+ ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(errContent, true, UTF_8.name()));
+ getXML(fileName, getParseContext());
+ System.setErr(origErr);
+ String err = errContent.toString(UTF_8.name());
+ assertTrue(err.isEmpty(),
+ "expected no error msg, but got >" + err + "<");
+ }
}
- /**
- * Test detection of framesets (HTML frames loading external URLs).
- */
+
@Test
- public void testFrameset() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testFrameset.docx", parseContext);
+ public void testFeatureExtraction() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_features.docx", getParseContext());
Metadata m = metadataList.get(0);
- assertEquals("true", m.get(Office.HAS_FRAMESETS));
+ assertContains("Kyle Reese", Arrays.asList(m.getValues(Office.COMMENT_PERSONS)));
+ assertEquals("true", m.get(Office.HAS_HIDDEN_TEXT));
+ assertEquals("true", m.get(Office.HAS_TRACK_CHANGES));
+ assertEquals("true", m.get(Office.HAS_COMMENTS));
}
+ @Test
+ public void testDotx() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_template.docx", getParseContext());
+ String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+ assertContains("Metallica", content);
+ assertContains("Hetfield", content);
+ assertContains("one eye open", content);
+ assertContains("Getting the perfect", content);
+ }
}
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/SXSLFExtractorTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxTest.java
similarity index 52%
rename from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXSLFExtractorTest.java
rename to tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxTest.java
index 6d95f5404c..c2f79be3fe 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXSLFExtractorTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxTest.java
@@ -18,125 +18,33 @@ package org.apache.tika.parser.microsoft.ooxml;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xml.sax.ContentHandler;
import org.apache.tika.TikaTest;
-import org.apache.tika.config.loader.TikaLoader;
-import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.TikaCoreProperties;
-import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.PasswordProvider;
import org.apache.tika.parser.microsoft.OfficeParserConfig;
import org.apache.tika.sax.BodyContentHandler;
+public abstract class AbstractOOXMLPptxTest extends TikaTest {
-public class SXSLFExtractorTest extends TikaTest {
+ abstract ParseContext getParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- private ParseContext parseContext;
-
- @BeforeEach
- public void setUp() {
- parseContext = new ParseContext();
- officeParserConfig.setUseSAXPptxExtractor(true);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
-
- }
-
- @Test
- public void basicTest() throws Exception {
-
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_various2.pptx", parseContext);
-
- assertEquals(14, metadataList.size(), "right number of attachments");
-
- String mainContent = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
-
- assertContains("This slide is hidden", mainContent);//TODO: parameterize this
-
- //basic content
- assertContains("FirstBullet", mainContent);
-
- //hyperlink
- assertContains("<a href=\"http://tika.apache.org/\">tika_hyperlink</a>", mainContent);
- //hyperlink in cell
- assertContains("<a href=\"http://lucene.apache.org/\">lucene_hyperlink</a>",
- mainContent);
-
- //text box
- assertContains("Slide2TextBox", mainContent);
- assertContains("<td>R1c1</td>", mainContent);
-
- //wordArt
- assertContains("This is some WordART", mainContent);
-
- //notes
- assertContains("NotesForSlide2", mainContent);
- assertContains("Notes for slide3", mainContent);
- assertContains("NotesMasterHeader", mainContent);
- assertContains("NotesMasterFooter", mainContent);
- assertContains("NotesMasterPageNumber", mainContent);
- assertContains("NotesWordArt", mainContent);
- assertContains("NotesWordArtPage2", mainContent);
- assertContains("NotesTableSlide2", mainContent);
-
- //comments
- assertContains(
- "<p class=\"slide-comment\"><b>Timothy Allison (TA)</b>This is a reply to the " +
- "initial comment</p>",
- mainContent);
-
- //HandoutMaster
- assertContains("HandoutHeader1", mainContent);
- assertContains("HandoutFooter", mainContent);
- assertContains("HandoutDate", mainContent);
- assertContains("TextBoxInHandOut", mainContent);
-
- //text box in master
- assertContains("MASTERTEXTBOX", mainContent);
-
- //equation
- assertContains("3/4", mainContent);
-
- //make sure footer elements are in their own <p/>
- assertContains("<p>12/16/2016</p>", mainContent);
- assertContains("<p>8</p>", mainContent);
-
-
- assertContains("<td>NotesTableSlide2", mainContent);
-
- assertContains("MASTERFOOTERMSG", mainContent);
-
-
- //should not include boilerplate from master
- assertNotContained("Click to edit Master", mainContent);
- assertNotContained("Second level", mainContent);
-
- //TODO: chart content
- //assertContains("SLIDE3ChartTitle", mainContent);
- //assertContains("Category 1", mainContent);
- }
-
- @Test
- public void poiBug54916Test() throws Exception {
- String xml = getXML("testPPTX_overlappingRelations.pptx", parseContext).xml;
- assertContains("POI cannot read this", xml);
- assertContains("Has a relationship to another slide", xml);
- assertContains("can read this too", xml);
+ protected OfficeParserConfig getOrCreateOfficeParserConfig(ParseContext parseContext) {
+ OfficeParserConfig config = parseContext.get(OfficeParserConfig.class);
+ if (config == null) {
+ config = new OfficeParserConfig();
+ parseContext.set(OfficeParserConfig.class, config);
+ }
+ return config;
}
/**
@@ -145,7 +53,7 @@ public class SXSLFExtractorTest extends TikaTest {
*/
@Test
public void testPowerPoint() throws Exception {
- String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm",
+ String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm"
//"thmx", // TIKA-418: Will be supported in POI 3.7 beta 2
//"xps" // TIKA-418: Not yet supported by POI
};
@@ -155,7 +63,7 @@ public class SXSLFExtractorTest extends TikaTest {
"application/vnd.ms-powerpoint.presentation.macroenabled.12",
"application/vnd.ms-powerpoint.slideshow.macroenabled.12",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
- "application/vnd.ms-powerpoint.template.macroenabled.12",};
+ "application/vnd.ms-powerpoint.template.macroenabled.12"};
for (int i = 0; i < extensions.length; i++) {
String extension = extensions[i];
@@ -165,7 +73,7 @@ public class SXSLFExtractorTest extends TikaTest {
ContentHandler handler = new BodyContentHandler();
try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
- AUTO_DETECT_PARSER.parse(tis, handler, metadata, parseContext);
+ AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
"Mime-type checking for " + filename);
@@ -229,7 +137,7 @@ public class SXSLFExtractorTest extends TikaTest {
};
try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
- AUTO_DETECT_PARSER.parse(tis, handler, metadata, parseContext);
+ AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
}
}
}
@@ -254,7 +162,7 @@ public class SXSLFExtractorTest extends TikaTest {
ContentHandler handler = new BodyContentHandler();
try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
- AUTO_DETECT_PARSER.parse(tis, handler, metadata, parseContext);
+ AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
// Should get the metadata
assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
@@ -268,7 +176,7 @@ public class SXSLFExtractorTest extends TikaTest {
@Test
public void testVariousPPTX() throws Exception {
Metadata metadata = new Metadata();
- String xml = getXML("testPPT_various.pptx", metadata, parseContext).xml;
+ String xml = getXML("testPPT_various.pptx", metadata, getParseContext()).xml;
assertContains("<p>Footnote appears here", xml);
assertContains("<p>[1] This is a footnote.", xml);
assertContains("<p>This is the header text.</p>", xml);
@@ -286,15 +194,10 @@ public class SXSLFExtractorTest extends TikaTest {
assertContains("<p><a href=\"http://tika.apache.org/\">This is a hyperlink</a>", xml);
assertContains("<p>Here is a list:", xml);
for (int row = 1; row <= 3; row++) {
- //assertContains("·\tBullet " + row, content);
- //assertContains("\u00b7\tBullet " + row, content);
assertContains("<p>Bullet " + row, xml);
}
assertContains("Here is a numbered list:", xml);
for (int row = 1; row <= 3; row++) {
- //assertContains(row + ")\tNumber bullet " + row, content);
- //assertContains(row + ") Number bullet " + row, content);
- // TODO: OOXMLExtractor fails to number the bullets:
assertContains("<p>Number bullet " + row, xml);
}
@@ -323,15 +226,14 @@ public class SXSLFExtractorTest extends TikaTest {
@Test
public void testCommentPPTX() throws Exception {
- XMLResult r = getXML("testPPT_comment.pptx", parseContext);
+ XMLResult r = getXML("testPPT_comment.pptx", getParseContext());
assertContains("<p class=\"slide-comment\"><b>Allison, Timothy B. (ATB)", r.xml);
}
@Test
public void testMasterFooter() throws Exception {
-
assertContains("Master footer is here",
- getXML("testPPT_masterFooter.pptx", parseContext).xml);
+ getXML("testPPT_masterFooter.pptx", getParseContext()).xml);
}
@Test
@@ -341,11 +243,9 @@ public class SXSLFExtractorTest extends TikaTest {
//the underlying xml has "Master footer" in
//the actual slide's xml, not just in the master slide.
- OfficeParserConfig config = new OfficeParserConfig();
+ ParseContext context = getParseContext();
+ OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
config.setIncludeSlideMasterContent(false);
- config.setUseSAXPptxExtractor(true);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
String xml = getXML("testPPT_masterFooter.pptx", context).xml;
assertNotContained("Master footer", xml);
}
@@ -357,90 +257,35 @@ public class SXSLFExtractorTest extends TikaTest {
@Test
public void testMasterText() throws Exception {
assertContains("Text that I added to the master slide",
- getXML("testPPT_masterText.pptx", parseContext).xml);
+ getXML("testPPT_masterText.pptx", getParseContext()).xml);
//now test turning off master content
- OfficeParserConfig config = new OfficeParserConfig();
+ ParseContext context = getParseContext();
+ OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
config.setIncludeSlideMasterContent(false);
- config.setUseSAXPptxExtractor(true);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- assertNotContained("Text that I added", getXML("testPPT_masterText.pptx", context).xml);
+ assertNotContained("Text that I added",
+ getXML("testPPT_masterText.pptx", context).xml);
}
@Test
public void testMasterText2() throws Exception {
assertContains("Text that I added to the master slide",
- getXML("testPPT_masterText2.pptx", parseContext).xml);
+ getXML("testPPT_masterText2.pptx", getParseContext()).xml);
//now test turning off master content
- OfficeParserConfig config = new OfficeParserConfig();
+ ParseContext context = getParseContext();
+ OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
config.setIncludeSlideMasterContent(false);
- config.setUseSAXPptxExtractor(true);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- assertNotContained("Text that I added", getXML("testPPT_masterText2.pptx", context).xml);
+ assertNotContained("Text that I added",
+ getXML("testPPT_masterText2.pptx", context).xml);
}
@Test
public void testWordArt() throws Exception {
- assertContains("Here is some red word Art", getXML("testWordArt.pptx", parseContext).xml);
- }
-
- @Test
- public void testPowerPointCustomProperties() throws Exception {
- Metadata metadata = new Metadata();
-
- ParseContext context = new ParseContext();
- context.set(Locale.class, Locale.US);
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setUseSAXPptxExtractor(true);
- context.set(OfficeParserConfig.class, officeParserConfig);
-
- getXML("testPPT_custom_props.pptx", metadata, parseContext);
- assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
- metadata.get(Metadata.CONTENT_TYPE));
- assertEquals("JOUVIN ETIENNE", metadata.get(TikaCoreProperties.CREATOR));
- assertEquals("EJ04325S", metadata.get(TikaCoreProperties.MODIFIER));
- assertEquals("2011-08-22T13:30:53Z", metadata.get(TikaCoreProperties.CREATED));
- assertEquals("2011-08-22T13:32:49Z", metadata.get(TikaCoreProperties.MODIFIED));
- assertEquals("1", metadata.get(Office.SLIDE_COUNT));
- assertEquals("3", metadata.get(Office.WORD_COUNT));
- assertEquals("Test extraction properties pptx", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("true", metadata.get("custom:myCustomBoolean"));
- assertEquals("3", metadata.get("custom:myCustomNumber"));
- assertEquals("MyStringValue", metadata.get("custom:MyCustomString"));
- assertEquals("2010-12-30T22:00:00Z", metadata.get("custom:MyCustomDate"));
- assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
- }
-
- // TIKA-997:
- @Test
- @Disabled("TODO: add in embedded file markup")
- public void testEmbeddedZipInPPTX() throws Exception {
- String xml = getXML("test_embedded_zip.pptx", parseContext).xml;
- int h = xml.indexOf("<div class=\"embedded\" id=\"slide1_rId3\" />");
- int i = xml.indexOf("Send me a note");
- int j = xml.indexOf("<div class=\"embedded\" id=\"slide2_rId4\" />");
- int k = xml.indexOf("<p>No title</p>");
- assertTrue(h != -1);
- assertTrue(i != -1);
- assertTrue(j != -1);
- assertTrue(k != -1);
- assertTrue(h < i);
- assertTrue(i < j);
- assertTrue(j < k);
- }
-
- // TIKA-1032:
- @Test
- @Disabled("TODO: add in embedded file markup")
- public void testEmbeddedPPTXTwoSlides() throws Exception {
- String xml = getXML("testPPT_embedded_two_slides.pptx", parseContext).xml;
- assertContains("<div class=\"embedded\" id=\"slide1_rId7\" />", xml);
- assertContains("<div class=\"embedded\" id=\"slide2_rId7\" />", xml);
+ assertContains("Here is some red word Art",
+ getXML("testWordArt.pptx", getParseContext()).xml);
}
//TIKA-817
@@ -449,14 +294,13 @@ public class SXSLFExtractorTest extends TikaTest {
//Following POI-52368, the stored date is extracted,
//not the auto-generated date.
- XMLResult result = getXML("testPPT_autodate.pptx", parseContext);
+ XMLResult result = getXML("testPPT_autodate.pptx", getParseContext());
assertContains("<p>Now</p>\n" + "<p>2011-12-19 10:20:04 AM</p>\n", result.xml);
-
}
@Test
public void testPPTXThumbnail() throws Exception {
- String xml = getXML("testPPTX_Thumbnail.pptx", parseContext).xml;
+ String xml = getXML("testPPTX_Thumbnail.pptx", getParseContext()).xml;
int a = xml.indexOf(
"<body><div class=\"slide-content\"><p>This file contains an embedded thumbnail");
int b = xml.indexOf("<div class=\"embedded\" id=\"/docProps/thumbnail.jpeg\" />");
@@ -466,120 +310,23 @@ public class SXSLFExtractorTest extends TikaTest {
}
@Test
- public void testEncrypted() throws Exception {
- Map<String, String> tests = new HashMap<>();
- tests.put("testPPT_protected_passtika.pptx", "This is an encrypted PowerPoint 2007 slide.");
-
- Metadata m = new Metadata();
- PasswordProvider passwordProvider = new PasswordProvider() {
- @Override
- public String getPassword(Metadata metadata) {
- return "tika";
- }
- };
- ParseContext passwordContext = new ParseContext();
- passwordContext.set(org.apache.tika.parser.PasswordProvider.class, passwordProvider);
- passwordContext.set(OfficeParserConfig.class, officeParserConfig);
-
- for (Map.Entry<String, String> e : tests.entrySet()) {
- try (TikaInputStream tis = getResourceAsStream("/test-documents/" + e.getKey())) {
- ContentHandler handler = new BodyContentHandler();
- AUTO_DETECT_PARSER.parse(tis, handler, m, passwordContext);
- assertContains(e.getValue(), handler.toString());
- }
- }
-
- ParseContext context = new ParseContext();
- //now try with no password
- for (Map.Entry<String, String> e : tests.entrySet()) {
- boolean exc = false;
- try (TikaInputStream tis = getResourceAsStream("/test-documents/" + e.getKey())) {
- ContentHandler handler = new BodyContentHandler();
- AUTO_DETECT_PARSER.parse(tis, handler, m, context);
- } catch (EncryptedDocumentException ex) {
- exc = true;
- }
- assertTrue(exc);
- }
-
- }
-
-
- @Test
- public void testMacrosInPptm() throws Exception {
-
- Metadata parsedBy = new Metadata();
- parsedBy.add(TikaCoreProperties.TIKA_PARSED_BY,
- "org.apache.tika.parser.microsoft.ooxml.xslf.XSLFEventBasedPowerPointExtractor");
-
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_macros.pptm", parseContext);
-
- //test default is "don't extract macros"
- for (Metadata metadata : metadataList) {
- if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
- fail("Shouldn't have extracted macros as default");
- }
- }
-
- assertContainsAtLeast(parsedBy, metadataList);
-
- //now test that they are extracted
- ParseContext context = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setExtractMacros(true);
- officeParserConfig.setUseSAXPptxExtractor(true);
- context.set(OfficeParserConfig.class, officeParserConfig);
-
- Metadata minExpected = new Metadata();
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
- minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
- minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
- TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
-
- metadataList = getRecursiveMetadata("testPPT_macros.pptm", context);
-
- assertContainsAtLeast(minExpected, metadataList);
- assertContainsAtLeast(parsedBy, metadataList);
-
- //test configuring via config file
- AutoDetectParser parser = (AutoDetectParser) TikaLoader.load(
- getConfigPath(SXSLFExtractorTest.class, "tika-config-sax-macros.json"))
- .loadAutoDetectParser();
- metadataList = getRecursiveMetadata("testPPT_macros.pptm", parser);
- assertContainsAtLeast(minExpected, metadataList);
- assertContainsAtLeast(parsedBy, metadataList);
- }
-
- @Test
- public void testDiagramData() throws Exception {
- assertContains("President", getXML("testPPT_diagramData.pptx", parseContext).xml);
+ public void testPPTXDiagramData() throws Exception {
+ assertContains("President",
+ getXML("testPPT_diagramData.pptx", getParseContext()).xml);
}
@Test
public void testPPTXChartData() throws Exception {
- String xml = getXML("testPPT_charts.pptx", parseContext).xml;
+ String xml = getXML("testPPT_charts.pptx", getParseContext()).xml;
assertContains("peach", xml);
assertContains("March\tApril", xml);
assertNotContained("chartSpace", xml);
}
- @Test
- public void testEmbeddedMedia() throws Exception {
- List<Metadata> metadataList =
- getRecursiveMetadata("testPPT_embeddedMP3.pptx", parseContext);
- assertEquals(4, metadataList.size());
- assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
- metadataList.get(0).get(Metadata.CONTENT_TYPE));
- assertEquals("audio/mpeg", metadataList.get(1).get(Metadata.CONTENT_TYPE));
- assertEquals("image/png", metadataList.get(2).get(Metadata.CONTENT_TYPE));
- assertEquals("image/jpeg", metadataList.get(3).get(Metadata.CONTENT_TYPE));
-
- }
-
@Test
public void testPPTXGroups() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_groups.pptx", parseContext);
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_groups.pptx", getParseContext());
assertEquals(3, metadataList.size());
String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
assertContains("WordArt1", content);
@@ -591,7 +338,6 @@ public class SXSLFExtractorTest extends TikaTest {
assertContains("Text box4", content);
assertContains("Text box5", content);
-
assertContains("href=\"http://tika.apache.org", content);
assertContains("smart1", content);
assertContains("MyTitle", content);
@@ -603,4 +349,15 @@ public class SXSLFExtractorTest extends TikaTest {
metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
}
+ @Test
+ public void testEmbeddedMedia() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_embeddedMP3.pptx", getParseContext());
+ assertEquals(4, metadataList.size());
+ assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ metadataList.get(0).get(Metadata.CONTENT_TYPE));
+ assertEquals("audio/mpeg", metadataList.get(1).get(Metadata.CONTENT_TYPE));
+ assertEquals("image/png", metadataList.get(2).get(Metadata.CONTENT_TYPE));
+ assertEquals("image/jpeg", metadataList.get(3).get(Metadata.CONTENT_TYPE));
+ }
}
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/OOXMLDocxDOMTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMTest.java
new file mode 100644
index 0000000000..5d9fd42ec6
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
+import org.apache.tika.parser.microsoft.OfficeParserTest;
+
+/**
+ * Runs the shared DOCX tests using the DOM-based parser (default),
+ * plus DOM-specific tests.
+ */
+public class OOXMLDocxDOMTest extends AbstractOOXMLDocxTest {
+
+ @Override
+ ParseContext getParseContext() {
+ return new ParseContext();
+ }
+
+ @Test
+ public void testTextDecorationNestedUnderlineStrike() throws Exception {
+ // DOM nests s inside u: <u>unde<s>r</s>line</u>
+ String xml = getXML("testWORD_various.docx", getParseContext()).xml;
+ assertContains("<i><u>unde<s>r</s>line</u></i>", xml);
+ }
+
+ @Test
+ public void testDOCXHeaderFooterNotExtractionViaConfig() throws Exception {
+ // Test configuration via tika-config
+ AutoDetectParser configuredParser = (AutoDetectParser) TikaLoader.load(
+ getConfigPath(OfficeParserTest.class, "tika-config-headers-footers.json"))
+ .loadAutoDetectParser();
+ String xml = getXML("testWORD_various.docx", configuredParser).xml;
+ assertNotContained("This is the header text.", xml);
+ assertNotContained("This is the footer text.", xml);
+ }
+
+ @Test
+ public void testDOCXParagraphNumbering() throws Exception {
+ String xml = getXML("testWORD_numbered_list.docx", getParseContext()).xml;
+ assertContains("1) This", xml);
+ assertContains("a) Is", xml);
+ assertContains("i) A multi", xml);
+ assertContains("ii) Level", xml);
+ assertContains("1. Within cell 1", xml);
+ assertContains("b. Cell b", xml);
+ assertContains("iii) List", xml);
+ assertContains("2) foo", xml);
+ assertContains("ii) baz", xml);
+ assertContains("ii) foo", xml);
+ assertContains("II. bar", xml);
+ assertContains("6. six", xml);
+ assertContains("7. seven", xml);
+ assertContains("a. seven a", xml);
+ assertContains("e. seven e", xml);
+ assertContains("2. A ii 2", xml);
+ assertContains("3. page break list 3", xml);
+ assertContains("Some-1-CrazyFormat Greek numbering with crazy format - alpha", xml);
+ assertContains("1.1.1. 1.1.1", xml);
+ assertContains("1.1. 1.2->1.1 //set the value", xml);
+ }
+
+ @Test
+ public void testMacrosInDocm() throws Exception {
+ //test default is "don't extract macros"
+ for (Metadata metadata : getRecursiveMetadata("testWORD_macros.docm", getParseContext())) {
+ if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
+ fail("Shouldn't have extracted macros as default");
+ }
+ }
+
+ //now test that they were extracted
+ ParseContext context = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setExtractMacros(true);
+ context.set(OfficeParserConfig.class, officeParserConfig);
+
+ Metadata minExpected = new Metadata();
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
+ minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
+ minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
+
+ assertContainsAtLeast(minExpected,
+ getRecursiveMetadata("testWORD_macros.docm", context));
+
+ //test configuring via config file
+ AutoDetectParser parser = (AutoDetectParser) TikaLoader.load(
+ getConfigPath(OOXMLParserTest.class, "tika-config-dom-macros.json"))
+ .loadAutoDetectParser();
+ assertContainsAtLeast(minExpected,
+ getRecursiveMetadata("testWORD_macros.docm", parser));
+ }
+
+ @Test
+ public void testEmbedded() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_embeded.docx", getParseContext());
+ Metadata main = metadataList.get(0);
+ String content = main.get(TikaCoreProperties.TIKA_CONTENT);
+ assertContains(
+ "<img src=\"embedded:image2.jpeg\" alt=\"A description...\" />", content);
+ assertContains("<div class=\"embedded\" id=\"rId8\" />", content);
+ assertEquals(16, metadataList.size());
+ }
+
+}
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
new file mode 100644
index 0000000000..38f0cb080b
--- /dev/null
+++ 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
@@ -0,0 +1,366 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.exception.EncryptedDocumentException;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.OfficeOpenXMLExtended;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.PasswordProvider;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
+
+/**
+ * Runs the shared DOCX tests using the SAX-based streaming parser,
+ * plus SAX-specific tests (security features, deleted content, etc.).
+ */
+public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
+
+ @Override
+ ParseContext getParseContext() {
+ ParseContext parseContext = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setUseSAXDocxExtractor(true);
+ parseContext.set(OfficeParserConfig.class, officeParserConfig);
+ return parseContext;
+ }
+
+ @Test
+ public void testTextDecorationNestedUnderlineStrike() throws Exception {
+ // SAX closes u before opening s (since s is outer): <u>unde</u><s><u>r</u></s><u>line</u>
+ String xml = getXML("testWORD_various.docx", getParseContext()).xml;
+ assertContains("<i><u>unde</u><s><u>r</u></s><u>line</u></i>", xml);
+ }
+
+ @Test
+ public void basicTest() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_2006ml.docx", getParseContext());
+
+ assertEquals(8, metadataList.size());
+ Metadata m = metadataList.get(0);
+
+ assertEquals("2016-11-29T00:58:00Z", m.get(TikaCoreProperties.CREATED));
+ assertEquals("2016-11-29T17:54:00Z", m.get(TikaCoreProperties.MODIFIED));
+ assertEquals("My Document Title", m.get(TikaCoreProperties.TITLE));
+ assertEquals("This is the Author", m.get(TikaCoreProperties.CREATOR));
+ assertEquals("3", m.get(OfficeOpenXMLCore.REVISION));
+ assertEquals("Allison, Timothy B.", m.get(TikaCoreProperties.MODIFIER));
+ assertEquals("260", m.get(Office.WORD_COUNT));
+ assertEquals("3", m.get(Office.PARAGRAPH_COUNT));
+ assertEquals("1742", m.get(Office.CHARACTER_COUNT_WITH_SPACES));
+ assertEquals("12", m.get(Office.LINE_COUNT));
+ assertEquals("16.0000", m.get(OfficeOpenXMLExtended.APP_VERSION));
+
+ String content = m.get(TikaCoreProperties.TIKA_CONTENT);
+
+ assertContainsCount("engaging title page", content, 1);
+ assertContainsCount("This is the Author\n", content, 1);
+ assertContainsCount("This is an engaging title page", content, 1);
+
+ assertContains("My Document Title", content);
+ assertContains("My Document Subtitle", content);
+
+ assertContains(
+ "<p class=\"toc_1\">\t<a href=\"#_Toc467647605\">Heading1\t3</a></p>",
+ content);
+
+ assertContains("2. Really basic 2.", content);
+
+ assertContainsCount("This is a text box", content, 1);
+
+ assertContains(
+ "<p>This is a hyperlink: <a href=\"http://tika.apache.org\">tika</a></p>",
+ content);
+
+ assertContains(
+ "<p>This is a link to a local file: " +
+ "<a href=\"file:///C:/data/test.png\">test.png</a></p>",
+ content);
+
+ assertContains("<p>This is 10 spaces</p>", content);
+
+ assertContains(
+ "<p class=\"table_of_figures\">\t" +
+ "<a href=\"#_Toc467647797\">Table 1: Table1 Caption\t2</a></p>",
+ content);
+
+ assertContains("<td>Embedded table r1c1", content);
+ assertContainsCount("This is text within a shape", content, 1);
+ assertContains("<p>Rich text content control", content);
+ assertContains("<p>Simple text content control", content);
+ assertContains("Repeating content", content);
+ assertContains("Drop down1", content);
+ assertContains("<p>11/16/2016</p>", content);
+ assertContains("tab\ttab", content);
+ assertContainsCount("serious word art", content, 1);
+ assertContainsCount("Wordartr1c1", content, 1);
+ assertContains("Click or tap to enter a date", content);
+ assertContains(
+ "<p>The <i>quick</i> brown <b>fox </b>j<i>um</i><b><i>ped</i></b> over",
+ content);
+ assertContains("This is a comment", content);
+ assertContains("This is an endnote", content);
+ assertContains("this is the footnote", content);
+ assertContains("First page header", content);
+ assertContains("Even page header", content);
+ assertContains("Odd page header", content);
+ assertContains("First page footer", content);
+ assertContains("Even page footer", content);
+ assertContains("Odd page footer", content);
+ assertNotContained("frog", content);
+ assertContains("Mattmann", content);
+ }
+
+ @Test
+ public void testPicturesInVariousPlaces() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_embedded_pics.docx", getParseContext());
+
+ assertEquals(3, metadataList.size());
+ String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+ for (int i = 1; i < 4; i++) {
+ assertContains("header" + i + "_pic", content);
+ assertContains("footer" + i + "_pic", content);
+ }
+ assertContains("body_pic.jpg", content);
+ assertContains("sdt_pic.jpg", content);
+ assertContains("deeply_embedded_pic", content);
+ assertContains("deleted_pic", content);
+ assertContains("footnotes_pic", content);
+ assertContains("comments_pic", content);
+ assertContains("endnotes_pic", content);
+
+ assertContainsCount("<img src=", content, 14);
+ }
+
+ @Test
+ public void testSkipDeleted() throws Exception {
+ ParseContext pc = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setIncludeDeletedContent(true);
+ officeParserConfig.setUseSAXDocxExtractor(true);
+ officeParserConfig.setIncludeMoveFromContent(true);
+ pc.set(OfficeParserConfig.class, officeParserConfig);
+
+ XMLResult r = getXML("testWORD_2006ml.docx", pc);
+ assertContains("frog", r.xml);
+ assertContainsCount("Second paragraph", r.xml, 2);
+ }
+
+ @Test
+ public void testEncrypted() throws Exception {
+ Map<String, String> tests = new HashMap<>();
+ tests.put("testWORD_protected_passtika.docx",
+ "This is an encrypted Word 2007 File");
+
+ PasswordProvider passwordProvider = metadata -> "tika";
+ OfficeParserConfig opc = new OfficeParserConfig();
+ opc.setUseSAXDocxExtractor(true);
+ ParseContext passwordContext = new ParseContext();
+ passwordContext.set(PasswordProvider.class, passwordProvider);
+ passwordContext.set(OfficeParserConfig.class, opc);
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ assertContains(e.getValue(), getXML(e.getKey(), passwordContext).xml);
+ }
+
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ boolean exc = false;
+ try {
+ getXML(e.getKey(), getParseContext());
+ } catch (EncryptedDocumentException ex) {
+ exc = true;
+ }
+ assertTrue(exc);
+ }
+ }
+
+ @Test
+ public void testDOCXParagraphNumbering() throws Exception {
+ String xml = getXML("testWORD_numbered_list.docx", getParseContext()).xml;
+ assertContains("add a list here", xml);
+ assertContains("1) This", xml);
+ assertContains("a) Is", xml);
+ assertContains("i) A multi", xml);
+ assertContains("ii) Level", xml);
+ assertContains("1. Within cell 1", xml);
+ assertContains("b. Cell b", xml);
+ assertContains("iii) List", xml);
+ assertContains("2) foo", xml);
+ assertContains("ii) baz", xml);
+ assertContains("ii) foo", xml);
+ assertContains("II. bar", xml);
+ assertContains("6. six", xml);
+ assertContains("7. seven", xml);
+ assertContains("a. seven a", xml);
+ assertContains("e. seven e", xml);
+ assertContains("2. A ii 2", xml);
+ assertContains("3. page break list 3", xml);
+ assertContains(
+ "Some-1-CrazyFormat Greek numbering with crazy format - alpha", xml);
+ assertContains("1.1.1. 1.1.1", xml);
+ assertContains("1.1. 1.2->1.1 //set the value", xml);
+ }
+
+ @Test
+ public void testMacrosInDocm() throws Exception {
+ Metadata parsedBy = new Metadata();
+ parsedBy.add(TikaCoreProperties.TIKA_PARSED_BY,
+ "org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor");
+
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_macros.docm", getParseContext());
+ for (Metadata metadata : metadataList) {
+ if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
+ fail("Shouldn't have extracted macros as default");
+ }
+ }
+ assertContainsAtLeast(parsedBy, metadataList);
+
+ ParseContext context = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setExtractMacros(true);
+ officeParserConfig.setUseSAXDocxExtractor(true);
+ context.set(OfficeParserConfig.class, officeParserConfig);
+
+ metadataList = getRecursiveMetadata("testWORD_macros.docm", context);
+ assertContains("quick",
+ metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
+ assertContainsAtLeast(parsedBy, metadataList);
+
+ Metadata minExpected = new Metadata();
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
+ minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
+ minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
+
+ assertContainsAtLeast(minExpected, metadataList);
+ assertContainsAtLeast(parsedBy, metadataList);
+
+ AutoDetectParser parser = (AutoDetectParser) TikaLoader.load(
+ getConfigPath(OOXMLDocxSAXTest.class, "tika-config-sax-macros.json"))
+ .loadAutoDetectParser();
+ metadataList = getRecursiveMetadata("testWORD_macros.docm", parser);
+ assertContainsAtLeast(minExpected, metadataList);
+ assertContainsAtLeast(parsedBy, metadataList);
+ }
+
+ @Test
+ public void testEmbedded() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_embeded.docx", getParseContext());
+ Metadata main = metadataList.get(0);
+ String content = main.get(TikaCoreProperties.TIKA_CONTENT);
+ assertContains(
+ "<img src=\"embedded:image2.jpeg\" alt=\"A description...\" />", content);
+ assertContains("<div class=\"embedded\" id=\"rId8\" />", content);
+ assertEquals(16, metadataList.size());
+ }
+
+ @Test
+ public void testInitializationViaConfig() throws Exception {
+ //NOTE: this test relies on a bug in the DOM extractor that
+ //is passing over the title information.
+ //once we fix that, this test will no longer be meaningful!
+ AutoDetectParser p = (AutoDetectParser) TikaLoader.load(
+ getConfigPath(OOXMLDocxSAXTest.class, "tika-config-sax-docx.json"))
+ .loadAutoDetectParser();
+ XMLResult xml = getXML("testWORD_2006ml.docx", p, new Metadata());
+ assertContains("engaging title", xml.xml);
+ }
+
+ @Test
+ public void testTruncatedSAXDocx() throws Exception {
+ assertThrows(TikaException.class, () -> {
+ getRecursiveMetadata("testWORD_truncated.docx", getParseContext());
+ });
+ }
+
+ // Security feature tests
+
+ @Test
+ public void testHoverAndVmlHyperlinks() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testHoverAndVml.docx", getParseContext());
+ Metadata m = metadataList.get(0);
+ assertEquals("true", m.get(Office.HAS_HOVER_HYPERLINKS));
+ assertEquals("true", m.get(Office.HAS_VML_HYPERLINKS));
+
+ String xml = getXML("testHoverAndVml.docx", getParseContext()).xml;
+ assertContains("class=\"external-ref-hlinkHover\"", xml);
+ assertContains("http://hover.example.com/phishing", xml);
+ assertContains("class=\"external-ref-vml-shape-href\"", xml);
+ assertContains("http://vml.example.org/shape-link", xml);
+ }
+
+ @Test
+ public void testMailMerge() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testMailMerge.docx", getParseContext());
+ Metadata m = metadataList.get(0);
+ assertEquals("true", m.get(Office.HAS_MAIL_MERGE));
+ }
+
+ @Test
+ public void testAttachedTemplate() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testAttachedTemplate.docx", getParseContext());
+ Metadata m = metadataList.get(0);
+ assertEquals("true", m.get(Office.HAS_ATTACHED_TEMPLATE));
+
+ String xml = getXML("testAttachedTemplate.docx", getParseContext()).xml;
+ assertContains("class=\"external-ref-attachedTemplate\"", xml);
+ assertContains("example.com/templates", xml);
+ }
+
+ @Test
+ public void testSubdocument() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testSubdocument.docx", getParseContext());
+ Metadata m = metadataList.get(0);
+ assertEquals("true", m.get(Office.HAS_SUBDOCUMENTS));
+
+ String xml = getXML("testSubdocument.docx", getParseContext()).xml;
+ assertContains("class=\"external-ref-subDocument\"", xml);
+ assertContains("example.org/chapters", xml);
+ }
+
+ @Test
+ public void testFrameset() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testFrameset.docx", getParseContext());
+ Metadata m = metadataList.get(0);
+ assertEquals("true", m.get(Office.HAS_FRAMESETS));
+ }
+}
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/OOXMLParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
index ecbe1956ec..f8fe58b142 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
@@ -16,20 +16,11 @@
*/
package org.apache.tika.parser.microsoft.ooxml;
-import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.PrintStream;
-import java.io.StringWriter;
import java.text.DecimalFormatSymbols;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -38,10 +29,6 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
import org.apache.poi.util.LocaleUtil;
import org.junit.jupiter.api.AfterAll;
@@ -49,22 +36,18 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.xml.sax.ContentHandler;
import org.apache.tika.MultiThreadedTikaTest;
import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.detect.DefaultDetector;
import org.apache.tika.detect.Detector;
import org.apache.tika.exception.EncryptedDocumentException;
-import org.apache.tika.exception.TikaException;
import org.apache.tika.io.TikaInputStream;
-import org.apache.tika.metadata.DublinCore;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.OfficeOpenXMLExtended;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
-import org.apache.tika.parser.EmptyParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.PasswordProvider;
@@ -72,8 +55,6 @@ import org.apache.tika.parser.RecursiveParserWrapper;
import org.apache.tika.parser.microsoft.OfficeParser;
import org.apache.tika.parser.microsoft.OfficeParserConfig;
import org.apache.tika.parser.microsoft.OfficeParserTest;
-import org.apache.tika.sax.BodyContentHandler;
-import org.apache.tika.utils.XMLReaderUtils;
public class OOXMLParserTest extends MultiThreadedTikaTest {
@@ -209,278 +190,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- /**
- * We have a number of different powerpoint files,
- * such as presentation, macro-enabled etc
- */
- @Test
- public void testPowerPoint() throws Exception {
- String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm"
- //"thmx", // TIKA-418: Will be supported in POI 3.7 beta 2
- //"xps" // TIKA-418: Not yet supported by POI
- };
-
- String[] mimeTypes = new String[]{
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- "application/vnd.ms-powerpoint.presentation.macroenabled.12",
- "application/vnd.ms-powerpoint.slideshow.macroenabled.12",
- "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
- "application/vnd.ms-powerpoint.template.macroenabled.12"};
-
- for (int i = 0; i < extensions.length; i++) {
- String extension = extensions[i];
- String filename = "testPPT." + extension;
-
- Metadata metadata = new Metadata();
- ContentHandler handler = new BodyContentHandler();
- ParseContext context = new ParseContext();
- String content = getText(filename, metadata, context);
-
- assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
- "Mime-type checking for " + filename);
- assertEquals("Attachment Test", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("Rajiv", metadata.get(TikaCoreProperties.CREATOR));
-
- // Theme files don't have the text in them
- if (extension.equals("thmx")) {
- assertEquals("", content);
- } else {
- assertTrue(content.contains("Attachment Test"),
- "Text missing for " + filename + "\n" + content);
- assertTrue(content.contains("This is a test file data with the same content"),
- "Text missing for " + filename + "\n" + content);
- assertTrue(content.contains("content parsing"),
- "Text missing for " + filename + "\n" + content);
- assertTrue(content.contains("Different words to test against"),
- "Text missing for " + filename + "\n" + content);
- assertTrue(content.contains("Mystery"),
- "Text missing for " + filename + "\n" + content);
- }
- }
-
- }
-
- /**
- * Test that the metadata is already extracted when the body is processed.
- * See TIKA-1109
- */
- @Test
- public void testPowerPointMetadataEarly() throws Exception {
- String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm"
- //"thmx", // TIKA-418: Will be supported in POI 3.7 beta 2
- //"xps" // TIKA-418: Not yet supported by POI
- };
-
- final String[] mimeTypes = new String[]{
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- "application/vnd.ms-powerpoint.presentation.macroenabled.12",
- "application/vnd.ms-powerpoint.slideshow.macroenabled.12",
- "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
- "application/vnd.ms-powerpoint.template.macroenabled.12"};
-
- for (int i = 0; i < extensions.length; i++) {
- String extension = extensions[i];
- final String filename = "testPPT." + extension;
- final Metadata metadata = new Metadata();
-
- // Allow the value to be access from the inner class
- final int currentI = i;
- ContentHandler handler = new BodyContentHandler() {
- @Override
- public void startDocument() {
- assertEquals(mimeTypes[currentI], metadata.get(Metadata.CONTENT_TYPE),
- "Mime-type checking for " + filename);
- assertEquals("Attachment Test", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("Rajiv", metadata.get(TikaCoreProperties.CREATOR));
-
- }
-
- };
- ParseContext context = new ParseContext();
-
- try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
- AUTO_DETECT_PARSER.parse(tis, handler, metadata, context);
- }
- }
- }
-
- /**
- * For the PowerPoint formats we don't currently support, ensure that
- * we don't break either
- */
- @Test
- public void testUnsupportedPowerPoint() throws Exception {
- String[] extensions = new String[]{"xps", "thmx"};
- String[] mimeTypes = new String[]{"application/vnd.ms-xpsdocument",
- "application/vnd.openxmlformats-officedocument" // Is this right?
- };
-
- for (int i = 0; i < extensions.length; i++) {
- String extension = extensions[i];
- String filename = "testPPT." + extension;
-
- Metadata metadata = new Metadata();
- metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, filename);
- getXML(filename, metadata);
- // Should get the metadata
- assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
- "Mime-type checking for " + filename);
-
-
- }
- }
-
- /**
- * Test the plain text output of the Word converter
- *
- * @throws Exception
- */
- @Test
- public void testWord() throws Exception {
- Metadata metadata = new Metadata();
- String content = getText("testWORD.docx", metadata, new ParseContext());
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- metadata.get(Metadata.CONTENT_TYPE));
- assertEquals("Sample Word Document", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("Keith Bennett", metadata.get(TikaCoreProperties.CREATOR));
- assertTrue(content.contains("Sample Word Document"));
-
- }
-
- /**
- * Test the plain text output of the Word converter
- *
- * @throws Exception
- */
- @Test
- public void testWordFootnote() throws Exception {
- XMLResult xmlResult = getXML("footnotes.docx");
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- xmlResult.metadata.get(Metadata.CONTENT_TYPE));
- assertTrue(xmlResult.xml.contains("snoska"));
- //TIKA-4657 -- footnote content should be in a div with class "footnote"
- // and should not be nested inside the paragraph
- assertContains("<div class=\"footnote\">", xmlResult.xml);
- assertNotContained("<p><div class=\"footnote\">", xmlResult.xml);
- }
-
- @Test
- public void testEndnoteWithTable() throws Exception {
- XMLResult xmlResult = getXML("testWORD_endnote_table.docx");
- assertContains("Cat Property Act", xmlResult.xml);
- //TIKA-4657 -- endnote content should be in a div with class "endnote"
- assertContains("<div class=\"endnote\">", xmlResult.xml);
- }
-
- /**
- * Test that the word converter is able to generate the
- * correct HTML for the document
- */
- @Test
- public void testWordHTML() throws Exception {
- XMLResult result = getXML("testWORD.docx");
- String xml = result.xml;
- Metadata metadata = result.metadata;
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- metadata.get(Metadata.CONTENT_TYPE));
- assertEquals("Sample Word Document", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("Keith Bennett", metadata.get(TikaCoreProperties.CREATOR));
- assertTrue(xml.contains("Sample Word Document"));
-
- // Check that custom headings came through
- assertTrue(xml.contains("<h1 class=\"title\">"));
- // Regular headings
- assertTrue(xml.contains("<h1>Heading Level 1</h1>"));
- assertTrue(xml.contains("<h2>Heading Level 2</h2>"));
- // Headings with anchor tags in them
- assertTrue(xml.contains("<h3><a name=\"OnLevel3\" />Heading Level 3</h3>"));
- // Bold and italic
- assertTrue(xml.contains("<b>BOLD</b>"));
- assertTrue(xml.contains("<i>ITALIC</i>"));
- // Table
- assertTrue(xml.contains("<table>"));
- assertTrue(xml.contains("<td>"));
- // Links
- assertTrue(xml.contains("<a href=\"http://tika.apache.org/\">Tika</a>"));
- // Anchor links
- assertContains("<a href=\"#OnMainHeading\">The Main Heading Bookmark</a>", xml);
- // Paragraphs with other styles
- assertTrue(xml.contains("<p class=\"signature\">This one"));
-
- result = getXML("testWORD_3imgs.docx");
- xml = result.xml;
-
- // Images 2-4 (there is no 1!)
- assertTrue(xml.contains("<img src=\"embedded:image2.png\" alt=\"A description...\" />"),
- "Image not found in:\n" + xml);
- assertTrue(xml.contains("<img src=\"embedded:image3.jpeg\" alt=\"A description...\" />"),
- "Image not found in:\n" + xml);
- assertTrue(xml.contains("<img src=\"embedded:image4.png\" alt=\"A description...\" />"),
- "Image not found in:\n" + xml);
-
- // Text too
- assertTrue(xml.contains("<p>The end!</p>"));
-
- // TIKA-692: test document containing multiple
- // character runs within a bold tag:
- xml = getXML("testWORD_bold_character_runs.docx").xml;
-
- // Make sure bold text arrived as single
- // contiguous string even though Word parser
- // handled this as 3 character runs
- assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"), "Bold text wasn't contiguous: " + xml);
-
- // TIKA-692: test document containing multiple
- // character runs within a bold tag:
- xml = getXML("testWORD_bold_character_runs2.docx").xml;
-
- // Make sure bold text arrived as single
- // contiguous string even though Word parser
- // handled this as 3 character runs
- assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"), "Bold text wasn't contiguous: " + xml);
- }
-
- /**
- * Test that we can extract image from docx header
- */
- @Test
- public void testWordPicturesInHeader() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("headerPic.docx");
- assertEquals(2, metadataList.size());
- Metadata m = metadataList.get(0);
- String mainContent = m.get(TikaCoreProperties.TIKA_CONTENT);
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- m.get(Metadata.CONTENT_TYPE));
- // Check that custom headings came through
- assertTrue(mainContent.contains("<img"));
- }
-
- @Test
- @Disabled("need to add links in xhtml")
- public void testPicturesInVariousPlaces() throws Exception {
- //test that images are actually extracted from
- //headers, footers, comments, endnotes, footnotes
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_embedded_pics.docx");
-
- //only process embedded resources once
- assertEquals(3, metadataList.size());
- String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
- for (int i = 1; i < 4; i++) {
- assertContains("header" + i + "_pic", content);
- assertContains("footer" + i + "_pic", content);
- }
- assertContains("body_pic.jpg", content);
- assertContains("sdt_pic.jpg", content);
- assertContains("deeply_embedded_pic", content);
- assertContains("deleted_pic", content);//TODO: don't extract this
- assertContains("footnotes_pic", content);
- assertContains("comments_pic", content);
- assertContains("endnotes_pic", content);
-// assertContains("sdt2_pic.jpg", content);//name of file is not stored in image-sdt
-
- assertContainsCount("<img src=", content, 14);
- }
-
/**
* Documents with some sheets are protected, but not all.
* See TIKA-364.
@@ -513,270 +222,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertContains("Office", xmlResult.xml);
}
- /**
- * Test docx without headers
- * TIKA-633
- */
- @Test
- public void testNullHeaders() throws Exception {
- assertFalse(getXML("NullHeader.docx").xml.isEmpty(),
- "Should have found some text");
- }
-
- @Test
- public void testTextDecoration() throws Exception {
- String xml = getXML("testWORD_various.docx").xml;
-
- assertContains("<b>Bold</b>", xml);
- assertContains("<i>italic</i>", xml);
- assertContains("<u>underline</u>", xml);
- assertContains("<s>strikethrough</s>", xml);
- }
-
- @Test
- public void testTextDecorationNested() throws Exception {
- String xml = getXML("testWORD_various.docx").xml;
-
- assertContains("<i>ita<s>li</s>c</i>", xml);
- assertContains("<i>ita<s>l<u>i</u></s>c</i>", xml);
- assertContains("<i><u>unde<s>r</s>line</u></i>", xml);
-
- //confirm that spaces aren't added for </s> and </u>
- String txt = getText("testWORD_various.docx");
- assertContainsCount("italic", txt, 3);
- assertNotContained("ita ", txt);
-
- assertContainsCount("underline", txt, 2);
- assertNotContained("unde ", txt);
- }
-
- @Test
- public void testVarious() throws Exception {
- Metadata metadata = new Metadata();
-
- String content = getText("testWORD_various.docx", metadata);
- //content = content.replaceAll("\\s+"," ");
- assertContains("Footnote appears here", content);
- assertContains("This is a footnote.", content);
- assertContains("This is the header text.", content);
- assertContains("This is the footer text.", content);
- assertContains("Here is a text box", content);
- assertContains("Bold", content);
- assertContains("italic", content);
- assertContains("underline", content);
- assertContains("superscript", content);
- assertContains("subscript", content);
- assertContains("Here is a citation:", content);
- assertContains("Figure 1 This is a caption for Figure 1", content);
- assertContains("(Kramer)", content);
- assertContains("Row 1 Col 1 Row 1 Col 2 Row 1 Col 3 Row 2 Col 1 Row 2 Col 2 Row 2 Col 3",
- content.replaceAll("\\s+", " "));
- assertContains("Row 1 column 1 Row 2 column 1 Row 1 column 2 Row 2 column 2",
- content.replaceAll("\\s+", " "));
- assertContains("This is a hyperlink", content);
- assertContains("Here is a list:", content);
- for (int row = 1; row <= 3; row++) {
- //assertContains("·\tBullet " + row, content);
- //assertContains("\u00b7\tBullet " + row, content);
- assertContains("Bullet " + row, content);
- }
- assertContains("Here is a numbered list:", content);
- for (int row = 1; row <= 3; row++) {
- //assertContains(row + ")\tNumber bullet " + row, content);
- //assertContains(row + ") Number bullet " + row, content);
- // TODO: OOXMLExtractor fails to number the bullets:
- assertContains("Number bullet " + row, content);
- }
-
- for (int row = 1; row <= 2; row++) {
- for (int col = 1; col <= 3; col++) {
- assertContains("Row " + row + " Col " + col, content);
- }
- }
-
- assertContains("Keyword1 Keyword2", content);
- assertEquals("Keyword1 Keyword2", metadata.get(Office.KEYWORDS));
- assertContains("Keyword1 Keyword2",
- Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
-
-
- assertContains("Subject is here", content);
- assertContains("Subject is here",
- Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
-
-
- assertContains("Suddenly some Japanese text:", content);
- // Special version of (GHQ)
- assertContains("\uff08\uff27\uff28\uff31\uff09", content);
- // 6 other characters
- assertContains("\u30be\u30eb\u30b2\u3068\u5c3e\u5d0e\u3001\u6de1\u3005\u3068\u6700\u671f",
- content);
-
- assertContains("And then some Gothic text:", content);
- assertContains("\uD800\uDF32\uD800\uDF3f\uD800\uDF44\uD800\uDF39\uD800\uDF43\uD800\uDF3A",
- content);
- }
-
- @Test
- public void testDOCXHeaderFooterNotExtraction() throws Exception {
- ParseContext parseContext = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setIncludeHeadersAndFooters(false);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
- String xml = getXML("testWORD_various.docx", parseContext).xml;
- assertNotContained("This is the header text.", xml);
- assertNotContained("This is the footer text.", xml);
-
- //now test configuration via tika-config
- Parser configuredParser = TikaLoader.load(
- getConfigPath(OfficeParserTest.class, "tika-config-headers-footers.json"))
- .loadAutoDetectParser();
- xml = getXML("testWORD_various.docx", configuredParser).xml;
- assertNotContained("This is the header text.", xml);
- assertNotContained("This is the footer text.", xml);
-
- }
-
- @Test
- public void testVariousPPTX() throws Exception {
- Metadata metadata = new Metadata();
- String xml = getXML("testPPT_various.pptx", metadata).xml;
- assertContains("<p>Footnote appears here", xml);
- assertContains("<p>[1] This is a footnote.", xml);
- assertContains("<p>This is the header text.</p>", xml);
- assertContains("<p>This is the footer text.</p>", xml);
- assertContains("<p>Here is a text box</p>", xml);
- assertContains("<p>Bold", xml);
- assertContains("italic underline superscript subscript", xml);
- assertContains("<p>Here is a citation:", xml);
- assertContains("Figure 1 This is a caption for Figure 1", xml);
- assertContains("(Kramer)", xml);
- assertContains("<table><tr>\t<td>Row 1 Col 1</td>", xml);
- assertContains("<td>Row 2 Col 2</td>\t<td>Row 2 Col 3</td></tr>", xml);
- assertContains("<p>Row 1 column 1</p>", xml);
- assertContains("<p>Row 2 column 2</p>", xml);
- assertContains("<p><a href=\"http://tika.apache.org/\">This is a hyperlink</a>", xml);
- assertContains("<p>Here is a list:", xml);
- for (int row = 1; row <= 3; row++) {
- //assertContains("·\tBullet " + row, content);
- //assertContains("\u00b7\tBullet " + row, content);
- assertContains("<p>Bullet " + row, xml);
- }
- assertContains("Here is a numbered list:", xml);
- for (int row = 1; row <= 3; row++) {
- //assertContains(row + ")\tNumber bullet " + row, content);
- //assertContains(row + ") Number bullet " + row, content);
- // TODO: OOXMLExtractor fails to number the bullets:
- assertContains("<p>Number bullet " + row, xml);
- }
-
- for (int row = 1; row <= 2; row++) {
- for (int col = 1; col <= 3; col++) {
- assertContains("Row " + row + " Col " + col, xml);
- }
- }
-
- assertContains("Keyword1 Keyword2", xml);
- assertEquals("Keyword1 Keyword2", metadata.get(Office.KEYWORDS));
-
- assertContains("Subject is here", xml);
- assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
-
- assertContains("Keyword1 Keyword2",
- Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
- assertContains("Subject is here",
- Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
-
-
- assertContains("Suddenly some Japanese text:", xml);
- // Special version of (GHQ)
- assertContains("\uff08\uff27\uff28\uff31\uff09", xml);
- // 6 other characters
- assertContains("\u30be\u30eb\u30b2\u3068\u5c3e\u5d0e\u3001\u6de1\u3005\u3068\u6700\u671f",
- xml);
-
- assertContains("And then some Gothic text:", xml);
- assertContains("\uD800\uDF32\uD800\uDF3f\uD800\uDF44\uD800\uDF39\uD800\uDF43\uD800\uDF3A",
- xml);
- }
-
- @Test
- public void testSkipHeaderFooter() throws Exception {
- //now test turning off header/footer
- OfficeParserConfig config = new OfficeParserConfig();
- config.setIncludeHeadersAndFooters(false);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- String xml = getXML("testPPT_various.pptx", context).xml;
- assertNotContained("This is the header text", xml);
-
- }
-
- @Test
- public void testCommentPPTX() throws Exception {
- XMLResult r = getXML("testPPT_comment.pptx");
- assertContains("<p class=\"slide-comment\"><b>Allison, Timothy B. (ATB)", r.xml);
- }
-
- @Test
- public void testMasterFooter() throws Exception {
- String content = getText("testPPT_masterFooter.pptx");
- assertContains("Master footer is here", content);
- }
-
- @Test
- @Disabled("can't tell why this isn't working")
- public void testTurningOffMasterContent() throws Exception {
- //now test turning off master content
-
- //the underlying xml has "Master footer" in
- //the actual slide's xml, not just in the master slide.
- OfficeParserConfig config = new OfficeParserConfig();
- config.setIncludeSlideMasterContent(false);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- String xml = getXML("testPPT_masterFooter.pptx", context).xml;
- assertNotContained("Master footer", xml);
- }
-
- /**
- * TIKA-712 Master Slide Text from PPT and PPTX files
- * should be extracted too
- */
- @Test
- public void testMasterText() throws Exception {
-
- String content = getText("testPPT_masterText.pptx");
- assertContains("Text that I added to the master slide", content);
-
- //now test turning off master content
- OfficeParserConfig config = new OfficeParserConfig();
- config.setIncludeSlideMasterContent(false);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- content = getXML("testPPT_masterText.pptx", context).xml;
- assertNotContained("Text that I added", content);
- }
-
- @Test
- public void testMasterText2() throws Exception {
- String content = getText("testPPT_masterText2.pptx");
- assertContains("Text that I added to the master slide", content);
-
- //now test turning off master content
- OfficeParserConfig config = new OfficeParserConfig();
- config.setIncludeSlideMasterContent(false);
- ParseContext context = new ParseContext();
- context.set(OfficeParserConfig.class, config);
- content = getXML("testPPT_masterText2.pptx", context).xml;
- assertNotContained("Text that I added", content);
- }
-
- @Test
- public void testWordArt() throws Exception {
- assertContains("Here is some red word Art", getText("testWordArt.pptx"));
- }
-
/**
* Ensures that custom OOXML properties are extracted
*/
@@ -801,197 +246,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
}
- @Test
- public void testWordCustomProperties() throws Exception {
- Metadata metadata = new Metadata();
-
- try (TikaInputStream tis = getResourceAsStream(
- "/test-documents/testWORD_custom_props.docx")) {
- ContentHandler handler = new BodyContentHandler(-1);
- ParseContext context = new ParseContext();
- context.set(Locale.class, Locale.US);
- new OOXMLParser().parse(tis, handler, metadata, context);
- }
-
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- metadata.get(Metadata.CONTENT_TYPE));
- assertEquals("EJ04325S", metadata.get(TikaCoreProperties.CREATOR));
- assertEquals("Etienne Jouvin", metadata.get(TikaCoreProperties.MODIFIER));
- assertEquals("2011-07-29T16:52:00Z", metadata.get(TikaCoreProperties.CREATED));
- assertEquals("2012-01-03T22:14:00Z", metadata.get(TikaCoreProperties.MODIFIED));
- assertEquals("Microsoft Office Word", metadata.get(OfficeOpenXMLExtended.APPLICATION));
- assertEquals("1", metadata.get(Office.PAGE_COUNT));
- assertEquals("2", metadata.get(Office.WORD_COUNT));
- assertEquals("My Title", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("My Keyword", metadata.get(Office.KEYWORDS));
- assertContains("My Keyword", Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
-
- assertEquals("Normal.dotm", metadata.get(OfficeOpenXMLExtended.TEMPLATE));
- assertEquals("My subject", metadata.get(DublinCore.SUBJECT));
- assertEquals("EDF-DIT", metadata.get(TikaCoreProperties.PUBLISHER));
- assertEquals("true", metadata.get("custom:myCustomBoolean"));
- assertEquals("3", metadata.get("custom:myCustomNumber"));
- assertEquals("MyStringValue", metadata.get("custom:MyCustomString"));
- assertEquals("2010-12-30T23:00:00Z", metadata.get("custom:MyCustomDate"));
- assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
- }
-
- @Test
- public void testPowerPointCustomProperties() throws Exception {
- Metadata metadata = new Metadata();
-
- try (TikaInputStream tis = getResourceAsStream("/test-documents/testPPT_custom_props.pptx")) {
- ContentHandler handler = new BodyContentHandler(-1);
- ParseContext context = new ParseContext();
- context.set(Locale.class, Locale.US);
- new OOXMLParser().parse(tis, handler, metadata, context);
- }
-
- assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
- metadata.get(Metadata.CONTENT_TYPE));
- assertEquals("JOUVIN ETIENNE", metadata.get(TikaCoreProperties.CREATOR));
- assertEquals("EJ04325S", metadata.get(TikaCoreProperties.MODIFIER));
- assertEquals("2011-08-22T13:30:53Z", metadata.get(TikaCoreProperties.CREATED));
- assertEquals("2011-08-22T13:32:49Z", metadata.get(TikaCoreProperties.MODIFIED));
- assertEquals("1", metadata.get(Office.SLIDE_COUNT));
- assertEquals("3", metadata.get(Office.WORD_COUNT));
- assertEquals("Test extraction properties pptx", metadata.get(TikaCoreProperties.TITLE));
- assertEquals("true", metadata.get("custom:myCustomBoolean"));
- assertEquals("3", metadata.get("custom:myCustomNumber"));
- assertEquals("MyStringValue", metadata.get("custom:MyCustomString"));
- assertEquals("2010-12-30T22:00:00Z", metadata.get("custom:MyCustomDate"));
- assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
- }
-
- // TIKA-989:
- @Test
- public void testEmbeddedPDF() throws Exception {
- Metadata metadata = new Metadata();
- StringWriter sw = new StringWriter();
- SAXTransformerFactory factory = XMLReaderUtils.getSAXTransformerFactory();
- TransformerHandler handler = factory.newTransformerHandler();
- handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
- handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "no");
- handler.setResult(new StreamResult(sw));
-
- try (TikaInputStream tis = getResourceAsStream(
- "/test-documents/testWORD_embedded_pdf.docx")) {
- new OOXMLParser().parse(tis, handler, metadata, new ParseContext());
- }
- String xml = sw.toString();
- int i = xml.indexOf("Here is the pdf file:");
- int j = xml.indexOf("<div class=\"embedded\" id=\"rId5\"/>");
- int k = xml.indexOf("Bye Bye");
- int l = xml.indexOf("<div class=\"embedded\" id=\"rId6\"/>");
- int m = xml.indexOf("Bye for real.");
- assertTrue(i != -1);
- assertTrue(j != -1);
- assertTrue(k != -1);
- assertTrue(l != -1);
- assertTrue(m != -1);
- assertTrue(i < j);
- assertTrue(j < k);
- assertTrue(k < l);
- assertTrue(l < m);
- }
-
- // TIKA-997:
- @Test
- public void testEmbeddedZipInPPTX() throws Exception {
- String xml = getXML("test_embedded_zip.pptx").xml;
- int h = xml.indexOf("<div class=\"embedded\" id=\"slide1_rId3\" />");
- int i = xml.indexOf("Send me a note");
- int j = xml.indexOf("<div class=\"embedded\" id=\"slide2_rId4\" />");
- int k = xml.indexOf("<p>No title</p>");
- assertTrue(h != -1);
- assertTrue(i != -1);
- assertTrue(j != -1);
- assertTrue(k != -1);
- assertTrue(h < i);
- assertTrue(i < j);
- assertTrue(j < k);
- }
-
- // TIKA-1006
- @Test
- public void testWordNullStyle() throws Exception {
- String xml = getXML("testWORD_null_style.docx").xml;
- assertContains("Test av styrt dokument", xml);
- }
-
- /**
- * TIKA-1044 - Handle word documents where parts of the
- * text have no formatting or styles applied to them
- */
- @Test
- public void testNoFormat() throws Exception {
- ContentHandler handler = new BodyContentHandler();
- Metadata metadata = new Metadata();
-
- try (TikaInputStream tis = getResourceAsStream("/test-documents/testWORD_no_format.docx")) {
- new OOXMLParser().parse(tis, handler, metadata, new ParseContext());
- }
-
- String content = handler.toString();
- assertContains("This is a piece of text that causes an exception", content);
- }
-
- // TIKA-1005:
- @Test
- public void testTextInsideTextBox() throws Exception {
- String xml = getXML("testWORD_text_box.docx").xml;
- assertContains("This text is directly in the body of the document.", xml);
- assertContains("This text is inside of a text box in the body of the document.", xml);
- assertContains("This text is inside of a text box in the header of the document.", xml);
- assertContains("This text is inside of a text box in the footer of the document.", xml);
- }
-
- //TIKA-2807
- @Test
- public void testSDTInTextBox() throws Exception {
- String xml = getXML("testWORD_sdtInTextBox.docx").xml;
- assertContains("rich-text-content-control_inside-text-box", xml);
- assertContainsCount("inside-text", xml, 1);
- }
-
- //TIKA-2346
- @Test
- public void testTurningOffTextBoxExtraction() throws Exception {
- ParseContext pc = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setIncludeShapeBasedContent(false);
- pc.set(OfficeParserConfig.class, officeParserConfig);
- String xml = getXML("testWORD_text_box.docx", pc).xml;
- assertContains("This text is directly in the body of the document.", xml);
- assertNotContained("This text is inside of a text box in the body of the document.", xml);
- assertNotContained("This text is inside of a text box in the header of the document.", xml);
- assertNotContained("This text is inside of a text box in the footer of the document.", xml);
- }
-
- // TIKA-1032:
- @Test
- public void testEmbeddedPPTXTwoSlides() throws Exception {
- String xml = getXML("testPPT_embedded_two_slides.pptx").xml;
- assertContains("<div class=\"embedded\" id=\"slide1_rId7\" />", xml);
- assertContains("<div class=\"embedded\" id=\"slide2_rId7\" />", xml);
- }
-
- /**
- * Test for missing text described in
- * <a href="https://issues.apache.org/jira/browse/TIKA-1130">TIKA-1130</a>.
- * and TIKA-1317
- */
- @Test
- public void testMissingText() throws Exception {
- XMLResult xmlResult = getXML("testWORD_missing_text.docx");
- assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- xmlResult.metadata.get(Metadata.CONTENT_TYPE));
- assertContains("BigCompany", xmlResult.xml);
- assertContains("Seasoned", xmlResult.xml);
- assertContains("Rich_text_in_cell", xmlResult.xml);
-
- }
-
//TIKA-1100:
@Test
public void testExcelTextBox() throws Exception {
@@ -1011,49 +265,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertNotContained("autoshape", xml);
}
- //TIKA-792; with room for future missing bean tests
- @Test
- public void testWordMissingOOXMLBeans() throws Exception {
- //If a bean is missing, POI prints stack trace to stderr
- String[] fileNames = new String[]{"testWORD_missing_ooxml_bean1.docx",//TIKA-792
- };
- PrintStream origErr = System.err;
- for (String fileName : fileNames) {
- //grab stderr
- ByteArrayOutputStream errContent = new ByteArrayOutputStream();
- System.setErr(new PrintStream(errContent, true, UTF_8.name()));
- getXML(fileName);
-
- //return stderr
- System.setErr(origErr);
-
- String err = errContent.toString(UTF_8.name());
- assertTrue(err.isEmpty());
- }
- }
-
- //TIKA-817
- @Test
- public void testPPTXAutodate() throws Exception {
- //Following POI-52368, the stored date is extracted,
- //not the auto-generated date.
-
- XMLResult result = getXML("testPPT_autodate.pptx");
- assertContains("<p>Now</p>\n" + "<p>2011-12-19 10:20:04 AM</p>\n", result.xml);
-
- }
-
- @Test
- public void testDOCXThumbnail() throws Exception {
- String xml = getXML("testDOCX_Thumbnail.docx").xml;
- int a = xml.indexOf("This file contains a thumbnail");
- int b = xml.indexOf("<div class=\"embedded\" id=\"/docProps/thumbnail.emf\" />");
-
- assertTrue(a != -1);
- assertTrue(b != -1);
- assertTrue(a < b);
- }
-
@Test
public void testXLSXThumbnail() throws Exception {
String xml = getXML("testXLSX_Thumbnail.xlsx").xml;
@@ -1065,17 +276,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertTrue(a < b);
}
- @Test
- public void testPPTXThumbnail() throws Exception {
- String xml = getXML("testPPTX_Thumbnail.pptx").xml;
- int a = xml.indexOf(
- "<body><div class=\"slide-content\"><p>This file contains an embedded thumbnail");
- int b = xml.indexOf("<div class=\"embedded\" id=\"/docProps/thumbnail.jpeg\" />");
- assertTrue(a != -1);
- assertTrue(b != -1);
- assertTrue(a < b);
- }
-
@Test
public void testEncrypted() throws Exception {
Map<String, String> tests = new HashMap<>();
@@ -1116,78 +316,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
}
- @Test
- public void testDOCXParagraphNumbering() throws Exception {
- String xml = getXML("testWORD_numbered_list.docx").xml;
- assertContains("1) This", xml);
- assertContains("a) Is", xml);
- assertContains("i) A multi", xml);
- assertContains("ii) Level", xml);
- assertContains("1. Within cell 1", xml);
- assertContains("b. Cell b", xml);
- assertContains("iii) List", xml);
- assertContains("2) foo", xml);
- assertContains("ii) baz", xml);
- assertContains("ii) foo", xml);
- assertContains("II. bar", xml);
- assertContains("6. six", xml);
- assertContains("7. seven", xml);
- assertContains("a. seven a", xml);
- assertContains("e. seven e", xml);
- assertContains("2. A ii 2", xml);
- assertContains("3. page break list 3", xml);
- assertContains("Some-1-CrazyFormat Greek numbering with crazy format - alpha", xml);
- assertContains("1.1.1. 1.1.1", xml);
- assertContains("1.1. 1.2->1.1 //set the value", xml);
-
- //TODO: comment is not being extracted!
- //assertContains("add a list here", xml);
- }
-
- @Test
- public void testDOCXOverrideParagraphNumbering() throws Exception {
- String xml = getXML("testWORD_override_list_numbering.docx").xml;
-
- //Test 1
- assertContains("<p>1.1.1.1...1 1.1.1.1...1</p>", xml);
- assertContains("1st.2.3someText 1st.2.3someText", xml);
- assertContains("1st.2.2someOtherText.1 1st.2.2someOtherText.1", xml);
- assertContains("5th 5th", xml);
-
-
- //Test 2
- assertContains("1.a.I 1.a.I", xml);
- //test no reset because level 2 is not sufficient to reset
- assertContains("<p>1.b.III 1.b.III</p>", xml);
- //test restarted because of level 0's increment to 2
- assertContains("2.a.I 2.a.I", xml);
- //test handling of skipped level
- assertContains("<p>2.b 2.b</p>", xml);
-
- //Test 3
- assertContains("(1)) (1))", xml);
- //tests start level 1 at 17 and
- assertContains("2.17 2.17", xml);
- //tests that isLegal turns everything into decimal
- assertContains("2.18.2.1 2.18.2.1", xml);
- assertContains("<p>2 2</p>", xml);
-
- //Test4
- assertContains("<p>1 1</p>", xml);
- assertContains("<p>A A</p>", xml);
- assertContains("<p>B B</p>", xml);
- //this tests overrides
- assertContains("<p>C C</p>", xml);
- assertContains("<p>4 4</p>", xml);
-
- //Test5
- assertContains(">00 00", xml);
- assertContains(">01 01", xml);
- assertContains(">01. 01.", xml);
- assertContains(">01..1 01..1", xml);
- assertContains(">02 02", xml);
- }
-
@Test
public void testExcelHeaderAndFooterExtraction() throws Exception {
XMLResult xml = getXML("testEXCEL_headers_footers.xlsx");
@@ -1241,19 +369,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testMultiAuthorsManagers() throws Exception {
- XMLResult r = getXML("testWORD_multi_authors.docx");
- String[] authors = r.metadata.getValues(TikaCoreProperties.CREATOR);
- assertEquals(3, authors.length);
- assertEquals("author2", authors[1]);
-
- String[] managers = r.metadata.getValues(OfficeOpenXMLExtended.MANAGER);
- assertEquals(2, managers.length);
- assertEquals("manager1", managers[0]);
- assertEquals("manager2", managers[1]);
- }
-
@Test
public void testHyperlinksInXLSX() throws Exception {
String xml = getXML("testEXCEL_hyperlinks.xlsx").xml;
@@ -1268,16 +383,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testOrigSourcePath() throws Exception {
- Metadata embed1_zip_metadata = getRecursiveMetadata("test_recursive_embedded.docx").get(2);
- assertContains("C:\\Users\\tallison\\AppData\\Local\\Temp\\embed1.zip", Arrays.asList(
- embed1_zip_metadata.getValues(TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
- assertContains("C:\\Users\\tallison\\Desktop\\tmp\\New folder (2)\\embed1.zip",
- Arrays.asList(
- embed1_zip_metadata.getValues(TikaCoreProperties.ORIGINAL_RESOURCE_NAME)));
- }
-
@Test
public void testBigIntegersWGeneralFormat() throws Exception {
//TIKA-2025
@@ -1313,87 +418,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testBoldHyperlink() throws Exception {
- //TIKA-1255
- String xml = getXML("testWORD_boldHyperlink.docx").xml;
- xml = xml.replaceAll("\\s+", " ");
- assertContains("<a href=\"http://tika.apache.org/\">hyper <b>link</b></a>", xml);
- assertContains("<a href=\"http://tika.apache.org/\"><b>hyper</b> link</a>; bold", xml);
- }
-
- @Test
- public void testLongForIntExceptionInSummaryDetails() throws Exception {
- //TIKA-2055
- assertContains("bold", getXML("testWORD_totalTimeOutOfRange.docx").xml);
- }
-
- @Test
- public void testMacrosInDocm() throws Exception {
-
- //test default is "don't extract macros"
- for (Metadata metadata : getRecursiveMetadata("testWORD_macros.docm")) {
- if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
- fail("Shouldn't have extracted macros as default");
- }
- }
-
- //now test that they were extracted
- ParseContext context = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setExtractMacros(true);
- context.set(OfficeParserConfig.class, officeParserConfig);
-
-
- Metadata minExpected = new Metadata();
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
- minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
- minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
- TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
-
- assertContainsAtLeast(minExpected, getRecursiveMetadata("testWORD_macros.docm", context));
-
- //test configuring via config file
- Parser parser = TikaLoader.load(
- getConfigPath(OOXMLParserTest.class, "tika-config-dom-macros.json"))
- .loadAutoDetectParser();
- assertContainsAtLeast(minExpected,
- getRecursiveMetadata("testWORD_macros.docm", parser));
- }
-
- @Test
- public void testMacrosInPptm() throws Exception {
-
- //test default is "don't extract macros"
- for (Metadata metadata : getRecursiveMetadata("testPPT_macros.pptm")) {
- if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
- fail("Shouldn't have extracted macros as default");
- }
- }
-
- //now test that they were extracted
- ParseContext context = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setExtractMacros(true);
- context.set(OfficeParserConfig.class, officeParserConfig);
-
- Metadata minExpected = new Metadata();
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
- minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
- minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
- minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
- TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
-
- assertContainsAtLeast(minExpected, getRecursiveMetadata("testPPT_macros.pptm", context));
-
- //test configuring via config file
- Parser parser = TikaLoader.load(
- getConfigPath(OOXMLParserTest.class, "tika-config-dom-macros.json"))
- .loadAutoDetectParser();
- assertContainsAtLeast(minExpected, getRecursiveMetadata("testPPT_macros.pptm", parser));
- }
-
@Test
public void testMacroinXlsm() throws Exception {
@@ -1429,45 +453,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
getRecursiveMetadata("testEXCEL_macro.xlsm", parser));
}
- //@Test //use this for lightweight benchmarking to compare xwpf options
- public void testBatch() throws Exception {
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setUseSAXDocxExtractor(true);
- long started = System.currentTimeMillis();
- int ex = 0;
- for (int i = 0; i < 100; i++) {
- for (File f : getResourceAsFile("/test-documents").listFiles()) {
- if (!f.getName().endsWith(".docx")) {
- continue;
- }
- try (TikaInputStream tis = TikaInputStream.get(f.toPath())) {
- ParseContext parseContext = new ParseContext();
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
- //test only the extraction of the main docx content, not embedded docs
- parseContext.set(Parser.class, new EmptyParser());
- XMLResult r = getXML(tis, AUTO_DETECT_PARSER, new Metadata(), parseContext);
- } catch (Exception e) {
- ex++;
-
- }
- }
- }
- System.out.println("elapsed: " + (System.currentTimeMillis() - started) + " with " + ex +
- " exceptions");
- }
-
- @Test
- public void testInitializationViaConfig() throws Exception {
- //NOTE: this test relies on a bug in the DOM extractor that
- //is passing over the title information.
- //once we fix that, this test will no longer be meaningful!
- Parser p = TikaLoader.load(
- getConfigPath(OfficeParserTest.class, "tika-config-sax-docx.json"))
- .loadAutoDetectParser();
- XMLResult xml = getXML("testWORD_2006ml.docx", p, new Metadata());
- assertContains("engaging title", xml.xml);
- }
-
@Test
public void testExcelXLSB() throws Exception {
Detector detector = new DefaultDetector();
@@ -1599,16 +584,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertContains("SmartArt", getXML("testEXCEL_diagramData.xlsx").xml);
}
- @Test
- public void testDOCXDiagramData() throws Exception {
- assertContains("From here", getXML("testWORD_diagramData.docx").xml);
- }
-
- @Test
- public void testPPTXDiagramData() throws Exception {
- assertContains("President", getXML("testPPT_diagramData.pptx").xml);
- }
-
@Test
public void testXLSXChartData() throws Exception {
String xml = getXML("testEXCEL_charts.xlsx").xml;
@@ -1625,48 +600,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertNotContained("chartSpace", xml);
}
- @Test
- public void testDOCXChartData() throws Exception {
- String xml = getXML("testWORD_charts.docx").xml;
- assertContains("peach", xml);
- assertContains("March\tApril", xml);
- assertNotContained("chartSpace", xml);
- }
-
- @Test
- public void testPPTXChartData() throws Exception {
- String xml = getXML("testPPT_charts.pptx").xml;
- assertContains("peach", xml);
- assertContains("March\tApril", xml);
- assertNotContained("chartSpace", xml);
- }
-
- @Test
- public void testPPTXGroups() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_groups.pptx");
- assertEquals(3, metadataList.size());
- String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
- assertContains("WordArt1", content);
- assertContains("WordArt2", content);
- assertContainsCount("Ungrouped text box", content, 1);//should only be 1
- assertContains("Text box1", content);
- assertContains("Text box2", content);
- assertContains("Text box3", content);
- assertContains("Text box4", content);
- assertContains("Text box5", content);
-
-
- assertContains("href=\"http://tika.apache.org", content);
- assertContains("smart1", content);
- assertContains("MyTitle", content);
-
- assertEquals("/image1.jpg",
- metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
-
- assertEquals("/thumbnail.jpeg",
- metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
- }
-
@Test
public void testXLSXPhoneticStrings() throws Exception {
//This unit test and test file come from Apache POI 51519.xlsx
@@ -1693,35 +626,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testDOCXPhoneticStrings() throws Exception {
-
- assertContains("\u6771\u4EAC (\u3068\u3046\u304D\u3087\u3046)",
- getXML("testWORD_phonetic.docx").xml);
-
- OfficeParserConfig config = new OfficeParserConfig();
- config.setConcatenatePhoneticRuns(false);
- ParseContext parseContext = new ParseContext();
- parseContext.set(OfficeParserConfig.class, config);
- String xml = getXML("testWORD_phonetic.docx", parseContext).xml;
- assertContains("\u6771\u4EAC", xml);
- assertNotContained("\u3068", xml);
- }
-
- @Test
- public void testEmbeddedMedia() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_embeddedMP3.pptx");
- assertEquals(4, metadataList.size());
- assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
- metadataList.get(0).get(Metadata.CONTENT_TYPE));
- assertEquals("audio/mpeg", metadataList.get(1).get(Metadata.CONTENT_TYPE));
- assertEquals("image/png", metadataList.get(2).get(Metadata.CONTENT_TYPE));
- assertEquals("image/jpeg", metadataList.get(3).get(Metadata.CONTENT_TYPE));
- // Verify INTERNAL_PATH is set for embedded media
- assertNotNull(metadataList.get(1).get(TikaCoreProperties.INTERNAL_PATH));
- assertTrue(metadataList.get(1).get(TikaCoreProperties.INTERNAL_PATH).contains("/ppt/media/"));
- }
-
@Test
public void testEmbeddedXLSInOLEObject() throws Exception {
List<Metadata> metadataList = getRecursiveMetadata("testPPT_oleWorkbook.pptx");
@@ -1747,17 +651,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testTruncatedSAXDocx() throws Exception {
- ParseContext pc = new ParseContext();
- OfficeParserConfig c = new OfficeParserConfig();
- c.setUseSAXDocxExtractor(true);
- pc.set(OfficeParserConfig.class, c);
- assertThrows(TikaException.class, () -> {
- getRecursiveMetadata("testWORD_truncated.docx", pc);
- });
- }
-
@Test
public void testDateFormat() throws Exception {
Parser p = TikaLoader.load(
@@ -1810,16 +703,6 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
}
- @Test
- public void testFeatureExtraction() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_features.docx");
- Metadata m = metadataList.get(0);
- assertContains("Kyle Reese", Arrays.asList(m.getValues(Office.COMMENT_PERSONS)));
- assertEquals("true", m.get(Office.HAS_HIDDEN_TEXT));
- assertEquals("true", m.get(Office.HAS_TRACK_CHANGES));
- assertEquals("true", m.get(Office.HAS_COMMENTS));
- }
-
@Test
public void testNoRecordSizeOverflow() throws Exception {
//TIKA-4474 -- test: files (passed as stream) no longer have limit on record size as they are spooled
@@ -1827,42 +710,4 @@ public class OOXMLParserTest extends MultiThreadedTikaTest {
assertContains("Repetitive content pattern 3 for compression test row 1", content);
}
- /**
- * Test extraction of field-based hyperlinks using instrText/fldChar.
- * These are hyperlinks embedded as field codes rather than relationship-based hyperlinks.
- * Uses the DOM-based XWPFWordExtractorDecorator.
- */
- @Test
- public void testInstrTextHyperlink() throws Exception {
- String xml = getXML("testInstrLink.docx").xml;
- // The document contains a HYPERLINK field code in instrText
- assertContains("<a href=\"https://exmaple.com/file\">", xml);
- assertContains("Access Document(s)", xml);
- }
-
- /**
- * Test extraction of external reference field codes (INCLUDEPICTURE, INCLUDETEXT, IMPORT, LINK).
- * These can be used to hide malicious URLs in documents.
- */
- @Test
- public void testExternalRefFieldCodes() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testExternalRefs.docx");
- Metadata m = metadataList.get(0);
- // Check metadata flag is set
- assertEquals("true", m.get(Office.HAS_FIELD_HYPERLINKS));
-
- String xml = getXML("testExternalRefs.docx").xml;
- // Test INCLUDEPICTURE field code
- assertContains("class=\"external-ref-INCLUDEPICTURE\"", xml);
- assertContains("http://example.com/tracking.png", xml);
- // Test INCLUDETEXT field code
- assertContains("class=\"external-ref-INCLUDETEXT\"", xml);
- assertContains("http://example.org/payload.txt", xml);
- // Test IMPORT field code
- assertContains("class=\"external-ref-IMPORT\"", xml);
- assertContains("http://example.net/exploit.wmf", xml);
- // Test LINK field code
- assertContains("class=\"external-ref-LINK\"", xml);
- assertContains("http://test.invalid/cmd.docx", xml);
- }
}
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/OOXMLPptxDOMTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMTest.java
new file mode 100644
index 0000000000..8b9ca989d9
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.xml.sax.ContentHandler;
+
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.exception.EncryptedDocumentException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.DublinCore;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.PasswordProvider;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
+import org.apache.tika.sax.BodyContentHandler;
+
+/**
+ * Runs the shared PPTX tests using the DOM-based parser (default),
+ * plus DOM-specific tests.
+ */
+public class OOXMLPptxDOMTest extends AbstractOOXMLPptxTest {
+
+ @Override
+ ParseContext getParseContext() {
+ return new ParseContext();
+ }
+
+ @Test
+ public void testVariousPPTXDOMExtra() throws Exception {
+ // DOM version has extra assertions for DublinCore.SUBJECT and TikaCoreProperties.SUBJECT
+ Metadata metadata = new Metadata();
+ getXML("testPPT_various.pptx", metadata, getParseContext());
+
+ assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
+
+ assertContains("Keyword1 Keyword2",
+ Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
+ assertContains("Subject is here",
+ Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
+ }
+
+ @Test
+ public void testPowerPointCustomProperties() throws Exception {
+ Metadata metadata = new Metadata();
+
+ try (TikaInputStream tis =
+ getResourceAsStream("/test-documents/testPPT_custom_props.pptx")) {
+ ContentHandler handler = new BodyContentHandler(-1);
+ ParseContext context = new ParseContext();
+ context.set(Locale.class, Locale.US);
+ new OOXMLParser().parse(tis, handler, metadata, context);
+ }
+
+ assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ metadata.get(Metadata.CONTENT_TYPE));
+ assertEquals("JOUVIN ETIENNE", metadata.get(TikaCoreProperties.CREATOR));
+ assertEquals("EJ04325S", metadata.get(TikaCoreProperties.MODIFIER));
+ assertEquals("2011-08-22T13:30:53Z", metadata.get(TikaCoreProperties.CREATED));
+ assertEquals("2011-08-22T13:32:49Z", metadata.get(TikaCoreProperties.MODIFIED));
+ assertEquals("1", metadata.get(Office.SLIDE_COUNT));
+ assertEquals("3", metadata.get(Office.WORD_COUNT));
+ assertEquals("Test extraction properties pptx", metadata.get(TikaCoreProperties.TITLE));
+ assertEquals("true", metadata.get("custom:myCustomBoolean"));
+ assertEquals("3", metadata.get("custom:myCustomNumber"));
+ assertEquals("MyStringValue", metadata.get("custom:MyCustomString"));
+ assertEquals("2010-12-30T22:00:00Z", metadata.get("custom:MyCustomDate"));
+ assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
+ }
+
+ // TIKA-997:
+ @Test
+ public void testEmbeddedZipInPPTX() throws Exception {
+ String xml = getXML("test_embedded_zip.pptx", getParseContext()).xml;
+ int h = xml.indexOf("<div class=\"embedded\" id=\"slide1_rId3\" />");
+ int i = xml.indexOf("Send me a note");
+ int j = xml.indexOf("<div class=\"embedded\" id=\"slide2_rId4\" />");
+ int k = xml.indexOf("<p>No title</p>");
+ assertTrue(h != -1);
+ assertTrue(i != -1);
+ assertTrue(j != -1);
+ assertTrue(k != -1);
+ assertTrue(h < i);
+ assertTrue(i < j);
+ assertTrue(j < k);
+ }
+
+ // TIKA-1032:
+ @Test
+ public void testEmbeddedPPTXTwoSlides() throws Exception {
+ String xml = getXML("testPPT_embedded_two_slides.pptx", getParseContext()).xml;
+ assertContains("<div class=\"embedded\" id=\"slide1_rId7\" />", xml);
+ assertContains("<div class=\"embedded\" id=\"slide2_rId7\" />", xml);
+ }
+
+ @Test
+ public void testMacrosInPptm() throws Exception {
+
+ //test default is "don't extract macros"
+ for (Metadata metadata : getRecursiveMetadata("testPPT_macros.pptm", getParseContext())) {
+ if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
+ fail("Shouldn't have extracted macros as default");
+ }
+ }
+
+ //now test that they were extracted
+ ParseContext context = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setExtractMacros(true);
+ context.set(OfficeParserConfig.class, officeParserConfig);
+
+ Metadata minExpected = new Metadata();
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
+ minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
+ minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
+
+ assertContainsAtLeast(minExpected,
+ getRecursiveMetadata("testPPT_macros.pptm", context));
+
+ //test configuring via config file
+ Parser parser = TikaLoader.load(
+ getConfigPath(OOXMLParserTest.class, "tika-config-dom-macros.json"))
+ .loadAutoDetectParser();
+ assertContainsAtLeast(minExpected,
+ getRecursiveMetadata("testPPT_macros.pptm", parser));
+ }
+
+ @Test
+ public void testEncrypted() throws Exception {
+ Map<String, String> tests = new HashMap<>();
+ tests.put("testPPT_protected_passtika.pptx",
+ "This is an encrypted PowerPoint 2007 slide.");
+
+ PasswordProvider passwordProvider = metadata -> "tika";
+ ParseContext passwordContext = new ParseContext();
+ passwordContext.set(PasswordProvider.class, passwordProvider);
+
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ assertContains(e.getValue(), getXML(e.getKey(), passwordContext).xml);
+ }
+
+ //now try with no password
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ boolean exc = false;
+ try {
+ getXML(e.getKey(), getParseContext());
+ } catch (EncryptedDocumentException ex) {
+ exc = true;
+ }
+ assertTrue(exc);
+ }
+ }
+
+ @Test
+ public void testSkipHeaderFooter() throws Exception {
+ //now test turning off header/footer
+ OfficeParserConfig config = new OfficeParserConfig();
+ config.setIncludeHeadersAndFooters(false);
+ ParseContext context = new ParseContext();
+ context.set(OfficeParserConfig.class, config);
+ String xml = getXML("testPPT_various.pptx", context).xml;
+ assertNotContained("This is the header text", xml);
+ }
+
+ @Test
+ public void testEmbeddedMediaDOMExtra() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_embeddedMP3.pptx", getParseContext());
+ // Verify INTERNAL_PATH is set for embedded media (DOM-specific)
+ assertNotNull(metadataList.get(1).get(TikaCoreProperties.INTERNAL_PATH));
+ assertTrue(metadataList.get(1).get(TikaCoreProperties.INTERNAL_PATH)
+ .contains("/ppt/media/"));
+ }
+}
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/OOXMLPptxSAXTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXTest.java
new file mode 100644
index 0000000000..c9996d0b91
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXTest.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.ContentHandler;
+
+import org.apache.tika.config.loader.TikaLoader;
+import org.apache.tika.exception.EncryptedDocumentException;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.PasswordProvider;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
+import org.apache.tika.sax.BodyContentHandler;
+
+/**
+ * Runs the shared PPTX tests using the SAX-based streaming parser,
+ * plus SAX-specific tests.
+ */
+public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
+
+ @Override
+ ParseContext getParseContext() {
+ ParseContext parseContext = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setUseSAXPptxExtractor(true);
+ parseContext.set(OfficeParserConfig.class, officeParserConfig);
+ return parseContext;
+ }
+
+ @Test
+ public void basicTest() throws Exception {
+
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_various2.pptx", getParseContext());
+
+ assertEquals(15, metadataList.size(), "right number of attachments");
+
+ String mainContent = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+
+ assertContains("This slide is hidden", mainContent);//TODO: parameterize this
+
+ //basic content
+ assertContains("FirstBullet", mainContent);
+
+ //hyperlink
+ assertContains("<a href=\"http://tika.apache.org/\">tika_hyperlink</a>", mainContent);
+ //hyperlink in cell
+ assertContains("<a href=\"http://lucene.apache.org/\">lucene_hyperlink</a>",
+ mainContent);
+
+ //text box
+ assertContains("Slide2TextBox", mainContent);
+ assertContains("<td>R1c1</td>", mainContent);
+
+ //wordArt
+ assertContains("This is some WordART", mainContent);
+
+ //notes
+ assertContains("NotesForSlide2", mainContent);
+ assertContains("Notes for slide3", mainContent);
+ assertContains("NotesMasterHeader", mainContent);
+ assertContains("NotesMasterFooter", mainContent);
+ assertContains("NotesMasterPageNumber", mainContent);
+ assertContains("NotesWordArt", mainContent);
+ assertContains("NotesWordArtPage2", mainContent);
+ assertContains("NotesTableSlide2", mainContent);
+
+ //comments
+ assertContains(
+ "<p class=\"slide-comment\"><b>Timothy Allison (TA)</b>This is a reply to the " +
+ "initial comment</p>",
+ mainContent);
+
+ //HandoutMaster
+ assertContains("HandoutHeader1", mainContent);
+ assertContains("HandoutFooter", mainContent);
+ assertContains("HandoutDate", mainContent);
+ assertContains("TextBoxInHandOut", mainContent);
+
+ //text box in master
+ assertContains("MASTERTEXTBOX", mainContent);
+
+ //equation
+ assertContains("3/4", mainContent);
+
+ //make sure footer elements are in their own <p/>
+ assertContains("<p>12/16/2016</p>", mainContent);
+ assertContains("<p>8</p>", mainContent);
+
+ assertContains("<td>NotesTableSlide2", mainContent);
+
+ assertContains("MASTERFOOTERMSG", mainContent);
+
+ //should not include boilerplate from master
+ assertNotContained("Click to edit Master", mainContent);
+ assertNotContained("Second level", mainContent);
+ }
+
+ @Test
+ public void poiBug54916Test() throws Exception {
+ String xml = getXML("testPPTX_overlappingRelations.pptx", getParseContext()).xml;
+ assertContains("POI cannot read this", xml);
+ assertContains("Has a relationship to another slide", xml);
+ assertContains("can read this too", xml);
+ }
+
+ @Test
+ public void testPowerPointCustomProperties() throws Exception {
+ Metadata metadata = new Metadata();
+
+ ParseContext context = new ParseContext();
+ context.set(Locale.class, Locale.US);
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setUseSAXPptxExtractor(true);
+ context.set(OfficeParserConfig.class, officeParserConfig);
+
+ getXML("testPPT_custom_props.pptx", metadata, getParseContext());
+ assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ metadata.get(Metadata.CONTENT_TYPE));
+ assertEquals("JOUVIN ETIENNE", metadata.get(TikaCoreProperties.CREATOR));
+ assertEquals("EJ04325S", metadata.get(TikaCoreProperties.MODIFIER));
+ assertEquals("2011-08-22T13:30:53Z", metadata.get(TikaCoreProperties.CREATED));
+ assertEquals("2011-08-22T13:32:49Z", metadata.get(TikaCoreProperties.MODIFIED));
+ assertEquals("1", metadata.get(Office.SLIDE_COUNT));
+ assertEquals("3", metadata.get(Office.WORD_COUNT));
+ assertEquals("Test extraction properties pptx", metadata.get(TikaCoreProperties.TITLE));
+ assertEquals("true", metadata.get("custom:myCustomBoolean"));
+ assertEquals("3", metadata.get("custom:myCustomNumber"));
+ assertEquals("MyStringValue", metadata.get("custom:MyCustomString"));
+ assertEquals("2010-12-30T22:00:00Z", metadata.get("custom:MyCustomDate"));
+ assertEquals("2010-12-29T22:00:00Z", metadata.get("custom:myCustomSecondDate"));
+ }
+
+ // TIKA-997:
+ @Test
+ @Disabled("TODO: add in embedded file markup")
+ public void testEmbeddedZipInPPTX() throws Exception {
+ String xml = getXML("test_embedded_zip.pptx", getParseContext()).xml;
+ int h = xml.indexOf("<div class=\"embedded\" id=\"slide1_rId3\" />");
+ int i = xml.indexOf("Send me a note");
+ int j = xml.indexOf("<div class=\"embedded\" id=\"slide2_rId4\" />");
+ int k = xml.indexOf("<p>No title</p>");
+ assertTrue(h != -1);
+ assertTrue(i != -1);
+ assertTrue(j != -1);
+ assertTrue(k != -1);
+ assertTrue(h < i);
+ assertTrue(i < j);
+ assertTrue(j < k);
+ }
+
+ // TIKA-1032:
+ @Test
+ @Disabled("TODO: add in embedded file markup")
+ public void testEmbeddedPPTXTwoSlides() throws Exception {
+ String xml = getXML("testPPT_embedded_two_slides.pptx", getParseContext()).xml;
+ assertContains("<div class=\"embedded\" id=\"slide1_rId7\" />", xml);
+ assertContains("<div class=\"embedded\" id=\"slide2_rId7\" />", xml);
+ }
+
+ @Test
+ public void testMacrosInPptm() throws Exception {
+
+ Metadata parsedBy = new Metadata();
+ parsedBy.add(TikaCoreProperties.TIKA_PARSED_BY,
+ "org.apache.tika.parser.microsoft.ooxml.xslf.XSLFEventBasedPowerPointExtractor");
+
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_macros.pptm", getParseContext());
+
+ //test default is "don't extract macros"
+ for (Metadata metadata : metadataList) {
+ if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
+ fail("Shouldn't have extracted macros as default");
+ }
+ }
+
+ assertContainsAtLeast(parsedBy, metadataList);
+
+ //now test that they are extracted
+ ParseContext context = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setExtractMacros(true);
+ officeParserConfig.setUseSAXPptxExtractor(true);
+ context.set(OfficeParserConfig.class, officeParserConfig);
+
+ Metadata minExpected = new Metadata();
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Embolden()");
+ minExpected.add(TikaCoreProperties.TIKA_CONTENT.getName(), "Sub Italicize()");
+ minExpected.add(Metadata.CONTENT_TYPE, "text/x-vbasic");
+ minExpected.add(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+ TikaCoreProperties.EmbeddedResourceType.MACRO.toString());
+
+ metadataList = getRecursiveMetadata("testPPT_macros.pptm", context);
+
+ assertContainsAtLeast(minExpected, metadataList);
+ assertContainsAtLeast(parsedBy, metadataList);
+
+ //test configuring via config file
+ AutoDetectParser parser = (AutoDetectParser) TikaLoader.load(
+ getConfigPath(OOXMLPptxSAXTest.class, "tika-config-sax-macros.json"))
+ .loadAutoDetectParser();
+ metadataList = getRecursiveMetadata("testPPT_macros.pptm", parser);
+ assertContainsAtLeast(minExpected, metadataList);
+ assertContainsAtLeast(parsedBy, metadataList);
+ }
+
+ @Test
+ public void testEncrypted() throws Exception {
+ Map<String, String> tests = new HashMap<>();
+ tests.put("testPPT_protected_passtika.pptx",
+ "This is an encrypted PowerPoint 2007 slide.");
+
+ Metadata m = new Metadata();
+ PasswordProvider passwordProvider = metadata -> "tika";
+ ParseContext passwordContext = new ParseContext();
+ passwordContext.set(PasswordProvider.class, passwordProvider);
+ OfficeParserConfig opc = new OfficeParserConfig();
+ opc.setUseSAXPptxExtractor(true);
+ passwordContext.set(OfficeParserConfig.class, opc);
+
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ try (TikaInputStream tis = getResourceAsStream("/test-documents/" + e.getKey())) {
+ ContentHandler handler = new BodyContentHandler();
+ AUTO_DETECT_PARSER.parse(tis, handler, m, passwordContext);
+ assertContains(e.getValue(), handler.toString());
+ }
+ }
+
+ ParseContext context = new ParseContext();
+ //now try with no password
+ for (Map.Entry<String, String> e : tests.entrySet()) {
+ boolean exc = false;
+ try (TikaInputStream tis = getResourceAsStream("/test-documents/" + e.getKey())) {
+ ContentHandler handler = new BodyContentHandler();
+ AUTO_DETECT_PARSER.parse(tis, handler, m, context);
+ } catch (EncryptedDocumentException ex) {
+ exc = true;
+ }
+ assertTrue(exc);
+ }
+ }
+}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_2006ml.docx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_2006ml.docx
index 682a64ff27..11c2b59076 100644
Binary files a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_2006ml.docx and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testWORD_2006ml.docx differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxPackageTest.java
new file mode 100644
index 0000000000..26967ab18d
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxPackageTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.microsoft.ooxml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
+
+public abstract class AbstractOOXMLDocxPackageTest extends TikaTest {
+
+ abstract ParseContext getParseContext();
+
+ @Test
+ public void testAltFileMHTChunk() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testAltChunkMHT.docx", getParseContext());
+ assertEquals(3, metadataList.size());
+ assertContains("Example of a table",
+ metadataList.get(2).get(TikaCoreProperties.TIKA_CONTENT));
+ }
+
+ @Test
+ public void testAltFileHTMLChunk() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testAltChunkHTML.docx", getParseContext());
+ assertEquals(2, metadataList.size());
+ assertContains("Example of a table",
+ metadataList.get(1).get(TikaCoreProperties.TIKA_CONTENT));
+ }
+}
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/ParagraphProperties.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java
similarity index 51%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java
index 80c004bea2..8b844f1d28 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/ParagraphProperties.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java
@@ -16,40 +16,26 @@
*/
package org.apache.tika.parser.microsoft.ooxml;
+import java.util.List;
-public class ParagraphProperties {
+import org.junit.jupiter.api.Test;
- private String styleId;
- private int ilvl = -1;
- private int numId = -1;
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.parser.ParseContext;
- public String getStyleID() {
- return styleId;
- }
-
- public void setStyleID(String styleId) {
- this.styleId = styleId;
- }
-
- public void reset() {
- styleId = null;
- ilvl = -1;
- numId = -1;
- }
+public abstract class AbstractOOXMLPptxPackageTest extends TikaTest {
- public int getIlvl() {
- return ilvl;
- }
-
- public void setIlvl(int ilvl) {
- this.ilvl = ilvl;
- }
-
- public int getNumId() {
- return numId;
- }
+ abstract ParseContext getParseContext();
- public void setNumId(int numId) {
- this.numId = numId;
+ @Test
+ public void testEmbeddedPDFInPPTX() throws Exception {
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testPPT_EmbeddedPDF.pptx", getParseContext());
+ Metadata pdfMetadata1 = metadataList.get(4);
+ assertContains("Apache Tika", pdfMetadata1.get(TikaCoreProperties.TIKA_CONTENT));
+ Metadata pdfMetadata2 = metadataList.get(5);
+ assertContains("Hello World", pdfMetadata2.get(TikaCoreProperties.TIKA_CONTENT));
}
}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMPackageTest.java
similarity index 78%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMPackageTest.java
index ef76cc641e..3f6b0f5804 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMPackageTest.java
@@ -21,36 +21,29 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.List;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.apache.tika.TikaTest;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.microsoft.EMFParser;
-import org.apache.tika.parser.microsoft.OfficeParserConfig;
-public class SXWPFExtractorTest extends TikaTest {
-
- private ParseContext parseContext;
-
- @BeforeEach
- public void setUp() {
- parseContext = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setUseSAXDocxExtractor(true);
- officeParserConfig.setUseSAXPptxExtractor(true);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
+/**
+ * DOM-specific docx package tests.
+ */
+public class OOXMLDocxDOMPackageTest extends AbstractOOXMLDocxPackageTest {
+ @Override
+ ParseContext getParseContext() {
+ return new ParseContext();
}
+
@Test
- @Disabled("TODO -- implement TIKA-3968 for SXWPFExtractor")
public void testEMFAssociatedWithAttachments() throws Exception {
//TIKA-3968
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_EMFAndAttachments.docx", parseContext);
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_EMFAndAttachments.docx", getParseContext());
assertEquals("true", metadataList.get(1).get(EMFParser.EMF_ICON_ONLY));
assertEquals("true", metadataList.get(3).get(EMFParser.EMF_ICON_ONLY));
@@ -63,9 +56,12 @@ public class SXWPFExtractorTest extends TikaTest {
assertEquals("AcroExch.Document.DC", metadataList.get(4).get(Office.PROG_ID));
assertEquals("Word.Document.12", metadataList.get(6).get(Office.PROG_ID));
- assertEquals("TestText.txt", metadataList.get(2).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("TestPdf.pdf", metadataList.get(4).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("testWORD123.docx", metadataList.get(6).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("TestText.txt",
+ metadataList.get(2).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("TestPdf.pdf",
+ metadataList.get(4).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("testWORD123.docx",
+ metadataList.get(6).get(TikaCoreProperties.RESOURCE_NAME_KEY));
assertEquals("/TestText.txt",
metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
@@ -76,10 +72,8 @@ public class SXWPFExtractorTest extends TikaTest {
assertContains("This is Text File",
metadataList.get(2).get(TikaCoreProperties.TIKA_CONTENT));
-
assertContains("This is test PDF document for parser.",
metadataList.get(4).get(TikaCoreProperties.TIKA_CONTENT));
-
assertContains("This is test word document for parser.",
metadataList.get(6).get(TikaCoreProperties.TIKA_CONTENT));
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
similarity index 83%
rename from tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
rename to tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
index ef76cc641e..33712f21ba 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/SXWPFExtractorTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
@@ -21,11 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.List;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.apache.tika.TikaTest;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.TikaCoreProperties;
@@ -33,24 +30,25 @@ import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.microsoft.EMFParser;
import org.apache.tika.parser.microsoft.OfficeParserConfig;
-public class SXWPFExtractorTest extends TikaTest {
-
- private ParseContext parseContext;
+/**
+ * SAX-specific docx package tests.
+ */
+public class OOXMLDocxSAXPackageTest extends AbstractOOXMLDocxPackageTest {
- @BeforeEach
- public void setUp() {
- parseContext = new ParseContext();
+ @Override
+ ParseContext getParseContext() {
+ ParseContext parseContext = new ParseContext();
OfficeParserConfig officeParserConfig = new OfficeParserConfig();
officeParserConfig.setUseSAXDocxExtractor(true);
- officeParserConfig.setUseSAXPptxExtractor(true);
parseContext.set(OfficeParserConfig.class, officeParserConfig);
-
+ return parseContext;
}
+
@Test
- @Disabled("TODO -- implement TIKA-3968 for SXWPFExtractor")
public void testEMFAssociatedWithAttachments() throws Exception {
//TIKA-3968
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_EMFAndAttachments.docx", parseContext);
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testWORD_EMFAndAttachments.docx", getParseContext());
assertEquals("true", metadataList.get(1).get(EMFParser.EMF_ICON_ONLY));
assertEquals("true", metadataList.get(3).get(EMFParser.EMF_ICON_ONLY));
@@ -63,9 +61,12 @@ public class SXWPFExtractorTest extends TikaTest {
assertEquals("AcroExch.Document.DC", metadataList.get(4).get(Office.PROG_ID));
assertEquals("Word.Document.12", metadataList.get(6).get(Office.PROG_ID));
- assertEquals("TestText.txt", metadataList.get(2).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("TestPdf.pdf", metadataList.get(4).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("testWORD123.docx", metadataList.get(6).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("TestText.txt",
+ metadataList.get(2).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("TestPdf.pdf",
+ metadataList.get(4).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals("testWORD123.docx",
+ metadataList.get(6).get(TikaCoreProperties.RESOURCE_NAME_KEY));
assertEquals("/TestText.txt",
metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
@@ -76,10 +77,8 @@ public class SXWPFExtractorTest extends TikaTest {
assertContains("This is Text File",
metadataList.get(2).get(TikaCoreProperties.TIKA_CONTENT));
-
assertContains("This is test PDF document for parser.",
metadataList.get(4).get(TikaCoreProperties.TIKA_CONTENT));
-
assertContains("This is test word document for parser.",
metadataList.get(6).get(TikaCoreProperties.TIKA_CONTENT));
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
index 8ff5ccbb27..003f3a8ae4 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLParserTest.java
@@ -28,24 +28,12 @@ import org.junit.jupiter.api.Test;
import org.apache.tika.TikaTest;
import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.Office;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
-import org.apache.tika.parser.microsoft.EMFParser;
-import org.apache.tika.parser.microsoft.OfficeParserConfig;
public class OOXMLParserTest extends TikaTest {
- @Test
- public void testEmbeddedPDFInPPTX() throws Exception {
- List<Metadata> metadataList = getRecursiveMetadata("testPPT_EmbeddedPDF.pptx");
- Metadata pdfMetadata1 = metadataList.get(4);
- assertContains("Apache Tika", pdfMetadata1.get(TikaCoreProperties.TIKA_CONTENT));
- Metadata pdfMetadata2 = metadataList.get(5);
- assertContains("Hello World", pdfMetadata2.get(TikaCoreProperties.TIKA_CONTENT));
- }
-
@Test
public void testEmbeddedPDFInXLSX() throws Exception {
List<Metadata> metadataList = getRecursiveMetadata("testExcel_embeddedPDF.xlsx");
@@ -53,105 +41,21 @@ public class OOXMLParserTest extends TikaTest {
assertContains("Hello World", pdfMetadata.get(TikaCoreProperties.TIKA_CONTENT));
}
- @Test
- public void testEmbeddedPDFInStreamingPPTX() throws Exception {
- ParseContext parseContext = new ParseContext();
- OfficeParserConfig officeParserConfig = new OfficeParserConfig();
- officeParserConfig.setUseSAXPptxExtractor(true);
- parseContext.set(OfficeParserConfig.class, officeParserConfig);
-
- List<Metadata> metadataList =
- getRecursiveMetadata("testPPT_EmbeddedPDF.pptx", parseContext);
- Metadata pdfMetadata1 = metadataList.get(4);
- assertContains("Apache Tika", pdfMetadata1.get(TikaCoreProperties.TIKA_CONTENT));
- Metadata pdfMetadata2 = metadataList.get(5);
- assertContains("Hello World", pdfMetadata2.get(TikaCoreProperties.TIKA_CONTENT));
- }
-
- @Test
- public void testEMFAssociatedWithAttachments() throws Exception {
- //TIKA-3968
- List<Metadata> metadataList = getRecursiveMetadata("testWORD_EMFAndAttachments.docx");
-
- assertEquals("true", metadataList.get(1).get(EMFParser.EMF_ICON_ONLY));
- assertEquals("true", metadataList.get(3).get(EMFParser.EMF_ICON_ONLY));
- assertEquals("true", metadataList.get(5).get(EMFParser.EMF_ICON_ONLY));
- assertEquals("TestText.txt", metadataList.get(1).get(EMFParser.EMF_ICON_STRING));
- assertEquals("TestPdf.pdf", metadataList.get(3).get(EMFParser.EMF_ICON_STRING));
- assertEquals("testWORD123.docx", metadataList.get(5).get(EMFParser.EMF_ICON_STRING));
-
- assertNull(metadataList.get(2).get(Office.PROG_ID));
- assertEquals("AcroExch.Document.DC", metadataList.get(4).get(Office.PROG_ID));
- assertEquals("Word.Document.12", metadataList.get(6).get(Office.PROG_ID));
-
- assertEquals("TestText.txt", metadataList.get(2).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("TestPdf.pdf", metadataList.get(4).get(TikaCoreProperties.RESOURCE_NAME_KEY));
- assertEquals("testWORD123.docx", metadataList.get(6).get(TikaCoreProperties.RESOURCE_NAME_KEY));
-
- assertEquals("/TestText.txt",
- metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
- assertEquals("/TestPdf.pdf",
- metadataList.get(4).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
- assertEquals("/testWORD123.docx",
- metadataList.get(6).get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
-
- assertContains("This is Text File",
- metadataList.get(2).get(TikaCoreProperties.TIKA_CONTENT));
-
- assertContains("This is test PDF document for parser.",
- metadataList.get(4).get(TikaCoreProperties.TIKA_CONTENT));
-
- assertContains("This is test word document for parser.",
- metadataList.get(6).get(TikaCoreProperties.TIKA_CONTENT));
-
- assertEquals(TikaCoreProperties.EmbeddedResourceType.ATTACHMENT.name(),
- metadataList.get(2).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
- assertEquals(TikaCoreProperties.EmbeddedResourceType.ATTACHMENT.name(),
- metadataList.get(4).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
- assertEquals(TikaCoreProperties.EmbeddedResourceType.ATTACHMENT.name(),
- metadataList.get(6).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
-
- assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.name(),
- metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
- assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.name(),
- metadataList.get(3).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
- assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.name(),
- metadataList.get(5).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
- }
-
@Disabled("TODO figure out why this doesn't work")
- @Test//(expected = org.apache.tika.exception.TikaException.class)
+ @Test
public void testCorruptedZip() throws Exception {
//TIKA_2446
getRecursiveMetadata("testZIP_corrupted_oom.zip");
}
- @Test
- public void testAltFileMHTChunk() throws Exception {
- //test file with permission from:
- // https://github.com/jgm/pandoc/files/1290782/Sample_DOCX_using_MHT_container.docx
- List<Metadata> metadataList = getRecursiveMetadata("testAltChunkMHT.docx");
- assertEquals(3, metadataList.size());
- assertContains("Example of a table",
- metadataList.get(2).get(TikaCoreProperties.TIKA_CONTENT));
- }
-
- @Test
- public void testAltFileHTMLChunk() throws Exception {
- //test file with permission from:
- // https://github.com/jgm/pandoc/files/1290782/Sample_DOCX_using_HTML_container.docx
- List<Metadata> metadataList = getRecursiveMetadata("testAltChunkHTML.docx");
- assertEquals(2, metadataList.size());
- assertContains("Example of a table",
- metadataList.get(1).get(TikaCoreProperties.TIKA_CONTENT));
- }
-
@Test
public void testDigestTranslator() throws Exception {
- TikaLoader loader = TikaLoader.load(getConfigPath(OOXMLParserTest.class, "tika-config-digests.json"));
+ TikaLoader loader = TikaLoader.load(
+ getConfigPath(OOXMLParserTest.class, "tika-config-digests.json"));
Parser parser = loader.loadAutoDetectParser();
ParseContext parseContext = loader.loadParseContext();
- List<Metadata> metadataList = getRecursiveMetadata("testMSChart-govdocs-428996.pptx", parser, parseContext);
+ List<Metadata> metadataList =
+ getRecursiveMetadata("testMSChart-govdocs-428996.pptx", parser, parseContext);
assertEquals(4, metadataList.size());
for (Metadata m : metadataList) {
assertNotNull(m.get("X-TIKA:digest:SHA256:BASE32"));
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/xwpf/XWPFNumberingShim.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java
similarity index 60%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java
index f563223964..007e6c8531 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java
@@ -14,23 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.tika.parser.microsoft.ooxml.xwpf;
+package org.apache.tika.parser.microsoft.ooxml;
-import java.io.IOException;
+import org.apache.tika.parser.ParseContext;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
-import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
+public class OOXMLPptxDOMPackageTest extends AbstractOOXMLPptxPackageTest {
-/**
- * Stub class of POI's XWPFNumbering because onDocumentRead() is protected
- */
-public class XWPFNumberingShim extends XWPFNumbering {
-
- public XWPFNumberingShim(PackagePart part) throws IOException, OpenXML4JException {
- super(part);
- onDocumentRead();
+ @Override
+ ParseContext getParseContext() {
+ return new ParseContext();
}
-
-
}
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/xwpf/XWPFNumberingShim.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
similarity index 58%
copy from tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
copy to tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
index f563223964..26b217b19e 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xwpf/XWPFNumberingShim.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
@@ -14,23 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.tika.parser.microsoft.ooxml.xwpf;
+package org.apache.tika.parser.microsoft.ooxml;
-import java.io.IOException;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.OfficeParserConfig;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
-import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.xwpf.usermodel.XWPFNumbering;
+public class OOXMLPptxSAXPackageTest extends AbstractOOXMLPptxPackageTest {
-/**
- * Stub class of POI's XWPFNumbering because onDocumentRead() is protected
- */
-public class XWPFNumberingShim extends XWPFNumbering {
-
- public XWPFNumberingShim(PackagePart part) throws IOException, OpenXML4JException {
- super(part);
- onDocumentRead();
+ @Override
+ ParseContext getParseContext() {
+ ParseContext parseContext = new ParseContext();
+ OfficeParserConfig officeParserConfig = new OfficeParserConfig();
+ officeParserConfig.setUseSAXPptxExtractor(true);
+ parseContext.set(OfficeParserConfig.class, officeParserConfig);
+ return parseContext;
}
-
-
}