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/04/09 11:38:12 UTC

(tika) branch main updated: TIKA-4707 -- rm dom parsers for docx/pptx (#2747)

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 8501894c11 TIKA-4707 -- rm dom parsers for docx/pptx (#2747)
8501894c11 is described below

commit 8501894c11bf9ddc748e351de3c3aa6e7751295d
Author: Tim Allison <ta...@apache.org>
AuthorDate: Thu Apr 9 07:38:06 2026 -0400

    TIKA-4707 -- rm dom parsers for docx/pptx (#2747)
---
 .../tika-parser-microsoft-module/pom.xml           |   6 +
 .../tika/parser/microsoft/OfficeParserConfig.java  |  37 --
 .../microsoft/ooxml/AbstractOOXMLExtractor.java    |  36 +-
 .../parser/microsoft/ooxml/MetadataExtractor.java  | 279 +-------
 .../parser/microsoft/ooxml/OOXMLExtractor.java     |  15 -
 .../microsoft/ooxml/OOXMLExtractorFactory.java     | 239 +++----
 .../ooxml/POIXMLTextExtractorDecorator.java        |  51 --
 .../microsoft/ooxml/SAXBasedMetadataExtractor.java |   1 -
 .../ooxml/SXSLFPowerPointExtractorDecorator.java   |  11 +-
 .../ooxml/SXWPFWordExtractorDecorator.java         |  10 +-
 .../microsoft/ooxml/TikaXSSFBCommentsTable.java    | 137 ++++
 .../ooxml/TikaXSSFBSharedStringsTable.java         | 159 +++++
 .../microsoft/ooxml/VSDXExtractorDecorator.java    | 174 +++++
 .../ooxml/XSLFPowerPointExtractorDecorator.java    | 412 ------------
 .../ooxml/XSSFBExcelExtractorDecorator.java        |  98 +--
 .../ooxml/XSSFExcelExtractorDecorator.java         |  24 +-
 .../ooxml/XWPFWordExtractorDecorator.java          | 734 ---------------------
 .../microsoft/ooxml/xps/XPSExtractorDecorator.java |  16 +-
 .../microsoft/ooxml/xps/XPSTextExtractor.java      |  89 ---
 .../xslf/XSLFEventBasedPowerPointExtractor.java    | 212 +-----
 .../ooxml/xwpf/XWPFEventBasedWordExtractor.java    | 386 +----------
 .../microsoft/ooxml/AbstractOOXMLDocxTest.java     | 601 -----------------
 .../microsoft/ooxml/AbstractOOXMLPptxTest.java     | 363 ----------
 .../parser/microsoft/ooxml/OOXMLDocxDOMTest.java   | 133 ----
 .../parser/microsoft/ooxml/OOXMLDocxSAXTest.java   | 505 ++++++++++++--
 .../parser/microsoft/ooxml/OOXMLPptxDOMTest.java   | 204 ------
 .../parser/microsoft/ooxml/OOXMLPptxSAXTest.java   | 341 ++++++++--
 .../parser/microsoft/ooxml/VSDXParserTest.java     |  62 ++
 .../resources/configs/tika-config-sax-docx.json    |   7 +-
 .../resources/configs/tika-config-sax-macros.json  |   4 +-
 .../resources/test-documents/testVISIO_60489.vsdx  | Bin 0 -> 44426 bytes
 .../resources/test-documents/testVISIO_text.vsdx   | Bin 0 -> 22343 bytes
 .../ooxml/AbstractOOXMLDocxPackageTest.java        |  51 --
 .../ooxml/AbstractOOXMLPptxPackageTest.java        |  41 --
 .../microsoft/ooxml/OOXMLDocxDOMPackageTest.java   |  94 ---
 .../microsoft/ooxml/OOXMLDocxSAXPackageTest.java   |  32 +-
 .../microsoft/ooxml/OOXMLPptxDOMPackageTest.java   |  27 -
 .../microsoft/ooxml/OOXMLPptxSAXPackageTest.java   |  26 +-
 38 files changed, 1512 insertions(+), 4105 deletions(-)

diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/pom.xml b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/pom.xml
index a3c7e1f1c8..4231df7044 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/pom.xml
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/pom.xml
@@ -93,6 +93,12 @@
     <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi-ooxml</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.poi</groupId>
+          <artifactId>poi-ooxml-lite</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
     <!-- needed by jackcess -->
     <dependency>
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 363b0a0773..bb2ff73af5 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
@@ -32,9 +32,6 @@ public class OfficeParserConfig implements Serializable {
     private boolean includeSlideMasterContent = true;
     private boolean concatenatePhoneticRuns = true;
 
-    private boolean useSAXDocxExtractor = false;
-    private boolean useSAXPptxExtractor = false;
-
     private boolean preferAlternateContentChoice = true;
 
     private boolean writeSelectHeadersInBody = false;
@@ -142,40 +139,6 @@ public class OfficeParserConfig implements Serializable {
         this.includeHeadersAndFooters = includeHeadersAndFooters;
     }
 
-    public boolean isUseSAXDocxExtractor() {
-        return useSAXDocxExtractor;
-    }
-
-    /**
-     * Use the experimental SAX-based streaming DOCX parser?
-     * If set to <code>false</code>, the classic parser will be used; if <code>true</code>,
-     * the new experimental parser will be used.
-     * <p/>
-     * Default: <code>false</code> (classic DOM parser)
-     *
-     * @param useSAXDocxExtractor
-     */
-    public void setUseSAXDocxExtractor(boolean useSAXDocxExtractor) {
-        this.useSAXDocxExtractor = useSAXDocxExtractor;
-    }
-
-    public boolean isUseSAXPptxExtractor() {
-        return useSAXPptxExtractor;
-    }
-
-    /**
-     * Use the experimental SAX-based streaming DOCX parser?
-     * If set to <code>false</code>, the classic parser will be used; if <code>true</code>,
-     * the new experimental parser will be used.
-     * <p/>
-     * Default: <code>false</code> (classic DOM parser)
-     *
-     * @param useSAXPptxExtractor
-     */
-    public void setUseSAXPptxExtractor(boolean useSAXPptxExtractor) {
-        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
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/AbstractOOXMLExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLExtractor.java
index 6beef7c1c4..c686414ed1 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLExtractor.java
@@ -30,8 +30,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackagePart;
@@ -45,7 +43,6 @@ import org.apache.poi.poifs.filesystem.Ole10Native;
 import org.apache.poi.poifs.filesystem.Ole10NativeException;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 import org.apache.poi.xssf.usermodel.XSSFRelation;
-import org.apache.poi.xwpf.usermodel.XWPFRelation;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
@@ -93,9 +90,14 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
     static final String RELATION_ALTERNATE_FORMAT_CHUNK =
             "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk";
 
+    private static final String PACK_OBJECT_REL_TYPE =
+            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
+    private static final String OLE_OBJECT_REL_TYPE =
+            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject";
+
     protected static final String[] EMBEDDED_RELATIONSHIPS =
             new String[]{RELATION_AUDIO, PackageRelationshipTypes.IMAGE_PART,
-                    POIXMLDocument.PACK_OBJECT_REL_TYPE, PackageRelationshipTypes.CORE_DOCUMENT,
+                    PACK_OBJECT_REL_TYPE, PackageRelationshipTypes.CORE_DOCUMENT,
                     RELATION_DIAGRAM_DATA};
     private static final String TYPE_OLE_OBJECT =
             "application/vnd.openxmlformats-officedocument.oleObject";
@@ -104,11 +106,11 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
     private final EmbeddedDocumentExtractor embeddedExtractor;
     private final ParseContext context;
     protected OfficeParserConfig config;
-    protected POIXMLTextExtractor extractor;
+    protected OPCPackage opcPackage;
 
-    public AbstractOOXMLExtractor(ParseContext context, POIXMLTextExtractor extractor) {
+    public AbstractOOXMLExtractor(ParseContext context, OPCPackage opcPackage) {
         this.context = context;
-        this.extractor = extractor;
+        this.opcPackage = opcPackage;
         embeddedExtractor = EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
 
         // This has already been set by OOXMLParser's call to configure()
@@ -116,18 +118,11 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
         this.config = context.get(OfficeParserConfig.class);
     }
 
-    /**
-     * @see org.apache.tika.parser.microsoft.ooxml.OOXMLExtractor#getDocument()
-     */
-    public POIXMLDocument getDocument() {
-        return (POIXMLDocument) extractor.getDocument();
-    }
-
     /**
      * @see org.apache.tika.parser.microsoft.ooxml.OOXMLExtractor#getMetadataExtractor()
      */
     public MetadataExtractor getMetadataExtractor() {
-        return new MetadataExtractor(extractor);
+        return new SAXBasedMetadataExtractor(opcPackage, context);
     }
 
     ParseContext getParseContext() {
@@ -173,7 +168,6 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
 
     private void handleThumbnail(ContentHandler handler, Metadata metadata) throws SAXException {
         try {
-            OPCPackage opcPackage = extractor.getPackage();
             for (PackageRelationship rel : opcPackage
                     .getRelationshipsByType(PackageRelationshipTypes.THUMBNAIL)) {
                 PackagePart tPart = opcPackage.getPart(rel);
@@ -274,7 +268,7 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
         if (rel.getTargetMode() != TargetMode.INTERNAL) {
             // External target - emit as external reference for security analysis
             String type = rel.getRelationshipType();
-            if (POIXMLDocument.OLE_OBJECT_REL_TYPE.equals(type)) {
+            if (OLE_OBJECT_REL_TYPE.equals(type)) {
                 emitExternalRef(xhtml, "externalOleObject", targetURI.toString());
                 parentMetadata.set(Office.HAS_EXTERNAL_OLE_OBJECTS, true);
             } else if (PackageRelationshipTypes.IMAGE_PART.equals(type)) {
@@ -293,7 +287,7 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
         }
         EmbeddedPartMetadata embeddedPartMetadata = embeddedPartMetadataMap.get(rel.getId());
         String type = rel.getRelationshipType();
-        if (POIXMLDocument.OLE_OBJECT_REL_TYPE.equals(type) &&
+        if (OLE_OBJECT_REL_TYPE.equals(type) &&
                 TYPE_OLE_OBJECT.equals(target.getContentType())) {
             handleEmbeddedOLE(target, xhtml, sourceDesc + rel.getId(), parentMetadata,
                     embeddedPartMetadata);
@@ -308,8 +302,8 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
             }
         } else if (RELATION_MEDIA.equals(type) || RELATION_VIDEO.equals(type) ||
                 RELATION_AUDIO.equals(type) ||
-                POIXMLDocument.PACK_OBJECT_REL_TYPE.equals(type) ||
-                POIXMLDocument.OLE_OBJECT_REL_TYPE.equals(type)) {
+                PACK_OBJECT_REL_TYPE.equals(type) ||
+                OLE_OBJECT_REL_TYPE.equals(type)) {
             handleEmbeddedFile(target, xhtml, sourceDesc + rel.getId(),
                     embeddedPartMetadata,
                     TikaCoreProperties.EmbeddedResourceType.ATTACHMENT);
@@ -566,7 +560,7 @@ public abstract class AbstractOOXMLExtractor implements OOXMLExtractor {
         Map<String, String> linkedRelationships = new HashMap<>();
         try {
             PackageRelationshipCollection prc =
-                    bodyPart.getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation());
+                    bodyPart.getRelationshipsByType(PackageRelationshipTypes.HYPERLINK_PART);
             for (int i = 0; i < prc.size(); i++) {
                 PackageRelationship pr = prc.getRelationship(i);
                 if (pr == 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/MetadataExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/MetadataExtractor.java
index 97efe3e186..f983872395 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/MetadataExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/MetadataExtractor.java
@@ -16,288 +16,15 @@
  */
 package org.apache.tika.parser.microsoft.ooxml;
 
-import java.math.BigDecimal;
-import java.util.Date;
-import java.util.Optional;
-
-import org.apache.poi.ooxml.POIXMLProperties;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
-import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
-import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
-import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException;
-import org.openxmlformats.schemas.officeDocument.x2006.customProperties.CTProperty;
-import org.openxmlformats.schemas.officeDocument.x2006.extendedProperties.CTProperties;
-
 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.microsoft.SummaryExtractor;
-import org.apache.tika.parser.microsoft.ooxml.xps.XPSTextExtractor;
-import org.apache.tika.parser.microsoft.ooxml.xslf.XSLFEventBasedPowerPointExtractor;
-import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor;
 
 /**
- * OOXML metadata extractor.
- * <p/>
- * Currently POI doesn't support metadata extraction for OOXML.
+ * OOXML metadata extractor base class.
  *
  * @see OOXMLExtractor#getMetadataExtractor()
  */
-public class MetadataExtractor {
-
-    private final POIXMLTextExtractor extractor;
-
-    public MetadataExtractor(POIXMLTextExtractor extractor) {
-        this.extractor = extractor;
-    }
-
-    public void extract(Metadata metadata) throws TikaException {
-        if (extractor.getDocument() != null ||
-                ((extractor instanceof XSSFEventBasedExcelExtractor ||
-                        extractor instanceof XWPFEventBasedWordExtractor ||
-                        extractor instanceof XSLFEventBasedPowerPointExtractor ||
-                        extractor instanceof XPSTextExtractor) && extractor.getPackage() != null)) {
-            extractMetadata(extractor.getCoreProperties(), metadata);
-            extractMetadata(extractor.getExtendedProperties(), metadata);
-            extractMetadata(extractor.getCustomProperties(), metadata);
-        }
-    }
-
-    private void extractMetadata(POIXMLProperties.CoreProperties properties, Metadata metadata) {
-        PackagePropertiesPart propsHolder = properties.getUnderlyingProperties();
-
-        setProperty(metadata, OfficeOpenXMLCore.CATEGORY, propsHolder.getCategoryProperty());
-        setProperty(metadata, OfficeOpenXMLCore.CONTENT_STATUS,
-                propsHolder.getContentStatusProperty());
-        setProperty(metadata, TikaCoreProperties.CREATED, propsHolder.getCreatedProperty());
-        addMultiProperty(metadata, TikaCoreProperties.CREATOR, propsHolder.getCreatorProperty());
-        setProperty(metadata, TikaCoreProperties.DESCRIPTION, propsHolder.getDescriptionProperty());
-        setProperty(metadata, TikaCoreProperties.IDENTIFIER, propsHolder.getIdentifierProperty());
-        addProperty(metadata, DublinCore.SUBJECT, propsHolder.getSubjectProperty());
-        addProperty(metadata, Office.KEYWORDS, propsHolder.getKeywordsProperty());
-        setProperty(metadata, TikaCoreProperties.LANGUAGE, propsHolder.getLanguageProperty());
-        setProperty(metadata, TikaCoreProperties.MODIFIER, propsHolder.getLastModifiedByProperty());
-        setProperty(metadata, TikaCoreProperties.PRINT_DATE, propsHolder.getLastPrintedProperty());
-        setProperty(metadata, TikaCoreProperties.MODIFIED, propsHolder.getModifiedProperty());
-        setProperty(metadata, OfficeOpenXMLCore.REVISION, propsHolder.getRevisionProperty());
-
-        setProperty(metadata, TikaCoreProperties.TITLE, propsHolder.getTitleProperty());
-        setProperty(metadata, OfficeOpenXMLCore.VERSION, propsHolder.getVersionProperty());
-
-    }
-
-    private void extractMetadata(POIXMLProperties.ExtendedProperties properties,
-                                 Metadata metadata) {
-        CTProperties propsHolder = properties.getUnderlyingProperties();
-
-        //TIKA-2055, some ooxml files can include unsigned int/long values
-        //which cause this exception.
-        //For now, catch it and record as '0' because
-        //Word converts to '0' on save.
-        int totalTime = 0;
-        try {
-            totalTime = propsHolder.getTotalTime();
-        } catch (XmlValueOutOfRangeException e) {
-            //swallow for now
-        }
-        setProperty(metadata, OfficeOpenXMLExtended.APPLICATION, propsHolder.getApplication());
-        setProperty(metadata, OfficeOpenXMLExtended.APP_VERSION, propsHolder.getAppVersion());
-        setProperty(metadata, TikaCoreProperties.PUBLISHER, propsHolder.getCompany());
-        setProperty(metadata, OfficeOpenXMLExtended.COMPANY, propsHolder.getCompany());
-        SummaryExtractor
-                .addMulti(metadata, OfficeOpenXMLExtended.MANAGER, propsHolder.getManager());
-        setProperty(metadata, OfficeOpenXMLExtended.NOTES, propsHolder.getNotes());
-        setProperty(metadata, OfficeOpenXMLExtended.PRESENTATION_FORMAT,
-                propsHolder.getPresentationFormat());
-        setProperty(metadata, OfficeOpenXMLExtended.TEMPLATE, propsHolder.getTemplate());
-        setProperty(metadata, OfficeOpenXMLExtended.TOTAL_TIME, totalTime);
-        int docSecurityFlag = propsHolder.getDocSecurity();
-        setProperty(metadata, OfficeOpenXMLExtended.DOC_SECURITY, docSecurityFlag);
-        setProperty(metadata, OfficeOpenXMLExtended.DOC_SECURITY_STRING,
-                getDocSecurityString(docSecurityFlag));
-        if (propsHolder.getPages() > 0) {
-            metadata.set(PagedText.N_PAGES, propsHolder.getPages());
-        } else if (propsHolder.getSlides() > 0) {
-            metadata.set(PagedText.N_PAGES, propsHolder.getSlides());
-        }
-
-        // Process the document statistics
-        setProperty(metadata, Office.PAGE_COUNT, propsHolder.getPages());
-        setProperty(metadata, Office.SLIDE_COUNT, propsHolder.getSlides());
-        setProperty(metadata, Office.PARAGRAPH_COUNT, propsHolder.getParagraphs());
-        setProperty(metadata, Office.LINE_COUNT, propsHolder.getLines());
-        setProperty(metadata, Office.WORD_COUNT, propsHolder.getWords());
-        setProperty(metadata, Office.CHARACTER_COUNT, propsHolder.getCharacters());
-        setProperty(metadata, Office.CHARACTER_COUNT_WITH_SPACES,
-                propsHolder.getCharactersWithSpaces());
-    }
-
-    private String getDocSecurityString(int docSecurityFlag) {
-        //mappings from: https://exiftool.org/TagNames/OOXML.html and
-        //https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.extendedproperties.documentsecurity?view=openxml-2.8.1
-        switch (docSecurityFlag) {
-            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;
-        }
-    }
-
-    private void extractMetadata(POIXMLProperties.CustomProperties properties, Metadata metadata) {
-        org.openxmlformats.schemas.officeDocument.x2006.customProperties.CTProperties props =
-                properties.getUnderlyingProperties();
-        for (int i = 0; i < props.sizeOfPropertyArray(); i++) {
-            CTProperty property = props.getPropertyArray(i);
-            String val = null;
-            Date date = null;
-
-            if (property.isSetLpwstr()) {
-                val = property.getLpwstr();
-            } else if (property.isSetLpstr()) {
-                val = property.getLpstr();
-            } else if (property.isSetDate()) {
-                date = property.getDate().getTime();
-            } else if (property.isSetFiletime()) {
-                date = property.getFiletime().getTime();
-            } else if (property.isSetBool()) {
-                val = Boolean.toString(property.getBool());
-            }
-
-            // Integers
-            else if (property.isSetI1()) {
-                val = Integer.toString(property.getI1());
-            } else if (property.isSetI2()) {
-                val = Integer.toString(property.getI2());
-            } else if (property.isSetI4()) {
-                val = Integer.toString(property.getI4());
-            } else if (property.isSetI8()) {
-                val = Long.toString(property.getI8());
-            } else if (property.isSetInt()) {
-                val = Integer.toString(property.getInt());
-            }
-
-            // Unsigned Integers
-            else if (property.isSetUi1()) {
-                val = Integer.toString(property.getUi1());
-            } else if (property.isSetUi2()) {
-                val = Integer.toString(property.getUi2());
-            } else if (property.isSetUi4()) {
-                val = Long.toString(property.getUi4());
-            } else if (property.isSetUi8()) {
-                val = property.getUi8().toString();
-            } else if (property.isSetUint()) {
-                val = Long.toString(property.getUint());
-            }
-
-            // Reals
-            else if (property.isSetR4()) {
-                val = Float.toString(property.getR4());
-            } else if (property.isSetR8()) {
-                val = Double.toString(property.getR8());
-            } else if (property.isSetDecimal()) {
-                BigDecimal d = property.getDecimal();
-                if (d == null) {
-                    val = null;
-                } else {
-                    val = d.toPlainString();
-                }
-            } else if (property.isSetArray()) {
-                // TODO Fetch the array values and output
-            } else if (property.isSetVector()) {
-                // TODO Fetch the vector values and output
-            } else if (property.isSetBlob() || property.isSetOblob()) {
-                // TODO Decode, if possible
-            } else if (property.isSetStream() || property.isSetOstream() ||
-                    property.isSetVstream()) {
-                // TODO Decode, if possible
-            } else if (property.isSetStorage() || property.isSetOstorage()) {
-                // TODO Decode, if possible
-            } else {
-                // This type isn't currently supported yet, skip the property
-            }
-
-            String propName = "custom:" + property.getName();
-            if (date != null) {
-                Property tikaProp = Property.externalDate(propName);
-                metadata.set(tikaProp, date);
-            } else if (val != null) {
-                metadata.set(propName, val);
-            }
-        }
-    }
-
-    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);
-        } else {
-            throw new IllegalArgumentException(
-                    "Can't add property of class: " + optionalValue.getClass());
-        }
-    }
-
-    private void setProperty(Metadata metadata, Property property, String value) {
-        if (value != null) {
-            metadata.set(property, value);
-        }
-    }
-
-    private void setProperty(Metadata metadata, String name, String value) {
-        if (value != null) {
-            metadata.set(name, value);
-        }
-    }
-
-    private void setProperty(Metadata metadata, Property property, int value) {
-        if (value > 0) {
-            metadata.set(property, value);
-        }
-    }
-
-    private void setProperty(Metadata metadata, String name, int value) {
-        if (value > 0) {
-            metadata.set(name, Integer.toString(value));
-        }
-    }
-
-    private void addMultiProperty(Metadata metadata, Property property, Optional<String> value) {
-        if (!value.isPresent()) {
-            return;
-        }
-        SummaryExtractor.addMulti(metadata, property, value.get());
-    }
+public abstract class MetadataExtractor {
 
+    public abstract void extract(Metadata metadata) throws TikaException;
 }
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/OOXMLExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractor.java
index 5c37c9c7b5..8810477bf4 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractor.java
@@ -18,7 +18,6 @@ package org.apache.tika.parser.microsoft.ooxml;
 
 import java.io.IOException;
 
-import org.apache.poi.ooxml.POIXMLDocument;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
@@ -28,23 +27,9 @@ import org.apache.tika.parser.ParseContext;
 
 /**
  * Interface implemented by all Tika OOXML extractors.
- *
- * @see org.apache.poi.ooxml.extractor.POIXMLTextExtractor
  */
 public interface OOXMLExtractor {
 
-    /**
-     * Returns the opened document.
-     *
-     * @see org.apache.poi.ooxml.extractor.POIXMLTextExtractor#getDocument()
-     */
-    POIXMLDocument getDocument();
-
-    /**
-     * {@link org.apache.poi.ooxml.extractor.POIXMLTextExtractor#getMetadataTextExtractor()}
-     * not yet supported
-     * for OOXML by POI.
-     */
     MetadataExtractor getMetadataExtractor();
 
     /**
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/OOXMLExtractorFactory.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractorFactory.java
index f407ffeb62..64c1612fe4 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractorFactory.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLExtractorFactory.java
@@ -18,27 +18,18 @@ package org.apache.tika.parser.microsoft.ooxml;
 
 import java.io.IOException;
 import java.util.Locale;
+import java.util.Set;
 
-import org.apache.poi.extractor.ExtractorFactory;
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackageAccess;
 import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
 import org.apache.poi.util.LocaleUtil;
-import org.apache.poi.xslf.extractor.XSLFExtractor;
-import org.apache.poi.xslf.usermodel.XMLSlideShow;
 import org.apache.poi.xslf.usermodel.XSLFRelation;
-import org.apache.poi.xssf.extractor.XSSFBEventBasedExcelExtractor;
-import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
+import org.apache.poi.xssf.usermodel.XSSFRelation;
 import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
-import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.apache.poi.xwpf.usermodel.XWPFRelation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
@@ -51,9 +42,7 @@ 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.microsoft.OfficeParserConfig;
 import org.apache.tika.parser.microsoft.ooxml.xps.XPSExtractorDecorator;
-import org.apache.tika.parser.microsoft.ooxml.xps.XPSTextExtractor;
 import org.apache.tika.parser.microsoft.ooxml.xslf.XSLFEventBasedPowerPointExtractor;
 import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor;
 
@@ -64,21 +53,22 @@ import org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor;
  */
 public class OOXMLExtractorFactory {
 
-    private static final Logger LOG = LoggerFactory.getLogger(OOXMLExtractorFactory.class);
-    private static final int MAX_BUFFER_LENGTH = 1000000;
-    private static POIXMLExtractorFactory EXTRACTOR_FACTORY = new POIXMLExtractorFactory();
+    private static final String OFFICE_DOCUMENT_REL =
+            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
+    private static final String STRICT_OFFICE_DOCUMENT_REL =
+            "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument";
 
-
-    //TODO find what happened to SUPPORTED_TYPES
-    private static XSLFRelation[] XSLF_RELATIONS = new XSLFRelation[] {
+    private static final XSLFRelation[] XSLF_RELATIONS = new XSLFRelation[]{
             XSLFRelation.MAIN, XSLFRelation.MACRO, XSLFRelation.MACRO_TEMPLATE,
             XSLFRelation.PRESENTATIONML,
             XSLFRelation.PRESENTATIONML_TEMPLATE, XSLFRelation.PRESENTATION_MACRO
     };
 
-    static {
-        ExtractorFactory.setAllThreadsPreferEventExtractors(true);
-    }
+    private static final XSSFRelation[] XSSF_RELATIONS = new XSSFRelation[]{
+            XSSFRelation.WORKBOOK, XSSFRelation.MACROS_WORKBOOK,
+            XSSFRelation.TEMPLATE_WORKBOOK, XSSFRelation.MACRO_TEMPLATE_WORKBOOK,
+            XSSFRelation.MACRO_ADDIN_WORKBOOK
+    };
 
     public static void parse(TikaInputStream tis, ContentHandler baseHandler, Metadata metadata,
                              ParseContext context) throws IOException, SAXException, TikaException {
@@ -88,15 +78,9 @@ public class OOXMLExtractorFactory {
         try {
             OOXMLExtractor extractor = null;
 
-            // Locate or Open the OPCPackage for the file
-            // Detector should have prepared OPCPackageWrapper if salvaging was needed
             if (tis.getOpenContainer() instanceof OPCPackageWrapper) {
                 pkg = ((OPCPackageWrapper) tis.getOpenContainer()).getOPCPackage();
             } else {
-                // POI 5.x can throw InvalidOperationException (a RuntimeException
-                // extending OpenXML4JRuntimeException) for truncated/corrupt zip files.
-                // The detector should have salvaged if needed, but catch broadly here
-                // as a safety net.
                 try {
                     pkg = OPCPackage.open(tis.getPath().toString(), PackageAccess.READ);
                 } catch (RuntimeException e) {
@@ -119,79 +103,89 @@ public class OOXMLExtractorFactory {
                 type = MediaType.parse(mediaTypeString);
             }
             if (type != null && OOXMLParser.UNSUPPORTED_OOXML_TYPES.contains(type)) {
-                // Not a supported type, delegate to Empty Parser
                 EmptyParser.INSTANCE.parse(tis, baseHandler, metadata, context);
                 return;
             }
 
             if (type == null || !OOXMLParser.SUPPORTED_TYPES.contains(type)) {
-                // Get the type, and ensure it's one we handle
                 type = OPCPackageDetector.detectOfficeOpenXML(pkg);
             }
 
             if (type == null || OOXMLParser.UNSUPPORTED_OOXML_TYPES.contains(type)) {
-                // Not a supported type, delegate to Empty Parser
                 EmptyParser.INSTANCE.parse(tis, baseHandler, metadata, context);
                 return;
             }
             metadata.set(Metadata.CONTENT_TYPE, type.toString());
-            // Have the appropriate OOXML text extractor picked
-            POIXMLTextExtractor poiExtractor = null;
-            // This has already been set by OOXMLParser's call to configure()
-            // We can rely on this being non-null.
-            OfficeParserConfig config = context.get(OfficeParserConfig.class);
-            if (config.isUseSAXDocxExtractor()) {
-                poiExtractor = trySXWPF(pkg);
-            }
-            if (poiExtractor == null) {
-                poiExtractor = tryXSLF(pkg, config.isUseSAXPptxExtractor());
-            }
-            if (type.equals(OOXMLParser.XPS)) {
-                poiExtractor = new XPSTextExtractor(pkg);
-            }
 
-            if (poiExtractor == null) {
-                poiExtractor = EXTRACTOR_FACTORY.create(pkg);
+            // Detect format and create the appropriate extractor
+            String coreContentType = getCorePartContentType(pkg);
+
+            if (isVisioType(type)) {
+                extractor = new VSDXExtractorDecorator(context, pkg);
+            } else if (type.equals(OOXMLParser.XPS)) {
+                extractor = new XPSExtractorDecorator(context, pkg);
+            } else if (coreContentType != null) {
+                // Try DOCX
+                for (XWPFRelation relation : XWPFWordExtractor.SUPPORTED_TYPES) {
+                    if (coreContentType.equals(relation.getContentType())) {
+                        XWPFEventBasedWordExtractor wordExtractor =
+                                new XWPFEventBasedWordExtractor(pkg);
+                        extractor = new SXWPFWordExtractorDecorator(metadata, context,
+                                wordExtractor);
+                        metadata.add(TikaCoreProperties.TIKA_PARSED_BY,
+                                XWPFEventBasedWordExtractor.class.getCanonicalName());
+                        break;
+                    }
+                }
+
+                // Try PPTX
+                if (extractor == null) {
+                    for (XSLFRelation relation : XSLF_RELATIONS) {
+                        if (relation.getContentType().equals(coreContentType)) {
+                            XSLFEventBasedPowerPointExtractor pptExtractor =
+                                    new XSLFEventBasedPowerPointExtractor(pkg);
+                            extractor = new SXSLFPowerPointExtractorDecorator(metadata, context,
+                                    pptExtractor);
+                            metadata.add(TikaCoreProperties.TIKA_PARSED_BY,
+                                    XSLFEventBasedPowerPointExtractor.class.getCanonicalName());
+                            break;
+                        }
+                    }
+                    if (extractor == null &&
+                            XSLFRelation.THEME_MANAGER.getContentType().equals(coreContentType)) {
+                        XSLFEventBasedPowerPointExtractor pptExtractor =
+                                new XSLFEventBasedPowerPointExtractor(pkg);
+                        extractor = new SXSLFPowerPointExtractorDecorator(metadata, context,
+                                pptExtractor);
+                        metadata.add(TikaCoreProperties.TIKA_PARSED_BY,
+                                XSLFEventBasedPowerPointExtractor.class.getCanonicalName());
+                    }
+                }
+
+                // Try XLSB
+                if (extractor == null &&
+                        XSSFRelation.XLSB_BINARY_WORKBOOK.getContentType()
+                                .equals(coreContentType)) {
+                    extractor = new XSSFBExcelExtractorDecorator(context, pkg, locale);
+                }
+
+                // Try XLSX
+                if (extractor == null) {
+                    for (XSSFRelation relation : XSSF_RELATIONS) {
+                        if (relation.getContentType().equals(coreContentType)) {
+                            extractor = new XSSFExcelExtractorDecorator(context, pkg, locale);
+                            break;
+                        }
+                    }
+                }
             }
 
-            POIXMLDocument document = poiExtractor.getDocument();
-            if (poiExtractor instanceof XSSFBEventBasedExcelExtractor) {
-                extractor = new XSSFBExcelExtractorDecorator(context, poiExtractor, locale);
-            } else if (poiExtractor instanceof XSSFEventBasedExcelExtractor) {
-                extractor = new XSSFExcelExtractorDecorator(context, poiExtractor, locale);
-            } else if (poiExtractor instanceof XWPFEventBasedWordExtractor) {
-                extractor = new SXWPFWordExtractorDecorator(metadata, context,
-                        (XWPFEventBasedWordExtractor) poiExtractor);
-                metadata.add(TikaCoreProperties.TIKA_PARSED_BY,
-                        XWPFEventBasedWordExtractor.class.getCanonicalName());
-            } else if (poiExtractor instanceof XSLFEventBasedPowerPointExtractor) {
-                extractor = new SXSLFPowerPointExtractorDecorator(metadata, context,
-                        (XSLFEventBasedPowerPointExtractor) poiExtractor);
-                metadata.add(TikaCoreProperties.TIKA_PARSED_BY,
-                        XSLFEventBasedPowerPointExtractor.class.getCanonicalName());
-            } else if (poiExtractor instanceof XPSTextExtractor) {
-                extractor = new XPSExtractorDecorator(context, poiExtractor);
-            } else if (document == null) {
+            if (extractor == null) {
                 throw new TikaException(
-                        "Expecting UserModel based POI OOXML extractor with a document, but none" +
-                                " found. " +
-                                "The extractor returned was a " + poiExtractor);
-            } else if (document instanceof XMLSlideShow) {
-                extractor = new XSLFPowerPointExtractorDecorator(metadata, context,
-                        (org.apache.poi.xslf.extractor.XSLFExtractor) poiExtractor);
-            } else if (document instanceof XWPFDocument) {
-                extractor = new XWPFWordExtractorDecorator(metadata, context,
-                        (XWPFWordExtractor) poiExtractor);
-            } else {
-                extractor = new POIXMLTextExtractorDecorator(context, poiExtractor);
+                        "No OOXML extractor found for content type: " + coreContentType);
             }
 
-
-            // Get the bulk of the metadata first, so that it's accessible during
-            //  parsing if desired by the client (see TIKA-1109)
             extractor.getMetadataExtractor().extract(metadata);
-
-            // Extract the text, along with any in-document metadata
             extractor.getXHTML(baseHandler, metadata, context);
         } catch (IllegalArgumentException e) {
             if (e.getMessage() != null &&
@@ -208,70 +202,33 @@ public class OOXMLExtractorFactory {
         }
     }
 
-    private static POIXMLTextExtractor trySXWPF(OPCPackage pkg)
-            throws TikaException, OpenXML4JException, IOException {
-        PackageRelationshipCollection packageRelationshipCollection = pkg.getRelationshipsByType(
-                "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
-        if (packageRelationshipCollection.size() == 0) {
-            packageRelationshipCollection = pkg.getRelationshipsByType(
-                    "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument");
-        }
-
-        if (packageRelationshipCollection.size() == 0) {
-            return null;
-        }
-        PackagePart corePart = pkg.getPart(packageRelationshipCollection.getRelationship(0));
-        if (corePart == null) {
-            throw new TikaException("Couldn't find core part.");
-        }
-        String targetContentType = corePart.getContentType();
-        for (XWPFRelation relation : XWPFWordExtractor.SUPPORTED_TYPES) {
-            if (targetContentType.equals(relation.getContentType())) {
-                return new XWPFEventBasedWordExtractor(pkg);
-            }
-        }
-        return null;
+    private static final Set<String> VISIO_SUBTYPES = Set.of(
+            "vnd.ms-visio.drawing",
+            "vnd.ms-visio.drawing.macroenabled.12",
+            "vnd.ms-visio.stencil",
+            "vnd.ms-visio.stencil.macroenabled.12",
+            "vnd.ms-visio.template",
+            "vnd.ms-visio.template.macroenabled.12"
+    );
+
+    private static boolean isVisioType(MediaType type) {
+        return type != null && VISIO_SUBTYPES.contains(type.getSubtype());
     }
 
-    private static POIXMLTextExtractor tryXSLF(OPCPackage pkg, boolean eventBased)
-            throws TikaException, OpenXML4JException, IOException {
-
-        PackageRelationshipCollection packageRelationshipCollection = pkg.getRelationshipsByType(
-                "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
-        if (packageRelationshipCollection.size() == 0) {
-            packageRelationshipCollection = pkg.getRelationshipsByType(
-                    "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument");
-        }
-
-        if (packageRelationshipCollection.size() == 0) {
-            return null;
-        }
-        PackagePart corePart = pkg.getPart(packageRelationshipCollection.getRelationship(0));
-        if (corePart == null) {
-            throw new TikaException("Couldn't find core part");
-        }
-        String targetContentType = corePart.getContentType();
-
-        for (int i = 0; i < XSLF_RELATIONS.length; i++) {
-            XSLFRelation xslfRelation = XSLF_RELATIONS[i];
-            if (xslfRelation.getContentType().equals(targetContentType)) {
-                if (eventBased) {
-                    return new XSLFEventBasedPowerPointExtractor(pkg);
-                } else {
-                    return new XSLFExtractor(new XMLSlideShow(pkg));
-                }
+    private static String getCorePartContentType(OPCPackage pkg) {
+        try {
+            PackageRelationshipCollection rels =
+                    pkg.getRelationshipsByType(OFFICE_DOCUMENT_REL);
+            if (rels.size() == 0) {
+                rels = pkg.getRelationshipsByType(STRICT_OFFICE_DOCUMENT_REL);
             }
-        }
-
-        if (XSLFRelation.THEME_MANAGER.getContentType().equals(targetContentType)) {
-            if (eventBased) {
-                return new XSLFEventBasedPowerPointExtractor(pkg);
-            } else {
-                return new XSLFExtractor(new XMLSlideShow(pkg));
+            if (rels.size() == 0) {
+                return null;
             }
+            PackagePart corePart = pkg.getPart(rels.getRelationship(0));
+            return corePart != null ? corePart.getContentType() : null;
+        } catch (Exception e) {
+            return null;
         }
-        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/POIXMLTextExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/POIXMLTextExtractorDecorator.java
deleted file mode 100644
index be3c78f3ae..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/POIXMLTextExtractorDecorator.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.List;
-
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
-import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
-import org.xml.sax.SAXException;
-
-import org.apache.tika.parser.ParseContext;
-import org.apache.tika.sax.XHTMLContentHandler;
-
-public class POIXMLTextExtractorDecorator extends AbstractOOXMLExtractor {
-
-    public POIXMLTextExtractorDecorator(ParseContext context, POIXMLTextExtractor extractor) {
-        super(context, extractor);
-
-        if (extractor instanceof XSSFExcelExtractor) {
-            ((XSSFExcelExtractor) extractor)
-                    .setIncludeTextBoxes(config.isIncludeShapeBasedContent());
-        }
-    }
-
-    @Override
-    protected void buildXHTML(XHTMLContentHandler xhtml) throws SAXException {
-        // extract document content as a single string (not structured)
-        xhtml.element("p", extractor.getText());
-    }
-
-    @Override
-    protected List<PackagePart> getMainDocumentParts() {
-        return new ArrayList<>();
-    }
-}
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
index 7307e35437..632eb0c6c8 100644
--- 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
@@ -60,7 +60,6 @@ class SAXBasedMetadataExtractor extends MetadataExtractor {
     private final ParseContext parseContext;
 
     SAXBasedMetadataExtractor(OPCPackage opcPackage, ParseContext parseContext) {
-        super(null);
         this.opcPackage = opcPackage;
         this.parseContext = 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/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 7d8030afad..43c1e7d8b7 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
@@ -25,7 +25,6 @@ import java.util.Map;
 import java.util.zip.ZipException;
 
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
-import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.openxml4j.opc.PackagePartName;
 import org.apache.poi.openxml4j.opc.PackageRelationship;
@@ -68,7 +67,6 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
                     //TODO: what else
             };
 
-    private final OPCPackage opcPackage;
     private final ParseContext context;
     private final Metadata metadata;
     private final CommentAuthors commentAuthors = new CommentAuthors();
@@ -76,10 +74,9 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
 
     public SXSLFPowerPointExtractorDecorator(Metadata metadata, ParseContext context,
                                              XSLFEventBasedPowerPointExtractor extractor) {
-        super(context, extractor);
+        super(context, extractor.getPackage());
         this.metadata = metadata;
         this.context = context;
-        this.opcPackage = extractor.getPackage();
         for (String contentType : MAIN_STORY_PART_RELATIONS) {
             List<PackagePart> pps = opcPackage.getPartsByContentType(contentType);
             if (pps.size() > 0) {
@@ -87,12 +84,6 @@ public class SXSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
                 break;
             }
         }
-        //if mainDocument == null, throw exception
-    }
-
-    @Override
-    public MetadataExtractor getMetadataExtractor() {
-        return new SAXBasedMetadataExtractor(opcPackage, context);
     }
 
     /**
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 88c7aaa0f7..d990e6f96a 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
@@ -27,7 +27,6 @@ import java.util.Set;
 import java.util.zip.ZipException;
 
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
-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;
@@ -96,7 +95,6 @@ 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<>();
@@ -104,15 +102,9 @@ public class SXWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
 
     public SXWPFWordExtractorDecorator(Metadata metadata, ParseContext context,
                                        XWPFEventBasedWordExtractor extractor) {
-        super(context, extractor);
+        super(context, extractor.getPackage());
         this.metadata = metadata;
         this.context = context;
-        this.opcPackage = extractor.getPackage();
-    }
-
-    @Override
-    public MetadataExtractor getMetadataExtractor() {
-        return new SAXBasedMetadataExtractor(opcPackage, context);
     }
 
     @Override
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/TikaXSSFBCommentsTable.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/TikaXSSFBCommentsTable.java
new file mode 100644
index 0000000000..79a968a0ba
--- /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/TikaXSSFBCommentsTable.java
@@ -0,0 +1,137 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.xssf.binary.XSSFBParser;
+import org.apache.poi.xssf.binary.XSSFBRecordType;
+import org.apache.poi.xssf.binary.XSSFBUtils;
+import org.xml.sax.SAXException;
+
+import org.apache.tika.sax.XHTMLContentHandler;
+
+/**
+ * Replacement for POI's {@code XSSFBCommentsTable} that does not depend on
+ * {@code XSSFBComment}/{@code XSSFBRichTextString}/{@code XSSFRichTextString}
+ * (which pull in poi-ooxml-lite / xmlbeans via {@code CTRst}).
+ * <p>
+ * Stores comments as plain author + text strings.
+ */
+class TikaXSSFBCommentsTable extends XSSFBParser {
+
+    private final Map<CellAddress, CommentEntry> comments = new TreeMap<>();
+    private final List<String> authors = new ArrayList<>();
+
+    private int authorId = -1;
+    private int cellRow = -1;
+    private int cellCol = -1;
+    private String commentText;
+    private final StringBuilder buffer = new StringBuilder();
+
+    TikaXSSFBCommentsTable(InputStream is) throws IOException {
+        super(is);
+        parse();
+    }
+
+    @Override
+    public void handleRecord(int id, byte[] data) {
+        XSSFBRecordType recordType = XSSFBRecordType.lookup(id);
+        switch (recordType) {
+            case BrtBeginComment:
+                authorId = (int) LittleEndian.getUInt(data, 0);
+                // cell range: firstRow at offset 4, firstCol at offset 12
+                cellRow = (int) LittleEndian.getUInt(data, LittleEndianConsts.INT_SIZE);
+                cellCol = (int) LittleEndian.getUInt(data,
+                        LittleEndianConsts.INT_SIZE + 2 * LittleEndianConsts.INT_SIZE);
+                break;
+            case BrtCommentText:
+                buffer.setLength(0);
+                XSSFBUtils.readXLWideString(data, 1, buffer);
+                commentText = buffer.toString();
+                break;
+            case BrtEndComment:
+                CellAddress addr = new CellAddress(cellRow, cellCol);
+                String author = (authorId >= 0 && authorId < authors.size())
+                        ? authors.get(authorId) : "";
+                comments.put(addr, new CommentEntry(author, commentText));
+                authorId = -1;
+                cellRow = -1;
+                cellCol = -1;
+                commentText = null;
+                break;
+            case BrtCommentAuthor:
+                buffer.setLength(0);
+                XSSFBUtils.readXLWideString(data, 0, buffer);
+                authors.add(buffer.toString());
+                break;
+            default:
+                break;
+        }
+    }
+
+    CommentEntry get(CellAddress cellAddress) {
+        return cellAddress == null ? null : comments.get(cellAddress);
+    }
+
+    boolean hasComments() {
+        return !comments.isEmpty();
+    }
+
+    /**
+     * Emits all comments as cell content. Called after sheet processing
+     * since we bypass POI's built-in comment handling.
+     */
+    void emitAllComments(XHTMLContentHandler xhtml) throws SAXException {
+        for (Map.Entry<CellAddress, CommentEntry> entry : comments.entrySet()) {
+            CommentEntry comment = entry.getValue();
+            xhtml.startElement("p", "class", "cell-comment");
+            String author = comment.getAuthor();
+            if (author != null && !author.isEmpty()) {
+                xhtml.characters(author + ": ");
+            }
+            xhtml.characters(comment.getText());
+            xhtml.endElement("p");
+        }
+    }
+
+    static class CommentEntry {
+        private final String author;
+        private final String text;
+
+        CommentEntry(String author, String text) {
+            this.author = author;
+            this.text = text;
+        }
+
+        String getAuthor() {
+            return author;
+        }
+
+        String getText() {
+            return text;
+        }
+    }
+}
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/TikaXSSFBSharedStringsTable.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/TikaXSSFBSharedStringsTable.java
new file mode 100644
index 0000000000..6e1ec853d5
--- /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/TikaXSSFBSharedStringsTable.java
@@ -0,0 +1,159 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.RichTextString;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.xssf.binary.XSSFBParser;
+import org.apache.poi.xssf.binary.XSSFBRecordType;
+import org.apache.poi.xssf.binary.XSSFBUtils;
+import org.apache.poi.xssf.model.SharedStrings;
+
+/**
+ * Replacement for POI's {@code XSSFBSharedStringsTable} that does not depend on
+ * {@code XSSFRichTextString} (which pulls in poi-ooxml-lite / xmlbeans via {@code CTRst}).
+ * <p>
+ * The binary parsing logic is identical to POI's implementation; only
+ * {@link #getItemAt(int)} is changed to return a lightweight {@link RichTextString}
+ * wrapper instead of {@code XSSFRichTextString}.
+ */
+class TikaXSSFBSharedStringsTable implements SharedStrings {
+
+    private static final String SHARED_STRINGS_BINARY_CT =
+            "application/vnd.ms-excel.sharedStrings";
+
+    private int count;
+    private int uniqueCount;
+    private final List<String> strings = new ArrayList<>();
+
+    TikaXSSFBSharedStringsTable(OPCPackage pkg) throws IOException {
+        ArrayList<PackagePart> parts =
+                pkg.getPartsByContentType(SHARED_STRINGS_BINARY_CT);
+        if (!parts.isEmpty()) {
+            PackagePart sstPart = parts.get(0);
+            try (InputStream stream = sstPart.getInputStream()) {
+                readFrom(stream);
+            }
+        }
+    }
+
+    private void readFrom(InputStream inputStream) throws IOException {
+        new SSTBinaryReader(inputStream).parse();
+    }
+
+    @Override
+    public RichTextString getItemAt(int idx) {
+        return new PlainRichTextString(strings.get(idx));
+    }
+
+    @Override
+    public int getCount() {
+        return count;
+    }
+
+    @Override
+    public int getUniqueCount() {
+        return uniqueCount;
+    }
+
+    private class SSTBinaryReader extends XSSFBParser {
+
+        SSTBinaryReader(InputStream is) {
+            super(is);
+        }
+
+        @Override
+        public void handleRecord(int recordType, byte[] data) {
+            XSSFBRecordType type = XSSFBRecordType.lookup(recordType);
+            switch (type) {
+                case BrtSstItem:
+                    // Inline XSSFBRichStr.build() logic — that class is package-private in POI
+                    StringBuilder sb = new StringBuilder();
+                    XSSFBUtils.readXLWideString(data, 1, sb);
+                    strings.add(sb.toString());
+                    break;
+                case BrtBeginSst:
+                    count = (int) LittleEndian.getUInt(data, 0);
+                    uniqueCount = (int) LittleEndian.getUInt(data, 4);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Minimal {@link RichTextString} that just wraps a plain string,
+     * avoiding the xmlbeans dependency in {@code XSSFRichTextString}.
+     */
+    private static class PlainRichTextString implements RichTextString {
+
+        private final String text;
+
+        PlainRichTextString(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public String getString() {
+            return text;
+        }
+
+        @Override
+        public int length() {
+            return text == null ? 0 : text.length();
+        }
+
+        @Override
+        public int numFormattingRuns() {
+            return 0;
+        }
+
+        @Override
+        public int getIndexOfFormattingRun(int index) {
+            return 0;
+        }
+
+        @Override
+        public void applyFont(int startIndex, int endIndex, short fontIndex) {
+        }
+
+        @Override
+        public void applyFont(int startIndex, int endIndex, Font font) {
+        }
+
+        @Override
+        public void applyFont(Font font) {
+        }
+
+        @Override
+        public void clearFormatting() {
+        }
+
+        @Override
+        public void applyFont(short fontIndex) {
+        }
+    }
+}
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/VSDXExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/VSDXExtractorDecorator.java
new file mode 100644
index 0000000000..7e671057c2
--- /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/VSDXExtractorDecorator.java
@@ -0,0 +1,174 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+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.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.sax.XHTMLContentHandler;
+import org.apache.tika.utils.XMLReaderUtils;
+
+/**
+ * SAX-based extractor for Visio OOXML (.vsdx) files.
+ * Extracts text from {@code <Text>} elements inside shapes on each page.
+ */
+public class VSDXExtractorDecorator extends AbstractOOXMLExtractor {
+
+    private static final String VISIO_DOCUMENT_REL =
+            "http://schemas.microsoft.com/visio/2010/relationships/document";
+    private static final String VISIO_PAGES_REL =
+            "http://schemas.microsoft.com/visio/2010/relationships/pages";
+    private static final String VISIO_PAGE_REL =
+            "http://schemas.microsoft.com/visio/2010/relationships/page";
+
+    private final ParseContext context;
+
+    public VSDXExtractorDecorator(ParseContext context, OPCPackage pkg) {
+        super(context, pkg);
+        this.context = context;
+    }
+
+    @Override
+    protected void buildXHTML(XHTMLContentHandler xhtml)
+            throws SAXException, IOException {
+        try {
+            List<PackagePart> pageParts = getPageParts();
+            for (PackagePart pagePart : pageParts) {
+                xhtml.startElement("div", "class", "page");
+                try (InputStream is = pagePart.getInputStream()) {
+                    XMLReaderUtils.parseSAX(is, new VisioPageHandler(xhtml), context);
+                } catch (TikaException e) {
+                    throw new SAXException(e);
+                }
+                xhtml.endElement("div");
+            }
+        } catch (InvalidFormatException e) {
+            throw new SAXException("Error reading VSDX pages", e);
+        }
+    }
+
+    private List<PackagePart> getPageParts() throws InvalidFormatException {
+        // Root -> visio/document.xml
+        PackagePart documentPart = getRelatedPart(opcPackage, VISIO_DOCUMENT_REL);
+        if (documentPart == null) {
+            return Collections.emptyList();
+        }
+
+        // document.xml -> pages/pages.xml
+        PackagePart pagesPart = getRelatedPart(documentPart, VISIO_PAGES_REL);
+        if (pagesPart == null) {
+            return Collections.emptyList();
+        }
+
+        // pages.xml -> page1.xml, page2.xml, ...
+        List<PackagePart> pageParts = new ArrayList<>();
+        PackageRelationshipCollection pageRels =
+                pagesPart.getRelationshipsByType(VISIO_PAGE_REL);
+        for (PackageRelationship rel : pageRels) {
+            PackagePart pagePart = pagesPart.getRelatedPart(rel);
+            if (pagePart != null) {
+                pageParts.add(pagePart);
+            }
+        }
+        return pageParts;
+    }
+
+    private PackagePart getRelatedPart(OPCPackage pkg, String relType)
+            throws InvalidFormatException {
+        PackageRelationshipCollection rels = pkg.getRelationshipsByType(relType);
+        if (rels.isEmpty()) {
+            return null;
+        }
+        return pkg.getPart(rels.getRelationship(0));
+    }
+
+    private PackagePart getRelatedPart(PackagePart part, String relType)
+            throws InvalidFormatException {
+        PackageRelationshipCollection rels = part.getRelationshipsByType(relType);
+        if (rels.isEmpty()) {
+            return null;
+        }
+        return part.getRelatedPart(rels.getRelationship(0));
+    }
+
+    @Override
+    protected List<PackagePart> getMainDocumentParts() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * SAX handler for Visio page XML. Extracts text from {@code <Text>}
+     * elements inside {@code <Shape>} elements.
+     */
+    private static class VisioPageHandler extends DefaultHandler {
+
+        private static final String VISIO_NS =
+                "http://schemas.microsoft.com/office/visio/2012/main";
+
+        private final XHTMLContentHandler xhtml;
+        private boolean inText;
+        private final StringBuilder textBuffer = new StringBuilder();
+
+        VisioPageHandler(XHTMLContentHandler xhtml) {
+            this.xhtml = xhtml;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName,
+                                 Attributes atts) {
+            if ("Text".equals(localName) && VISIO_NS.equals(uri)) {
+                inText = true;
+                textBuffer.setLength(0);
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName)
+                throws SAXException {
+            if ("Text".equals(localName) && VISIO_NS.equals(uri)) {
+                inText = false;
+                String text = textBuffer.toString().trim();
+                if (!text.isEmpty()) {
+                    xhtml.startElement("p");
+                    xhtml.characters(text);
+                    xhtml.endElement("p");
+                }
+            }
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) {
+            if (inText) {
+                textBuffer.append(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/XSLFPowerPointExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFPowerPointExtractorDecorator.java
deleted file mode 100644
index fb9876fce8..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSLFPowerPointExtractorDecorator.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * 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.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.xml.namespace.QName;
-
-import org.apache.poi.common.usermodel.Hyperlink;
-import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
-import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.openxml4j.opc.PackagePartName;
-import org.apache.poi.openxml4j.opc.PackageRelationship;
-import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
-import org.apache.poi.openxml4j.opc.PackagingURIHelper;
-import org.apache.poi.openxml4j.opc.TargetMode;
-import org.apache.poi.sl.usermodel.Placeholder;
-import org.apache.poi.xslf.extractor.XSLFExtractor;
-import org.apache.poi.xslf.usermodel.XMLSlideShow;
-import org.apache.poi.xslf.usermodel.XSLFComment;
-import org.apache.poi.xslf.usermodel.XSLFCommentAuthors;
-import org.apache.poi.xslf.usermodel.XSLFGraphicFrame;
-import org.apache.poi.xslf.usermodel.XSLFGroupShape;
-import org.apache.poi.xslf.usermodel.XSLFHyperlink;
-import org.apache.poi.xslf.usermodel.XSLFNotes;
-import org.apache.poi.xslf.usermodel.XSLFNotesMaster;
-import org.apache.poi.xslf.usermodel.XSLFPictureShape;
-import org.apache.poi.xslf.usermodel.XSLFRelation;
-import org.apache.poi.xslf.usermodel.XSLFShape;
-import org.apache.poi.xslf.usermodel.XSLFSheet;
-import org.apache.poi.xslf.usermodel.XSLFSlide;
-import org.apache.poi.xslf.usermodel.XSLFSlideLayout;
-import org.apache.poi.xslf.usermodel.XSLFSlideShow;
-import org.apache.poi.xslf.usermodel.XSLFTable;
-import org.apache.poi.xslf.usermodel.XSLFTableCell;
-import org.apache.poi.xslf.usermodel.XSLFTableRow;
-import org.apache.poi.xslf.usermodel.XSLFTextParagraph;
-import org.apache.poi.xslf.usermodel.XSLFTextRun;
-import org.apache.poi.xslf.usermodel.XSLFTextShape;
-import org.apache.xmlbeans.XmlObject;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentAuthor;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentAuthorList;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTSlide;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdList;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry;
-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.sax.XHTMLContentHandler;
-import org.apache.tika.utils.StringUtils;
-
-public class XSLFPowerPointExtractorDecorator extends AbstractOOXMLExtractor {
-
-    private final static String HANDOUT_MASTER =
-            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutMaster";
-
-    private Metadata metadata;
-
-    public XSLFPowerPointExtractorDecorator(Metadata metadata, ParseContext context,
-                                            XSLFExtractor extractor) {
-        super(context, extractor);
-        this.metadata = metadata;
-    }
-
-
-    /**
-     * @see XSLFExtractor#getText()
-     */
-    protected void buildXHTML(XHTMLContentHandler xhtml) throws SAXException, IOException {
-        XMLSlideShow slideShow = (XMLSlideShow) extractor.getDocument();
-        handleCommentAuthors(slideShow);
-
-        List<XSLFSlide> slides = slideShow.getSlides();
-        AtomicInteger hiddenSlideCounter = new AtomicInteger(0);
-        for (XSLFSlide slide : slides) {
-            handleSlide(slide, xhtml, hiddenSlideCounter);
-        }
-        if (hiddenSlideCounter.get() > 0) {
-            metadata.set(Office.HAS_HIDDEN_SLIDES, true);
-            metadata.set(Office.NUM_HIDDEN_SLIDES, hiddenSlideCounter.get());
-        }
-    }
-
-    private void handleSlide(XSLFSlide slide, XHTMLContentHandler xhtml, AtomicInteger hiddenSlideCounter) throws SAXException {
-        String slideDesc;
-        if (slide.getPackagePart() != null && slide.getPackagePart().getPartName() != null) {
-            slideDesc = getJustFileName(slide.getPackagePart().getPartName().toString());
-            slideDesc += "_";
-        } else {
-            slideDesc = null;
-        }
-
-        if (slide.isHidden()) {
-            hiddenSlideCounter.incrementAndGet();
-        }
-
-        // slide content
-        xhtml.startElement("div", "class", "slide-content");
-        extractContent(slide.getShapes(), false, xhtml, slideDesc);
-        xhtml.endElement("div");
-
-        if (config.isIncludeSlideMasterContent()) {
-            // slide layout which is the master sheet for this slide
-            xhtml.startElement("div", "class", "slide-master-content");
-            XSLFSlideLayout slideLayout = slide.getMasterSheet();
-            extractContent(slideLayout.getShapes(), true, xhtml, null);
-            xhtml.endElement("div");
-
-            // slide master which is the master sheet for all text layouts
-            XSLFSheet slideMaster = slideLayout.getMasterSheet();
-            extractContent(slideMaster.getShapes(), true, xhtml, null);
-        }
-        if (config.isIncludeSlideNotes()) {
-            // notes (if present)
-            XSLFNotes slideNotes = slide.getNotes();
-            if (slideNotes != null) {
-                xhtml.startElement("div", "class", "slide-notes");
-
-                extractContent(slideNotes.getShapes(), false, xhtml, slideDesc);
-
-                // master sheet for this notes
-                XSLFNotesMaster notesMaster = slideNotes.getMasterSheet();
-                if (notesMaster != null) {
-                    extractContent(notesMaster.getShapes(), true, xhtml, null);
-                }
-                xhtml.endElement("div");
-            }
-        }
-
-        // comments (if present)
-        List<XSLFComment> comments = slide.getComments();
-        if (comments != null) {
-            StringBuilder authorStringBuilder = new StringBuilder();
-            for (XSLFComment comment : comments) {
-                authorStringBuilder.setLength(0);
-                xhtml.startElement("p", "class", "slide-comment");
-                if (comment.getAuthor() != null) {
-                    authorStringBuilder.append(comment.getAuthor());
-                }
-                if (comment.getAuthorInitials() != null) {
-                    if (authorStringBuilder.length() > 0) {
-                        authorStringBuilder.append(" ");
-                    }
-                    authorStringBuilder.append("(").append(comment.getAuthorInitials()).append(")");
-                }
-                if (comment.getText() != null && authorStringBuilder.length() > 0) {
-                    authorStringBuilder.append(" - ");
-                }
-                if (authorStringBuilder.length() > 0) {
-                    xhtml.startElement("b");
-                    xhtml.characters(authorStringBuilder.toString());
-                    xhtml.endElement("b");
-                }
-
-                xhtml.characters(comment.getText());
-                xhtml.endElement("p");
-            }
-        }
-        //now dump diagram data
-        handleGeneralTextContainingPart(RELATION_DIAGRAM_DATA, "diagram-data",
-                slide.getPackagePart(), metadata,
-                new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
-                        new HashMap<>()//empty
-                ));
-        //now dump chart data
-        handleGeneralTextContainingPart(XSLFRelation.CHART.getRelation(), "chart",
-                slide.getPackagePart(), metadata,
-                new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
-                        new HashMap<>()//empty
-                ));
-
-        CTSlide ctSlide = slide.getXmlObject();
-        if (ctSlide.isSetTiming()) {
-            //perhaps require more, like: ctSlide.getTiming()?.getTnLst()?.getParArray()?.length
-            metadata.set(Office.HAS_ANIMATIONS, true);
-        }
-    }
-
-    private void handleCommentAuthors(XMLSlideShow slideShow) {
-        XSLFCommentAuthors commentAuthors = slideShow.getCommentAuthors();
-        if (commentAuthors != null) {
-            CTCommentAuthorList ctAuthorList = commentAuthors.getCTCommentAuthorsList();
-            CTCommentAuthor[] ctAuthorArray = ctAuthorList.getCmAuthorArray();
-            if (ctAuthorArray != null) {
-                Set<String> names = new HashSet<>();
-                for (CTCommentAuthor ctCommentAuthor : ctAuthorArray) {
-                    String n = ctCommentAuthor.getName();
-                    if (StringUtils.isBlank(n) || names.contains(n)) {
-                        continue;
-                    }
-                    metadata.add(Office.COMMENT_PERSONS, n);
-                    names.add(n);
-                }
-            }
-        }
-    }
-
-    private void extractContent(List<? extends XSLFShape> shapes, boolean skipPlaceholders,
-                                XHTMLContentHandler xhtml, String slideDesc) throws SAXException {
-        for (XSLFShape sh : shapes) {
-
-            if (sh instanceof XSLFTextShape) {
-                XSLFTextShape txt = (XSLFTextShape) sh;
-                Placeholder ph = txt.getTextType();
-                if (skipPlaceholders && ph != null) {
-                    continue;
-                }
-                boolean inHyperlink = false;
-                for (XSLFTextParagraph p : txt.getTextParagraphs()) {
-                    xhtml.startElement("p");
-                    if (!config.isIncludeHeadersAndFooters() && p.isHeaderOrFooter()) {
-                        continue;
-                    }
-                    for (XSLFTextRun run : p.getTextRuns()) {
-                        //TODO: add check for targetmode=external into POI
-                        //then check to confirm that the urls are actually
-                        //external and not footnote refs via the current hack
-                        Hyperlink hyperlink = run.getHyperlink();
-
-                        if (hyperlink != null && hyperlink.getAddress() != null &&
-                                !hyperlink.getAddress().contains("#_ftn")) {
-                            xhtml.startElement("a", "href", hyperlink.getAddress());
-                            inHyperlink = true;
-                        }
-                        xhtml.characters(run.getRawText());
-                        if (inHyperlink == true) {
-                            xhtml.endElement("a");
-                        }
-                        inHyperlink = false;
-                    }
-                    xhtml.endElement("p");
-                }
-            } else if (sh instanceof XSLFGroupShape) {
-                // recurse into groups of shapes
-                XSLFGroupShape group = (XSLFGroupShape) sh;
-                extractContent(group.getShapes(), skipPlaceholders, xhtml, slideDesc);
-            } else if (sh instanceof XSLFTable) {
-                //unlike tables in Word, ppt/x can't have recursive tables...I don't think
-                extractTable((XSLFTable) sh, xhtml);
-            } else if (sh instanceof XSLFGraphicFrame) {
-                XSLFGraphicFrame frame = (XSLFGraphicFrame) sh;
-                XmlObject[] sp = frame.getXmlObject().selectPath(
-                        "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:oleObj");
-                if (sp != null) {
-                    for (XmlObject emb : sp) {
-                        XmlObject relIDAtt = emb.selectAttribute(new QName(
-                                "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
-                                "id"));
-                        if (relIDAtt != null) {
-                            String relID = relIDAtt.getDomNode().getNodeValue();
-                            if (slideDesc != null) {
-                                relID = slideDesc + relID;
-                            }
-                            AttributesImpl attributes = new AttributesImpl();
-                            attributes.addAttribute("", "class", "class", "CDATA", "embedded");
-                            attributes.addAttribute("", "id", "id", "CDATA", relID);
-                            xhtml.startElement("div", attributes);
-                            xhtml.endElement("div");
-                        }
-                    }
-                }
-            } else if (sh instanceof XSLFPictureShape) {
-                if (!skipPlaceholders && (sh.getXmlObject() instanceof CTPicture)) {
-                    CTPicture ctPic = ((CTPicture) sh.getXmlObject());
-                    if (ctPic.getBlipFill() != null && ctPic.getBlipFill().getBlip() != null) {
-                        String relID = ctPic.getBlipFill().getBlip().getEmbed();
-                        if (relID != null) {
-                            if (slideDesc != null) {
-                                relID = slideDesc + relID;
-                            }
-                            AttributesImpl attributes = new AttributesImpl();
-                            attributes.addAttribute("", "class", "class", "CDATA", "embedded");
-                            attributes.addAttribute("", "id", "id", "CDATA", relID);
-                            xhtml.startElement("div", attributes);
-                            xhtml.endElement("div");
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void extractTable(XSLFTable tbl, XHTMLContentHandler xhtml) throws SAXException {
-        xhtml.startElement("table");
-        for (XSLFTableRow row : tbl) {
-            xhtml.startElement("tr");
-            for (XSLFTableCell c : row.getCells()) {
-                xhtml.startElement("td");
-                //TODO: Need to wait for fix in POI to test for hyperlink first
-                //shouldn't need to catch NPE...
-                XSLFHyperlink hyperlink = null;
-                try {
-                    hyperlink = c.getHyperlink();
-                } catch (NullPointerException e) {
-                    //swallow
-                }
-                if (hyperlink != null && hyperlink.getAddress() != null) {
-                    xhtml.startElement("a", "href", hyperlink.getAddress());
-                }
-                xhtml.characters(c.getText());
-                if (hyperlink != null && hyperlink.getAddress() != null) {
-                    xhtml.endElement("a");
-                }
-                xhtml.endElement("td");
-            }
-            xhtml.endElement("tr");
-        }
-        xhtml.endElement("table");
-
-    }
-
-    /**
-     * In PowerPoint files, slides have things embedded in them,
-     * and slide drawings which have the images
-     */
-    @Override
-    protected List<PackagePart> getMainDocumentParts() throws TikaException {
-        List<PackagePart> parts = new ArrayList<>();
-        XSLFSlideShow document = null;
-        try {
-            document = new XSLFSlideShow(extractor.getPackage());
-        } catch (Exception e) {
-            throw new TikaException(e.getMessage()); // Shouldn't happen
-        }
-
-        CTSlideIdList ctSlideIdList = document.getSlideReferences();
-        if (ctSlideIdList != null) {
-            for (int i = 0; i < ctSlideIdList.sizeOfSldIdArray(); i++) {
-                CTSlideIdListEntry ctSlide = ctSlideIdList.getSldIdArray(i);
-                // Add the slide
-                PackagePart slidePart;
-                try {
-                    slidePart = document.getSlidePart(ctSlide);
-                } catch (Exception e) {
-                    throw new TikaException("Broken OOXML file", e);
-                }
-                addSlideParts(slidePart, parts);
-            }
-        }
-        //add full document to include macros
-        parts.add(document.getPackagePart());
-
-        for (String rel : new String[]{XSLFRelation.SLIDE_MASTER.getRelation(), HANDOUT_MASTER}) {
-            try {
-                PackageRelationshipCollection prc =
-                        document.getPackagePart().getRelationshipsByType(rel);
-                for (int i = 0; i < prc.size(); i++) {
-                    PackagePart pp =
-                            document.getPackagePart().getRelatedPart(prc.getRelationship(i));
-                    if (pp != null) {
-                        parts.add(pp);
-                    }
-                }
-
-            } catch (InvalidFormatException e) {
-                //log
-            }
-        }
-
-        return parts;
-    }
-
-
-    private void addSlideParts(PackagePart slidePart, List<PackagePart> parts) {
-
-        for (String relation : new String[]{XSLFRelation.VML_DRAWING.getRelation(),
-                XSLFRelation.SLIDE_LAYOUT.getRelation(), XSLFRelation.NOTES_MASTER.getRelation(),
-                XSLFRelation.NOTES.getRelation(), XSLFRelation.CHART.getRelation(),
-                XSLFRelation.DIAGRAM_DRAWING.getRelation()}) {
-            try {
-                for (PackageRelationship packageRelationship : slidePart
-                        .getRelationshipsByType(relation)) {
-                    if (packageRelationship.getTargetMode() == TargetMode.INTERNAL) {
-                        PackagePartName relName = PackagingURIHelper
-                                .createPartName(packageRelationship.getTargetURI());
-                        parts.add(packageRelationship.getPackage().getPart(relName));
-                    }
-                }
-            } catch (InvalidFormatException e) {
-                //swallow
-            }
-        }
-        //and slide of course
-        parts.add(slidePart);
-
-    }
-
-}
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/XSSFBExcelExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFBExcelExtractorDecorator.java
index 0db32f067a..d59ddd6528 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFBExcelExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFBExcelExtractorDecorator.java
@@ -20,17 +20,17 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Locale;
 
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
+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.xssf.binary.XSSFBCommentsTable;
-import org.apache.poi.xssf.binary.XSSFBSharedStringsTable;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
 import org.apache.poi.xssf.binary.XSSFBSheetHandler;
 import org.apache.poi.xssf.binary.XSSFBStylesTable;
 import org.apache.poi.xssf.eventusermodel.XSSFBReader;
-import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
-import org.apache.poi.xssf.extractor.XSSFBEventBasedExcelExtractor;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
@@ -43,17 +43,9 @@ import org.apache.tika.sax.XHTMLContentHandler;
 
 public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
 
-    public XSSFBExcelExtractorDecorator(ParseContext context, POIXMLTextExtractor extractor,
+    public XSSFBExcelExtractorDecorator(ParseContext context, OPCPackage pkg,
                                         Locale locale) {
-        super(context, extractor, locale);
-    }
-
-    @Override
-    protected void configureExtractor(POIXMLTextExtractor extractor, Locale locale) {
-        //need to override this because setFormulasNotResults is not yet available
-        //for xlsb
-        //((XSSFBEventBasedExcelExtractor)extractor).setFormulasNotResults(false);
-        ((XSSFBEventBasedExcelExtractor) extractor).setLocale(locale);
+        super(context, pkg, locale);
     }
 
     @Override
@@ -67,15 +59,12 @@ public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
         super.getXHTML(handler, metadata, context);
     }
 
-    /**
-     * @see org.apache.poi.xssf.extractor.XSSFBEventBasedExcelExtractor#getText()
-     */
     @Override
     protected void buildXHTML(XHTMLContentHandler xhtml)
             throws SAXException, IOException {
-        OPCPackage container = extractor.getPackage();
+        OPCPackage container = opcPackage;
 
-        XSSFBSharedStringsTable strings;
+        TikaXSSFBSharedStringsTable strings;
         XSSFBReader.SheetIterator iter;
         XSSFBReader xssfReader;
         XSSFBStylesTable styles;
@@ -87,7 +76,7 @@ public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
             }
             styles = xssfReader.getXSSFBStylesTable();
             iter = (XSSFBReader.SheetIterator) xssfReader.getSheetsData();
-            strings = new XSSFBSharedStringsTable(container);
+            strings = new TikaXSSFBSharedStringsTable(container);
         } catch (OpenXML4JException e) {
             throw new IOException(e);
         }
@@ -99,24 +88,35 @@ public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
             sheetParts.add(sheetPart);
 
             SheetTextAsHTML sheetExtractor = new SheetTextAsHTML(config, xhtml);
-            XSSFBCommentsTable comments = iter.getXSSFBSheetComments();
 
-            // Start, and output the sheet name
+            // Parse comments with our own binary parser that avoids xmlbeans
+            TikaXSSFBCommentsTable tikaComments = parseBinaryComments(sheetPart);
+            if (tikaComments != null && tikaComments.hasComments()) {
+                metadata.set(Office.HAS_COMMENTS, true);
+            }
+
             xhtml.startElement("div");
             xhtml.element("h1", iter.getSheetName());
 
-            // Extract the main sheet contents
             xhtml.startElement("table");
             xhtml.startElement("tbody");
 
-            processSheet(sheetExtractor, comments, styles, strings, stream);
+            // Pass null for POI's comments table to avoid xmlbeans dependency.
+            // Comments are emitted separately after sheet processing.
+            XSSFBSheetHandler xssfbSheetHandler =
+                    new XSSFBSheetHandler(stream, styles, null, strings,
+                            sheetExtractor, formatter, false);
+            xssfbSheetHandler.parse();
 
             xhtml.endElement("tbody");
             xhtml.endElement("table");
 
-            // Output any headers and footers
-            // (Need to process the sheet to get them, so we can't
-            //  do the headers before the contents)
+            // Emit comments after the table (since we bypass POI's inline
+            // comment handling to avoid xmlbeans dependency)
+            if (tikaComments != null) {
+                tikaComments.emitAllComments(xhtml);
+            }
+
             for (String header : sheetExtractor.headers) {
                 extractHeaderFooter(header, xhtml);
             }
@@ -124,17 +124,35 @@ public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
                 extractHeaderFooter(footer, xhtml);
             }
             processDrawings(sheetPart, xhtml);
-
-            //for now dump sheet hyperlinks at bottom of page
-            //consider a double-pass of the inputstream to reunite hyperlinks with cells/textboxes
-            //step 1: extract hyperlink info from bottom of page
-            //step 2: process as we do now, but with cached hyperlink relationship info
             extractHyperLinks(sheetPart, xhtml);
-            // All done with this sheet
             xhtml.endElement("div");
         }
     }
 
+    private static final String RELATION_COMMENTS =
+            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
+
+    private TikaXSSFBCommentsTable parseBinaryComments(PackagePart sheetPart) {
+        try {
+            PackageRelationshipCollection rels =
+                    sheetPart.getRelationshipsByType(RELATION_COMMENTS);
+            if (rels.isEmpty()) {
+                return null;
+            }
+            PackageRelationship rel = rels.getRelationship(0);
+            PackagePartName partName =
+                    PackagingURIHelper.createPartName(rel.getTargetURI());
+            PackagePart commentsPart = rel.getPackage().getPart(partName);
+            if (commentsPart == null) {
+                return null;
+            }
+            try (InputStream is = commentsPart.getInputStream()) {
+                return new TikaXSSFBCommentsTable(is);
+            }
+        } catch (InvalidFormatException | IOException e) {
+            return null;
+        }
+    }
 
     @Override
     protected void extractHeaderFooter(String hf, XHTMLContentHandler xhtml) throws SAXException {
@@ -142,16 +160,4 @@ public class XSSFBExcelExtractorDecorator extends XSSFExcelExtractorDecorator {
             xhtml.element("p", hf);
         }
     }
-
-
-    private void processSheet(SheetContentsHandler sheetContentsExtractor,
-                              XSSFBCommentsTable comments, XSSFBStylesTable styles,
-                              XSSFBSharedStringsTable strings, InputStream sheetInputStream)
-            throws IOException, SAXException {
-
-        XSSFBSheetHandler xssfbSheetHandler =
-                new XSSFBSheetHandler(sheetInputStream, styles, comments, strings,
-                        sheetContentsExtractor, formatter, false);
-        xssfbSheetHandler.parse();
-    }
 }
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/XSSFExcelExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
index 899016caf8..dba6dab4eb 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/XSSFExcelExtractorDecorator.java
@@ -26,7 +26,6 @@ import java.util.Locale;
 import java.util.Map;
 
 import org.apache.poi.hssf.extractor.ExcelExtractor;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
@@ -42,7 +41,6 @@ import org.apache.poi.ss.usermodel.HeaderFooter;
 import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.xssf.eventusermodel.XSSFReader;
 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
-import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
 import org.apache.poi.xssf.usermodel.XSSFComment;
 import org.apache.poi.xssf.usermodel.helpers.HeaderFooterHelper;
 import org.xml.sax.Attributes;
@@ -102,13 +100,11 @@ public class XSSFExcelExtractorDecorator extends AbstractOOXMLExtractor {
     protected Metadata metadata;
     protected ParseContext parseContext;
 
-    public XSSFExcelExtractorDecorator(ParseContext context, POIXMLTextExtractor extractor,
+    public XSSFExcelExtractorDecorator(ParseContext context, OPCPackage pkg,
                                        Locale locale) {
-        super(context, extractor);
+        super(context, pkg);
 
         this.parseContext = context;
-        this.extractor = (XSSFEventBasedExcelExtractor) extractor;
-        configureExtractor(this.extractor, locale);
 
         if (locale == null) {
             formatter = new TikaExcelDataFormatter();
@@ -122,19 +118,9 @@ public class XSSFExcelExtractorDecorator extends AbstractOOXMLExtractor {
         }
     }
 
-    protected void configureExtractor(POIXMLTextExtractor extractor, Locale locale) {
-        ((XSSFEventBasedExcelExtractor) extractor)
-                .setIncludeTextBoxes(config.isIncludeShapeBasedContent());
-        ((XSSFEventBasedExcelExtractor) extractor).setFormulasNotResults(false);
-        ((XSSFEventBasedExcelExtractor) extractor).setLocale(locale);
-        //given that we load our own shared strings table, setting:
-        //((XSSFEventBasedExcelExtractor)extractor).setConcatenatePhoneticRuns();
-        //does no good here.
-    }
-
     @Override
     public MetadataExtractor getMetadataExtractor() {
-        return new SAXBasedMetadataExtractor(extractor.getPackage(), parseContext);
+        return new SAXBasedMetadataExtractor(opcPackage, parseContext);
     }
 
     @Override
@@ -154,7 +140,7 @@ public class XSSFExcelExtractorDecorator extends AbstractOOXMLExtractor {
     @Override
     protected void buildXHTML(XHTMLContentHandler xhtml)
             throws SAXException, IOException {
-        OPCPackage container = extractor.getPackage();
+        OPCPackage container = opcPackage;
 
         XSSFSharedStringsShim stringsShim;
         XSSFReader.SheetIterator iter;
@@ -916,7 +902,7 @@ public class XSSFExcelExtractorDecorator extends AbstractOOXMLExtractor {
 
         //add main document so that macros can be extracted
         //by AbstractOOXMLExtractor
-        parts.addAll(extractor.getPackage()
+        parts.addAll(opcPackage
                 .getPartsByRelationshipType(PackageRelationshipTypes.CORE_DOCUMENT));
 
         return parts;
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
deleted file mode 100644
index dfb4c71924..0000000000
--- 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
+++ /dev/null
@@ -1,734 +0,0 @@
-/*
- * 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.IOException;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.xml.namespace.QName;
-
-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;
-import org.apache.poi.xwpf.model.XWPFCommentsDecorator;
-import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
-import org.apache.poi.xwpf.usermodel.BodyType;
-import org.apache.poi.xwpf.usermodel.IBody;
-import org.apache.poi.xwpf.usermodel.IBodyElement;
-import org.apache.poi.xwpf.usermodel.ICell;
-import org.apache.poi.xwpf.usermodel.IRunElement;
-import org.apache.poi.xwpf.usermodel.ISDTContent;
-import org.apache.poi.xwpf.usermodel.XWPFDocument;
-import org.apache.poi.xwpf.usermodel.XWPFEndnote;
-import org.apache.poi.xwpf.usermodel.XWPFFootnote;
-import org.apache.poi.xwpf.usermodel.XWPFHeaderFooter;
-import org.apache.poi.xwpf.usermodel.XWPFHyperlink;
-import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
-import org.apache.poi.xwpf.usermodel.XWPFParagraph;
-import org.apache.poi.xwpf.usermodel.XWPFPicture;
-import org.apache.poi.xwpf.usermodel.XWPFPictureData;
-import org.apache.poi.xwpf.usermodel.XWPFRelation;
-import org.apache.poi.xwpf.usermodel.XWPFRun;
-import org.apache.poi.xwpf.usermodel.XWPFSDT;
-import org.apache.poi.xwpf.usermodel.XWPFSDTCell;
-import org.apache.poi.xwpf.usermodel.XWPFStyle;
-import org.apache.poi.xwpf.usermodel.XWPFStyles;
-import org.apache.poi.xwpf.usermodel.XWPFTable;
-import org.apache.poi.xwpf.usermodel.XWPFTableCell;
-import org.apache.poi.xwpf.usermodel.XWPFTableRow;
-import org.apache.xmlbeans.XmlCursor;
-import org.apache.xmlbeans.XmlObject;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTObject;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-import org.apache.tika.io.TikaInputStream;
-import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.Office;
-import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.microsoft.EMFParser;
-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;
-
-public class XWPFWordExtractorDecorator extends AbstractOOXMLExtractor {
-
-    // could be improved by using the real delimiter in xchFollow [MS-DOC], v20140721, 2.4.6.3,
-    // Part 3, Step 3
-    private static final String LIST_DELIMITER = " ";
-
-    //include all parts that might have embedded objects
-    private final static String[] MAIN_PART_RELATIONS =
-            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",
-                    AbstractOOXMLExtractor.RELATION_DIAGRAM_DATA};
-
-
-    private XWPFDocument document;
-    private XWPFStyles styles;
-    private Metadata metadata;
-
-    //This is a map of the rIds of embedded files and the EmbeddedPartMetadata
-    //that may be associated with the embedded file. The EmbeddedPartMetadata
-    //is populated during the parse of the main document.
-    private Map<String, EmbeddedPartMetadata> embeddedPartMetadataMap = new HashMap<>();
-
-    public XWPFWordExtractorDecorator(Metadata metadata, ParseContext context,
-                                      XWPFWordExtractor extractor) {
-        super(context, extractor);
-        this.metadata = metadata;
-        document = (XWPFDocument) extractor.getDocument();
-        styles = document.getStyles();
-    }
-
-    /**
-     * @see org.apache.poi.xwpf.extractor.XWPFWordExtractor#getText()
-     */
-    @Override
-    protected void buildXHTML(XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        XWPFHeaderFooterPolicy hfPolicy = document.getHeaderFooterPolicy();
-        XWPFListManager listManager = new XWPFListManager(loadNumbering());
-        // headers
-        if (hfPolicy != null && config.isIncludeHeadersAndFooters()) {
-            extractHeaders(xhtml, hfPolicy, listManager);
-        }
-
-        // process text in the order that it occurs in
-        extractIBodyText(document, listManager, xhtml);
-        extractFeatures(document, metadata);
-
-        //handle the diagram data
-        handleGeneralTextContainingPart(RELATION_DIAGRAM_DATA, "diagram-data",
-                document.getPackagePart(), metadata,
-                new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
-                        new HashMap<>()//empty
-                ));
-        //handle chart data
-        handleGeneralTextContainingPart(XSSFRelation.CHART.getRelation(), "chart",
-                document.getPackagePart(), metadata,
-                new OOXMLWordAndPowerPointTextHandler(new OOXMLTikaBodyPartHandler(xhtml),
-                        new HashMap<>()//empty
-                ));
-
-        // then all document footers
-        if (hfPolicy != null && config.isIncludeHeadersAndFooters()) {
-            extractFooters(xhtml, hfPolicy, listManager);
-        }
-    }
-
-    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());
-    }
-
-    @Override
-    protected Map<String, EmbeddedPartMetadata> getEmbeddedPartMetadataMap() {
-        return embeddedPartMetadataMap;
-    }
-
-    private void extractIBodyText(IBody bodyElement, XWPFListManager listManager,
-                                  XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        for (IBodyElement element : bodyElement.getBodyElements()) {
-            if (element instanceof XWPFParagraph) {
-                XWPFParagraph paragraph = (XWPFParagraph) element;
-                extractParagraph(paragraph, listManager, xhtml);
-            }
-            if (element instanceof XWPFTable) {
-                XWPFTable table = (XWPFTable) element;
-                extractTable(table, listManager, xhtml);
-            }
-            if (element instanceof XWPFSDT) {
-                extractSDT((XWPFSDT) element, xhtml);
-            }
-
-        }
-    }
-
-    private void extractSDT(XWPFSDT element, XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        ISDTContent content = element.getContent();
-        String tag = "p";
-        xhtml.startElement(tag);
-        xhtml.characters(content.getText());
-        xhtml.endElement(tag);
-    }
-
-    private void extractParagraph(XWPFParagraph paragraph, XWPFListManager listManager,
-                                  XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        // If this paragraph is actually a whole new section, then
-        //  it could have its own headers and footers
-        // Check and handle if so
-        XWPFHeaderFooterPolicy headerFooterPolicy = null;
-        if (paragraph.getCTP().getPPr() != null) {
-            CTSectPr ctSectPr = paragraph.getCTP().getPPr().getSectPr();
-            if (ctSectPr != null && config.isIncludeHeadersAndFooters()) {
-                headerFooterPolicy = new XWPFHeaderFooterPolicy(document, ctSectPr);
-                extractHeaders(xhtml, headerFooterPolicy, listManager);
-            }
-        }
-
-        // Is this a paragraph, or a heading?
-        String tag = "p";
-        String styleClass = null;
-        //TIKA-2144 check that styles is not null
-        if (paragraph.getStyleID() != null && styles != null) {
-            XWPFStyle style = styles.getStyle(paragraph.getStyleID());
-
-            if (style != null && style.getName() != null) {
-                TagAndStyle tas = WordExtractor.buildParagraphTagAndStyle(style.getName(),
-                        paragraph.getPartType() == BodyType.TABLECELL);
-                tag = tas.getTag();
-                styleClass = tas.getStyleClass();
-            }
-        }
-
-        if (styleClass == null) {
-            xhtml.startElement(tag);
-        } else {
-            xhtml.startElement(tag, "class", styleClass);
-        }
-
-        writeParagraphNumber(paragraph, listManager, xhtml);
-
-        // Output placeholder for any embedded docs:
-        processEmbeddedObjects(paragraph.getRuns(), xhtml);
-
-        // Attach bookmarks for the paragraph
-        // (In future, we might put them in the right place, for now
-        //  we just put them in the correct paragraph)
-        for (int i = 0; i < paragraph.getCTP().sizeOfBookmarkStartArray(); i++) {
-            CTBookmark bookmark = paragraph.getCTP().getBookmarkStartArray(i);
-            xhtml.startElement("a", "name", bookmark.getName());
-            xhtml.endElement("a");
-        }
-
-        Deque<FormattingUtils.Tag> formattingState = new ArrayDeque<>();
-
-        //hyperlinks may or may not have hyperlink ids
-        String lastHyperlinkId = null;
-        boolean inHyperlink = false;
-        // Track field-based hyperlinks (using instrText/fldChar)
-        FieldHyperlinkTracker fieldTracker = new FieldHyperlinkTracker();
-        boolean inFieldHyperlink = false;
-
-        // Do the iruns
-        for (IRunElement run : paragraph.getIRuns()) {
-            // Check for field-based hyperlinks first (instrText HYPERLINK)
-            if (run instanceof XWPFRun) {
-                XWPFRun xwpfRun = (XWPFRun) run;
-                boolean wasInFieldHyperlink = fieldTracker.isInFieldHyperlink();
-                String fieldUrl = extractFieldLinks(xwpfRun, fieldTracker);
-
-                // If we just entered a field hyperlink, open the anchor tag
-                if (fieldUrl != null && !inFieldHyperlink) {
-                    // Close any existing relationship-based hyperlink first
-                    if (inHyperlink) {
-                        FormattingUtils.closeStyleTags(xhtml, formattingState);
-                        xhtml.endElement("a");
-                        inHyperlink = false;
-                        lastHyperlinkId = null;
-                    }
-                    FormattingUtils.closeStyleTags(xhtml, formattingState);
-                    xhtml.startElement("a", "href", fieldUrl);
-                    inFieldHyperlink = true;
-                    metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
-                }
-
-                // If we just exited a field hyperlink, close the anchor tag
-                if (wasInFieldHyperlink && !fieldTracker.isInFieldHyperlink() && inFieldHyperlink) {
-                    FormattingUtils.closeStyleTags(xhtml, formattingState);
-                    xhtml.endElement("a");
-                    inFieldHyperlink = false;
-                }
-
-                // Emit any external refs (INCLUDEPICTURE, INCLUDETEXT, IMPORT, LINK) as anchors
-                if (fieldTracker.getLastExternalRefUrl() != null) {
-                    AttributesImpl extRefAtts = new AttributesImpl();
-                    extRefAtts.addAttribute("", "class", "class", "CDATA",
-                            "external-ref-" + fieldTracker.getLastExternalRefType());
-                    extRefAtts.addAttribute("", "href", "href", "CDATA",
-                            fieldTracker.getLastExternalRefUrl());
-                    xhtml.startElement("a", extRefAtts);
-                    xhtml.endElement("a");
-                    metadata.set(Office.HAS_FIELD_HYPERLINKS, true);
-                    fieldTracker.clearExternalRef();
-                }
-            }
-
-            if (run instanceof XWPFHyperlinkRun) {
-                XWPFHyperlinkRun hyperlinkRun = (XWPFHyperlinkRun) run;
-                if (hyperlinkRun.getHyperlinkId() == null ||
-                        !hyperlinkRun.getHyperlinkId().equals(lastHyperlinkId)) {
-                    if (inHyperlink) {
-                        //close out the old one
-                        FormattingUtils.closeStyleTags(xhtml, formattingState);
-                        xhtml.endElement("a");
-                        inHyperlink = false;
-                    }
-                    lastHyperlinkId = hyperlinkRun.getHyperlinkId();
-                    FormattingUtils.closeStyleTags(xhtml, formattingState);
-                    XWPFHyperlink link = hyperlinkRun.getHyperlink(document);
-                    if (link != null && link.getURL() != null) {
-                        xhtml.startElement("a", "href", link.getURL());
-                        inHyperlink = true;
-                    } else if (hyperlinkRun.getAnchor() != null &&
-                            hyperlinkRun.getAnchor().length() > 0) {
-                        xhtml.startElement("a", "href", "#" + hyperlinkRun.getAnchor());
-                        inHyperlink = true;
-                    }
-                }
-            } else if (inHyperlink) {
-                //if this isn't a hyperlink, but the last one was
-                FormattingUtils.closeStyleTags(xhtml, formattingState);
-                xhtml.endElement("a");
-                lastHyperlinkId = null;
-                inHyperlink = false;
-            }
-
-            if (run instanceof XWPFSDT) {
-                FormattingUtils.closeStyleTags(xhtml, formattingState);
-                processSDTRun((XWPFSDT) run, xhtml);
-                //for now, we're ignoring formatting in sdt
-                //if you hit an sdt reset to false
-            } else {
-                processRun((XWPFRun) run, paragraph, xhtml, formattingState);
-            }
-        }
-        FormattingUtils.closeStyleTags(xhtml, formattingState);
-        if (inHyperlink) {
-            xhtml.endElement("a");
-        }
-        if (inFieldHyperlink) {
-            xhtml.endElement("a");
-        }
-
-
-        // Now do any comments for the paragraph
-        XWPFCommentsDecorator comments = new XWPFCommentsDecorator(paragraph, null);
-        String commentText = comments.getCommentText();
-        if (commentText != null && commentText.length() > 0) {
-            xhtml.characters(commentText);
-        }
-
-        // Also extract any paragraphs embedded in text boxes
-        //Note "w:txbxContent//"...must look for all descendant paragraphs
-        //not just the immediate children of txbxContent -- TIKA-2807
-        if (config.isIncludeShapeBasedContent()) {
-            try {
-                for (XmlObject embeddedParagraph : paragraph.getCTP().selectPath(
-                        "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' declare namespace wps='http://schemas.microsoft.com/office/word/2010/wordprocessingShape' .//*/wps:txbx/w:txbxContent//w:p")) {
-                    extractParagraph(new XWPFParagraph(CTP.Factory.parse(embeddedParagraph.xmlText()),
-                            paragraph.getBody()), listManager, xhtml);
-                }
-            } catch (Exception e) {
-                // XmlException from CTP.Factory.parse — swallow for shape content
-            }
-        }
-
-        // Finish this paragraph
-        xhtml.endElement(tag);
-
-        // Extract footnote/endnote content after the paragraph close tag
-        // to avoid invalid nested block elements (TIKA-4657)
-        extractFootnoteEndnoteContent(paragraph, listManager, xhtml);
-
-        if (headerFooterPolicy != null && config.isIncludeHeadersAndFooters()) {
-            extractFooters(xhtml, headerFooterPolicy, listManager);
-        }
-    }
-
-    private void extractFootnoteEndnoteContent(XWPFParagraph paragraph,
-                                                  XWPFListManager listManager,
-                                                  XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        String nsW = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
-        QName footnoteRefQName = new QName(nsW, "footnoteReference");
-        QName endnoteRefQName = new QName(nsW, "endnoteReference");
-        QName idQName = new QName(nsW, "id");
-        for (XmlObject obj : paragraph.getCTP().selectPath(
-                "declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'"
-                        + " .//w:footnoteReference | .//w:endnoteReference")) {
-            XmlObject idAttr = obj.selectAttribute(idQName);
-            if (idAttr == null) {
-                continue;
-            }
-            int id;
-            try {
-                id = Integer.parseInt(idAttr.getDomNode().getNodeValue());
-            } catch (NumberFormatException e) {
-                continue;
-            }
-            boolean isFootnote = obj.getDomNode().getLocalName().equals("footnoteReference");
-            if (isFootnote) {
-                XWPFFootnote footnote = document.getFootnoteByID(id);
-                if (footnote != null) {
-                    xhtml.startElement("div", "class", "footnote");
-                    extractIBodyText(footnote, listManager, xhtml);
-                    xhtml.endElement("div");
-                }
-            } else {
-                XWPFEndnote endnote = document.getEndnoteByID(id);
-                if (endnote != null) {
-                    xhtml.startElement("div", "class", "endnote");
-                    extractIBodyText(endnote, listManager, xhtml);
-                    xhtml.endElement("div");
-                }
-            }
-        }
-    }
-
-    private void processEmbeddedObjects(List<XWPFRun> runs, XHTMLContentHandler xhtml)
-            throws SAXException {
-        // TODO: replace w/ XPath/XQuery:
-        for (XWPFRun run : runs) {
-            try (XmlCursor c = run.getCTR().newCursor()) {
-                c.selectPath("./*");
-                while (c.toNextSelection()) {
-                    XmlObject o = c.getObject();
-                    if (o instanceof CTObject) {
-                        try (XmlCursor objectCursor = o.newCursor()) {
-                            processObject(objectCursor, xhtml);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void processObject(XmlCursor cursor, XHTMLContentHandler xhtml) throws SAXException {
-
-        cursor.selectPath("./*");
-        String objectRelId = null;
-        String progId = null;
-        EmbeddedPartMetadata embeddedPartMetadata = null;
-        while (cursor.toNextSelection()) {
-            XmlObject o2 = cursor.getObject();
-            XmlObject embedAtt = o2.selectAttribute(new QName("Type"));
-            if (embedAtt != null &&
-                    embedAtt.getDomNode().getNodeValue().equals("Embed")) {
-                //TODO: get ProgID, while we're here?
-                // Type is "Embed"
-                XmlObject relIDAtt = o2.selectAttribute(new QName(
-                        "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
-                        "id"));
-                if (relIDAtt != null) {
-                    objectRelId = relIDAtt.getDomNode().getNodeValue();
-                }
-
-                XmlObject progIDAtt = o2.selectAttribute(new QName("ProgID"));
-                if (progIDAtt != null) {
-                    progId = progIDAtt.getDomNode().getNodeValue();
-                }
-            } else if (o2 instanceof CTShapeImpl) {
-                XmlObject[] imagedata = o2.selectChildren(
-                        new QName("urn:schemas" +
-                                "-microsoft-com:vml","imagedata"));
-                if (imagedata.length > 0) {
-                    XmlObject relIDAtt = imagedata[0].selectAttribute(new QName(
-                            "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
-                            "id"));
-                    if (relIDAtt != null) {
-                        String rid = relIDAtt.getDomNode().getNodeValue();
-                        embeddedPartMetadata = new EmbeddedPartMetadata(rid);
-                        tryToParseEmbeddedName(rid, embeddedPartMetadata);
-                    }
-                }
-            }
-        }
-        if (objectRelId == null) {
-            return;
-        }
-
-        if (embeddedPartMetadata != null) {
-            if (! StringUtils.isBlank(progId)) {
-                embeddedPartMetadata.setProgId(progId);
-            }
-            embeddedPartMetadataMap.put(objectRelId, embeddedPartMetadata);
-        }
-        AttributesImpl attributes = new AttributesImpl();
-        attributes.addAttribute("", "class", "class", "CDATA", "embedded");
-        attributes.addAttribute("", "id", "id", "CDATA", objectRelId);
-        if (embeddedPartMetadata != null && !StringUtils.isBlank(embeddedPartMetadata.getFullName())) {
-            attributes.addAttribute("", "name", "name", "CDATA",
-                    embeddedPartMetadata.getFullName());
-        }
-        xhtml.startElement("div", attributes);
-        xhtml.endElement("div");
-    }
-
-    private String tryToParseEmbeddedName(String rid, EmbeddedPartMetadata embeddedPartMetadata) {
-        //This tries to parse the embedded name out of a comment
-        //field in an emf
-        POIXMLDocumentPart part = document.getRelationById(rid);
-        if (part == null || part.getPackagePart() == null
-                || part.getPackagePart().getContentType() == null) {
-            return null;
-        }
-        PackagePart packagePart = part.getPackagePart();
-        if ("image/x-emf".equals(packagePart.getContentType())) {
-            try (TikaInputStream tis = TikaInputStream.get(packagePart.getInputStream())) {
-                EMFParser p = new EMFParser();
-                Metadata m = Metadata.newInstance(getParseContext());
-                ToTextContentHandler toTextContentHandler = new ToTextContentHandler();
-                p.parse(tis, toTextContentHandler, m, getParseContext());
-                embeddedPartMetadata.setRenderedName(toTextContentHandler.toString().trim());
-                embeddedPartMetadata.setFullName(m.get(EMFParser.EMF_ICON_STRING));
-            } catch (SecurityException e) {
-                throw e;
-            } catch (Exception e) {
-                //we tried
-            }
-        }
-        return null;
-    }
-
-    private void writeParagraphNumber(XWPFParagraph paragraph, XWPFListManager listManager,
-                                      XHTMLContentHandler xhtml) throws SAXException {
-        if (paragraph.getNumIlvl() == null) {
-            return;
-        }
-        String number = listManager.getFormattedNumber(paragraph);
-        if (number != null) {
-            xhtml.characters(number);
-        }
-
-    }
-
-    private void processRun(XWPFRun run, XWPFParagraph paragraph, XHTMLContentHandler xhtml,
-                            Deque<FormattingUtils.Tag> formattingState)
-            throws SAXException, IOException {
-        // open/close required tags if run changes formatting
-        FormattingUtils.ensureFormattingState(xhtml, FormattingUtils.toTags(run), formattingState);
-
-        if (config.isConcatenatePhoneticRuns()) {
-            xhtml.characters(run.toString());
-        } else {
-            xhtml.characters(run.text());
-        }
-
-        // If we have any pictures, output them
-        for (XWPFPicture picture : run.getEmbeddedPictures()) {
-            if (paragraph.getDocument() != null) {
-                XWPFPictureData data = picture.getPictureData();
-                if (data != null) {
-                    AttributesImpl attr = new AttributesImpl();
-
-                    attr.addAttribute("", "src", "src", "CDATA", "embedded:" + data.getFileName());
-                    attr.addAttribute("", "alt", "alt", "CDATA", picture.getDescription());
-
-                    xhtml.startElement("img", attr);
-                    xhtml.endElement("img");
-                }
-            }
-        }
-    }
-
-    private void processSDTRun(XWPFSDT run, XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        xhtml.characters(run.getContent().getText());
-    }
-
-    /**
-     * Extracts field-based hyperlinks from a run by examining fldChar and instrText elements.
-     * This handles HYPERLINK field codes that are not relationship-based.
-     *
-     * @param run the run to examine
-     * @param tracker the field hyperlink tracker maintaining state across runs
-     * @return the hyperlink URL if this run starts a hyperlink, null otherwise
-     */
-    private String extractFieldLinks(XWPFRun run, FieldHyperlinkTracker tracker) {
-        CTR ctr = run.getCTR();
-        try (XmlCursor cursor = ctr.newCursor()) {
-            if (cursor.toFirstChild()) {
-                do {
-                    String localName = cursor.getName().getLocalPart();
-                    if ("fldChar".equals(localName)) {
-                        XmlObject obj = cursor.getObject();
-                        if (obj instanceof CTFldChar) {
-                            CTFldChar fldChar = (CTFldChar) obj;
-                            STFldCharType.Enum fldType = fldChar.getFldCharType();
-                            if (fldType == STFldCharType.BEGIN) {
-                                tracker.startField();
-                            } else if (fldType == STFldCharType.SEPARATE) {
-                                return tracker.separate();
-                            } else if (fldType == STFldCharType.END) {
-                                tracker.endField();
-                            }
-                        }
-                    } else if ("instrText".equals(localName)) {
-                        XmlObject obj = cursor.getObject();
-                        if (obj instanceof CTText) {
-                            CTText text = (CTText) obj;
-                            tracker.addInstrText(text.getStringValue());
-                        }
-                    }
-                } while (cursor.toNextSibling());
-            }
-        }
-        return null;
-    }
-
-    private void extractTable(XWPFTable table, XWPFListManager listManager,
-                              XHTMLContentHandler xhtml)
-            throws SAXException, IOException {
-        xhtml.startElement("table");
-        xhtml.startElement("tbody");
-        for (XWPFTableRow row : table.getRows()) {
-            xhtml.startElement("tr");
-            for (ICell cell : row.getTableICells()) {
-                xhtml.startElement("td");
-                if (cell instanceof XWPFTableCell) {
-                    extractIBodyText((XWPFTableCell) cell, listManager, xhtml);
-                } else if (cell instanceof XWPFSDTCell) {
-                    xhtml.characters(((XWPFSDTCell) cell).getContent().getText());
-                }
-                xhtml.endElement("td");
-            }
-            xhtml.endElement("tr");
-        }
-        xhtml.endElement("tbody");
-        xhtml.endElement("table");
-    }
-
-    private void extractFooters(XHTMLContentHandler xhtml, XWPFHeaderFooterPolicy hfPolicy,
-                                XWPFListManager listManager)
-            throws SAXException, IOException {
-        // footers
-        if (hfPolicy.getFirstPageFooter() != null) {
-            extractHeaderText(xhtml, hfPolicy.getFirstPageFooter(), listManager);
-        }
-        if (hfPolicy.getEvenPageFooter() != null) {
-            extractHeaderText(xhtml, hfPolicy.getEvenPageFooter(), listManager);
-        }
-        if (hfPolicy.getDefaultFooter() != null) {
-            extractHeaderText(xhtml, hfPolicy.getDefaultFooter(), listManager);
-        }
-    }
-
-    private void extractHeaders(XHTMLContentHandler xhtml, XWPFHeaderFooterPolicy hfPolicy,
-                                XWPFListManager listManager)
-            throws SAXException, IOException {
-        if (hfPolicy == null) {
-            return;
-        }
-
-        if (hfPolicy.getFirstPageHeader() != null) {
-            extractHeaderText(xhtml, hfPolicy.getFirstPageHeader(), listManager);
-        }
-
-        if (hfPolicy.getEvenPageHeader() != null) {
-            extractHeaderText(xhtml, hfPolicy.getEvenPageHeader(), listManager);
-        }
-
-        if (hfPolicy.getDefaultHeader() != null) {
-            extractHeaderText(xhtml, hfPolicy.getDefaultHeader(), listManager);
-        }
-    }
-
-    private void extractHeaderText(XHTMLContentHandler xhtml, XWPFHeaderFooter header,
-                                   XWPFListManager listManager)
-            throws SAXException, IOException {
-
-        for (IBodyElement e : header.getBodyElements()) {
-            if (e instanceof XWPFParagraph) {
-                extractParagraph((XWPFParagraph) e, listManager, xhtml);
-            } else if (e instanceof XWPFTable) {
-                extractTable((XWPFTable) e, listManager, xhtml);
-            } else if (e instanceof XWPFSDT) {
-                extractSDT((XWPFSDT) e, xhtml);
-            }
-        }
-    }
-
-    /**
-     * Include main body and anything else that can
-     * have an attachment/embedded object
-     */
-    @Override
-    protected List<PackagePart> getMainDocumentParts() {
-        List<PackagePart> parts = new ArrayList<>();
-        parts.add(document.getPackagePart());
-        addRelatedParts(document.getPackagePart(), parts);
-        return parts;
-    }
-
-    private void addRelatedParts(PackagePart documentPart, List<PackagePart> relatedParts) {
-        for (String relation : MAIN_PART_RELATIONS) {
-            PackageRelationshipCollection prc = null;
-            try {
-                prc = documentPart.getRelationshipsByType(relation);
-                if (prc != null) {
-                    for (int i = 0; i < prc.size(); i++) {
-                        PackagePart packagePart =
-                                documentPart.getRelatedPart(prc.getRelationship(i));
-                        relatedParts.add(packagePart);
-                    }
-                }
-            } catch (InvalidFormatException e) {
-                //swallow
-            }
-        }
-
-    }
-
-}
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/xps/XPSExtractorDecorator.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xps/XPSExtractorDecorator.java
index ca7eec664d..e7d0a15cf1 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xps/XPSExtractorDecorator.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xps/XPSExtractorDecorator.java
@@ -26,8 +26,7 @@ import java.util.Map;
 
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.io.IOUtils;
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
+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;
@@ -57,12 +56,12 @@ public class XPSExtractorDecorator extends AbstractOOXMLExtractor {
     private final ZipPackage pkg;
     Map<String, Metadata> embeddedImages = new LinkedHashMap<>();
 
-    public XPSExtractorDecorator(ParseContext context, POIXMLTextExtractor extractor)
+    public XPSExtractorDecorator(ParseContext context, OPCPackage opcPackage)
             throws TikaException {
-        super(context, extractor);
+        super(context, opcPackage);
         this.context = context;
-        if (extractor.getPackage() instanceof ZipPackage) {
-            this.pkg = (ZipPackage) extractor.getPackage();
+        if (opcPackage instanceof ZipPackage) {
+            this.pkg = (ZipPackage) opcPackage;
         } else {
             throw new TikaException("OPCPackage must be a ZipPackage");
         }
@@ -88,11 +87,6 @@ public class XPSExtractorDecorator extends AbstractOOXMLExtractor {
         return TikaInputStream.get(zipEntrySource.getInputStream(zipEntry));
     }
 
-    @Override
-    public POIXMLDocument getDocument() {
-        return null;
-    }
-
     @Override
     protected void buildXHTML(XHTMLContentHandler xhtml) throws SAXException, IOException {
 
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/xps/XPSTextExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xps/XPSTextExtractor.java
deleted file mode 100644
index a05698d969..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/ooxml/xps/XPSTextExtractor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.xps;
-
-
-import java.io.Closeable;
-import java.io.IOException;
-
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.POIXMLProperties;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
-import org.apache.poi.openxml4j.opc.OPCPackage;
-
-/**
- * Currently, mostly a pass-through class to hold pkg and properties
- * and keep the general framework similar to our other POI-integrated
- * extractors.
- */
-public class XPSTextExtractor implements POIXMLTextExtractor {
-
-    private final OPCPackage pkg;
-    private final POIXMLProperties properties;
-
-    public XPSTextExtractor(OPCPackage pkg) throws OpenXML4JException, IOException {
-        this.pkg = pkg;
-        try {
-            this.properties = new POIXMLProperties(pkg);
-        } catch (Exception e) {
-            throw new IOException("Failed to read OOXML properties", e);
-        }
-    }
-
-    @Override
-    public OPCPackage getPackage() {
-        return pkg;
-    }
-
-    @Override
-    public String getText() {
-        return null;
-    }
-
-    @Override
-    public void setCloseFilesystem(boolean b) {
-
-    }
-
-    @Override
-    public boolean isCloseFilesystem() {
-        return false;
-    }
-
-    @Override
-    public Closeable getFilesystem() {
-        return null;
-    }
-
-    public POIXMLProperties.CoreProperties getCoreProperties() {
-        return this.properties.getCoreProperties();
-    }
-
-    public POIXMLProperties.ExtendedProperties getExtendedProperties() {
-        return this.properties.getExtendedProperties();
-    }
-
-    public POIXMLProperties.CustomProperties getCustomProperties() {
-        return this.properties.getCustomProperties();
-    }
-
-    @Override
-    public POIXMLDocument getDocument() {
-        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/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 3de987e67d..52d554a940 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
@@ -16,219 +16,21 @@
  */
 package org.apache.tika.parser.microsoft.ooxml.xslf;
 
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Date;
-
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.POIXMLProperties;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 
-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 {
-
+/**
+ * Lightweight holder for an {@link OPCPackage} for PPTX files.
+ * Content extraction is handled by {@code SXSLFPowerPointExtractorDecorator}.
+ */
+public class XSLFEventBasedPowerPointExtractor {
 
-    private OPCPackage container;
-    private POIXMLProperties properties;
+    private final OPCPackage container;
 
-    public XSLFEventBasedPowerPointExtractor(OPCPackage container)
-            throws OpenXML4JException, IOException {
+    public XSLFEventBasedPowerPointExtractor(OPCPackage container) {
         this.container = container;
-        try {
-            this.properties = new POIXMLProperties(container);
-        } catch (Exception e) {
-            throw new IOException("Failed to read OOXML properties", e);
-        }
     }
 
     public OPCPackage getPackage() {
         return this.container;
     }
-
-    public POIXMLProperties.CoreProperties getCoreProperties() {
-        return this.properties.getCoreProperties();
-    }
-
-    public POIXMLProperties.ExtendedProperties getExtendedProperties() {
-        return this.properties.getExtendedProperties();
-    }
-
-    public POIXMLProperties.CustomProperties getCustomProperties() {
-        return this.properties.getCustomProperties();
-    }
-
-    @Override
-    public POIXMLDocument getDocument() {
-        return null;
-    }
-
-
-    @Override
-    public String getText() {
-        //TODO
-        return "";
-    }
-
-    @Override
-    public void setCloseFilesystem(boolean b) {
-
-    }
-
-    @Override
-    public boolean isCloseFilesystem() {
-        return false;
-    }
-
-    @Override
-    public Closeable getFilesystem() {
-        return null;
-    }
-
-    @Override
-    public void close() throws IOException {
-        getPackage().revert();
-    }
-
-    private static class XSLFToTextContentHandler
-            implements XWPFBodyContentsHandler {
-        private final StringBuilder buffer;
-
-        public XSLFToTextContentHandler(StringBuilder buffer) {
-            this.buffer = buffer;
-        }
-
-        @Override
-        public void run(RunProperties runProperties, String contents) {
-            buffer.append(contents);
-        }
-
-        @Override
-        public void hyperlinkStart(String link) {
-            //no-op
-        }
-
-        @Override
-        public void hyperlinkEnd() {
-            //no-op
-        }
-
-        @Override
-        public void startParagraph(ParagraphProperties paragraphProperties) {
-            //no-op
-        }
-
-        @Override
-        public void endParagraph() {
-            buffer.append("\n");
-        }
-
-        @Override
-        public void startTable() {
-
-        }
-
-        @Override
-        public void endTable() {
-
-        }
-
-        @Override
-        public void startTableRow() {
-
-        }
-
-        @Override
-        public void endTableRow() {
-            buffer.append("\n");
-        }
-
-        @Override
-        public void startTableCell() {
-
-        }
-
-        @Override
-        public void endTableCell() {
-            buffer.append("\t");
-        }
-
-        @Override
-        public void startSDT() {
-
-        }
-
-        @Override
-        public void endSDT() {
-
-        }
-
-        @Override
-        public void startEditedSection(String editor, Date date,
-                                       EditType editType) {
-
-        }
-
-        @Override
-        public void endEditedSection() {
-
-        }
-
-        @Override
-        public boolean isIncludeDeletedText() {
-            return false;
-        }
-
-        @Override
-        public void footnoteReference(String id) {
-
-        }
-
-        @Override
-        public void endnoteReference(String id) {
-
-        }
-
-        @Override
-        public void commentReference(String id) {
-
-        }
-
-        @Override
-        public boolean isIncludeMoveFromText() {
-            return false;
-        }
-
-
-        @Override
-        public void embeddedOLERef(String refId, String progId, String emfImageRId) {
-            //no-op
-        }
-
-        @Override
-        public void linkedOLERef(String refId) {
-            //no-op
-        }
-
-        @Override
-        public void embeddedPicRef(String picFileName, String picDescription) {
-            //no-op
-        }
-
-        @Override
-        public void startBookmark(String id, String name) {
-
-        }
-
-        @Override
-        public void endBookmark(String 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/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 74976ea34e..16c166a6ee 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
@@ -16,70 +16,23 @@
  */
 package org.apache.tika.parser.microsoft.ooxml.xwpf;
 
-import java.io.Closeable;
 import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
-import org.apache.poi.ooxml.POIXMLDocument;
-import org.apache.poi.ooxml.POIXMLProperties;
-import org.apache.poi.ooxml.extractor.POIXMLTextExtractor;
-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.xwpf.usermodel.XWPFRelation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.SAXException;
-
-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.AbstractOOXMLExtractor;
-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;
-
-//TODO: move this into POI?
 
 /**
- * Experimental class that is based on POI's XSSFEventBasedExcelExtractor
+ * Lightweight holder for an {@link OPCPackage} for DOCX files.
+ * Content extraction is handled by {@code SXWPFWordExtractorDecorator}.
  */
-public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
-
-    private static final Logger LOG = LoggerFactory.getLogger(XWPFEventBasedWordExtractor.class);
+public class XWPFEventBasedWordExtractor {
 
-    private OPCPackage container;
-    private POIXMLProperties properties;
+    private final OPCPackage container;
     private boolean includeGlossary = true;
 
     public XWPFEventBasedWordExtractor(OPCPackage container)
             throws OpenXML4JException, IOException {
         this.container = 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() {
@@ -90,334 +43,7 @@ public class XWPFEventBasedWordExtractor implements POIXMLTextExtractor {
         this.includeGlossary = includeGlossary;
     }
 
-    public POIXMLProperties.CoreProperties getCoreProperties() {
-        POIXMLProperties props = getOrCreateProperties();
-        return props != null ? props.getCoreProperties() : null;
-    }
-
-    public POIXMLProperties.ExtendedProperties getExtendedProperties() {
-        POIXMLProperties props = getOrCreateProperties();
-        return props != null ? props.getExtendedProperties() : null;
-    }
-
-    public POIXMLProperties.CustomProperties getCustomProperties() {
-        POIXMLProperties props = getOrCreateProperties();
-        return props != null ? props.getCustomProperties() : null;
-    }
-
-    @Override
-    public POIXMLDocument getDocument() {
-        return null;
-    }
-
-
-    @Override
-    public String getText() {
-        StringBuilder sb = new StringBuilder();
-        //handle main document
-        List<PackagePart> pps =
-                container.getPartsByContentType(XWPFRelation.DOCUMENT.getContentType());
-        if (pps != null) {
-            for (PackagePart pp : pps) {
-                //likely only one, but why not...
-                try {
-                    handleDocumentPart(pp, sb);
-                } catch (IOException e) {
-                    LOG.warn("IOException handling document part", e);
-                } catch (SAXException e) {
-                    if (WriteLimitReachedException.isWriteLimitReached(e)) {
-                        throw new RuntimeSAXException(e);
-                    }
-                    //swallow this because we don't actually call it
-                    LOG.warn("SAXException handling document part", e);
-                } catch (TikaException e) {
-                    LOG.warn("ParseException handling document part", e);
-                }
-            }
-        }
-        //handle glossary document
-        if (includeGlossary) {
-            pps = container.getPartsByContentType(
-                    XWPFRelation.GLOSSARY_DOCUMENT.getContentType());
-
-            if (pps != null) {
-                for (PackagePart pp : pps) {
-                    //likely only one, but why not...
-                    try {
-                        handleDocumentPart(pp, sb);
-                    } catch (IOException e) {
-                        LOG.warn("IOException handling glossary document part", e);
-                    } catch (SAXException e) {
-                        if (WriteLimitReachedException.isWriteLimitReached(e)) {
-                            throw new RuntimeSAXException(e);
-                        }
-                        //swallow this because we don't actually call it
-                        LOG.warn("SAXException handling glossary document part", e);
-                    } catch (TikaException e) {
-                        LOG.warn("ParseException handling document part", e);
-                    }
-                }
-            }
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public void setCloseFilesystem(boolean b) {
-
-    }
-
-    @Override
-    public boolean isCloseFilesystem() {
-        return false;
-    }
-
-    @Override
-    public Closeable getFilesystem() {
-        return null;
-    }
-
-
-    private void handleDocumentPart(PackagePart documentPart, StringBuilder sb)
-            throws IOException, SAXException, TikaException {
-        //load the numbering/list manager and styles from the main document part
-        XWPFNumberingShim numbering = loadNumbering(documentPart);
-        XWPFListManager xwpfListManager = new XWPFListManager(
-                numbering != null ? numbering : XWPFNumberingShim.EMPTY);
-        //TODO: XWPFStyles styles = loadStyles(documentPart);
-
-        //headers
-        try {
-            PackageRelationshipCollection headersPRC =
-                    documentPart.getRelationshipsByType(XWPFRelation.HEADER.getRelation());
-            if (headersPRC != null) {
-                for (int i = 0; i < headersPRC.size(); i++) {
-                    PackagePart header = AbstractOOXMLExtractor.safeGetRelatedPart(
-                            documentPart, headersPRC.getRelationship(i));
-                    if (header == null) {
-                        continue;
-                    }
-                    handlePart(header, xwpfListManager, sb);
-                }
-            }
-        } catch (InvalidFormatException e) {
-            LOG.warn("Invalid format", e);
-        }
-
-        //main document
-        handlePart(documentPart, xwpfListManager, sb);
-
-        //for now, just dump other components at end
-        for (XWPFRelation rel : new XWPFRelation[]{XWPFRelation.FOOTNOTE, XWPFRelation.COMMENT,
-                XWPFRelation.FOOTER, XWPFRelation.ENDNOTE}) {
-            try {
-                PackageRelationshipCollection prc =
-                        documentPart.getRelationshipsByType(rel.getRelation());
-                if (prc != null) {
-                    for (int i = 0; i < prc.size(); i++) {
-                        PackagePart packagePart =
-                                AbstractOOXMLExtractor.safeGetRelatedPart(
-                                        documentPart, prc.getRelationship(i));
-                        if (packagePart == null) {
-                            continue;
-                        }
-                        handlePart(packagePart, xwpfListManager, sb);
-                    }
-                }
-            } catch (InvalidFormatException e) {
-                LOG.warn("Invalid format", e);
-            }
-        }
-    }
-
-    private void handlePart(PackagePart packagePart, XWPFListManager xwpfListManager,
-                            StringBuilder buffer) throws IOException, SAXException, TikaException {
-
-        Map<String, String> hyperlinks = loadHyperlinkRelationships(packagePart);
-        try (InputStream stream = packagePart.getInputStream()) {
-            XMLReaderUtils.parseSAX(stream,
-                    new OOXMLWordAndPowerPointTextHandler(new XWPFToTextContentHandler(buffer),
-                    hyperlinks), new ParseContext());
-        }
-
-    }
-
-    private Map<String, String> loadHyperlinkRelationships(PackagePart bodyPart) {
-        Map<String, String> hyperlinks = new HashMap<>();
-        try {
-            PackageRelationshipCollection prc =
-                    bodyPart.getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation());
-            for (int i = 0; i < prc.size(); i++) {
-                PackageRelationship pr = prc.getRelationship(i);
-                if (pr == null) {
-                    continue;
-                }
-                String id = pr.getId();
-                String url = (pr.getTargetURI() == null) ? null : pr.getTargetURI().toString();
-                if (id != null && url != null) {
-                    hyperlinks.put(id, url);
-                }
-            }
-        } catch (InvalidFormatException e) {
-            LOG.warn("Invalid format", e);
-        }
-        return hyperlinks;
-    }
-
-    private XWPFNumberingShim loadNumbering(PackagePart packagePart) {
-        try {
-            PackageRelationshipCollection numberingParts =
-                    packagePart.getRelationshipsByType(XWPFRelation.NUMBERING.getRelation());
-            if (numberingParts.size() > 0) {
-                PackageRelationship numberingRelationShip = numberingParts.getRelationship(0);
-                if (numberingRelationShip == null) {
-                    return null;
-                }
-                PackagePart numberingPart = container.getPart(numberingRelationShip);
-                if (numberingPart == null) {
-                    return null;
-                }
-                return new XWPFNumberingShim(numberingPart, new ParseContext());
-            }
-        } catch (Exception e) {
-            LOG.warn("Couldn't load numbering", e);
-        }
-        return null;
-    }
-
-    private static class XWPFToTextContentHandler
-            implements XWPFBodyContentsHandler {
-        private final StringBuilder buffer;
-
-        public XWPFToTextContentHandler(StringBuilder buffer) {
-            this.buffer = buffer;
-        }
-
-        @Override
-        public void run(RunProperties runProperties, String contents) {
-            buffer.append(contents);
-        }
-
-        @Override
-        public void hyperlinkStart(String link) {
-            //no-op
-        }
-
-        @Override
-        public void hyperlinkEnd() {
-            //no-op
-        }
-
-        @Override
-        public void startParagraph(ParagraphProperties paragraphProperties) {
-            //no-op
-        }
-
-        @Override
-        public void endParagraph() {
-            buffer.append("\n");
-        }
-
-        @Override
-        public void startTable() {
-
-        }
-
-        @Override
-        public void endTable() {
-
-        }
-
-        @Override
-        public void startTableRow() {
-
-        }
-
-        @Override
-        public void endTableRow() {
-            buffer.append("\n");
-        }
-
-        @Override
-        public void startTableCell() {
-
-        }
-
-        @Override
-        public void endTableCell() {
-            buffer.append("\t");
-        }
-
-        @Override
-        public void startSDT() {
-
-        }
-
-        @Override
-        public void endSDT() {
-            buffer.append("\n");
-        }
-
-        @Override
-        public void startEditedSection(String editor, Date date,
-                                       EditType editType) {
-
-        }
-
-        @Override
-        public void endEditedSection() {
-
-        }
-
-        @Override
-        public boolean isIncludeDeletedText() {
-            return true;
-        }
-
-        @Override
-        public void footnoteReference(String id) {
-
-        }
-
-        @Override
-        public void endnoteReference(String id) {
-
-        }
-
-        @Override
-        public void commentReference(String id) {
-
-        }
-
-        @Override
-        public boolean isIncludeMoveFromText() {
-            return false;
-        }
-
-        @Override
-        public void embeddedOLERef(String refId, String progId, String emfImageRId) {
-            //no-op
-        }
-
-        @Override
-        public void linkedOLERef(String refId) {
-            //no-op
-        }
-
-        @Override
-        public void embeddedPicRef(String picFileName, String picDescription) {
-            //no-op
-        }
-
-        @Override
-        public void startBookmark(String id, String name) {
-            //no-op
-        }
-
-        @Override
-        public void endBookmark(String id) {
-            //no-op
-        }
+    public boolean isIncludeGlossary() {
+        return includeGlossary;
     }
 }
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/AbstractOOXMLDocxTest.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
deleted file mode 100644
index 7407e169c6..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxTest.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * 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 java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-import org.junit.jupiter.api.Test;
-
-import org.apache.tika.TikaTest;
-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.parser.ParseContext;
-import org.apache.tika.parser.microsoft.OfficeParserConfig;
-
-public abstract class AbstractOOXMLDocxTest extends TikaTest {
-
-    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;
-    }
-
-    /**
-     * Test the plain text output of the Word converter
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testWord() throws Exception {
-
-        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));
-        assertEquals("Keith Bennett", xmlResult.metadata.get(TikaCoreProperties.CREATOR));
-        assertTrue(xmlResult.xml.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", 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", 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);
-    }
-
-    /**
-     * 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", getParseContext());
-        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
-        assertContains("<h1>Heading Level 1</h1>", xml);
-        assertTrue(xml.contains("<h2>Heading Level 2</h2>"));
-        // Headings with anchor tags in them
-        // 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>"));
-        // 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", getParseContext());
-        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>"));
-    }
-
-    /**
-     * Test that we can extract image from docx header
-     */
-    @Test
-    public void testWordPicturesInHeader() throws Exception {
-        List<Metadata> metadataList = getRecursiveMetadata("headerPic.docx", getParseContext());
-        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 docx without headers
-     * TIKA-633
-     */
-    @Test
-    public void testNullHeaders() throws Exception {
-        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 testMissingText() throws Exception {
-
-        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);
-
-    }
-
-    @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);
-    }
-
-    @Test
-    public void testLongForIntExceptionInSummaryDetails() throws Exception {
-        //TIKA-2055
-        assertContains("bold",
-                getXML("testWORD_totalTimeOutOfRange.docx", getParseContext()).xml);
-    }
-
-    @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", getParseContext()).metadata;
-
-        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"));
-    }
-
-    // TIKA-989:
-    @Test
-    public void testEmbeddedPDF() throws Exception {
-        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");
-        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-1006
-    @Test
-    public void testWordNullStyle() throws Exception {
-        String xml = getXML("testWORD_null_style.docx", getParseContext()).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 {
-        assertContains("This is a piece of text that causes an exception",
-                getXML("testWORD_no_format.docx", getParseContext()).xml);
-    }
-
-    // TIKA-1005:
-    @Test
-    public void testTextInsideTextBox() throws Exception {
-        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-2807
-    @Test
-    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", getParseContext()).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 testDiagramData() throws Exception {
-        assertContains("From here",
-                getXML("testWORD_diagramData.docx", getParseContext()).xml);
-    }
-
-    @Test
-    public void testDOCXChartData() throws Exception {
-        String xml = getXML("testWORD_charts.docx", getParseContext()).xml;
-        assertContains("peach", xml);
-        assertContains("March\tApril", xml);
-        assertNotContained("chartSpace", xml);
-    }
-
-    @Test
-    public void testDOCXPhoneticStrings() throws Exception {
-        assertContains("\u6771\u4EAC (\u3068\u3046\u304D\u3087\u3046)",
-                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 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);
-        }
-
-        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);
-        assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
-        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 extraction of field-based hyperlinks using instrText/fldChar.
-     * These are hyperlinks embedded as field codes rather than relationship-based hyperlinks.
-     */
-    @Test
-    public void testInstrTextHyperlink() throws Exception {
-        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).
-     * These can be used to hide malicious URLs in documents.
-     */
-    @Test
-    public void testExternalRefFieldCodes() throws Exception {
-        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", getParseContext()).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);
-    }
-
-    @Test
-    public void testTextDecoration() throws Exception {
-        String xml = getXML("testWORD_various.docx", getParseContext()).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", 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);
-    }
-
-    // TIKA-692
-    @Test
-    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);
-
-        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);
-    }
-
-    //TIKA-2346
-    @Test
-    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);
-    }
-
-    @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
-    public void testFeatureExtraction() throws Exception {
-        List<Metadata> metadataList =
-                getRecursiveMetadata("testWORD_features.docx", getParseContext());
-        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 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/AbstractOOXMLPptxTest.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
deleted file mode 100644
index c2f79be3fe..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxTest.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * 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 java.util.List;
-
-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.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.OfficeParserConfig;
-import org.apache.tika.sax.BodyContentHandler;
-
-public abstract class AbstractOOXMLPptxTest extends TikaTest {
-
-    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;
-    }
-
-    /**
-     * 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();
-
-            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
-                AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
-
-                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));
-
-                String content = handler.toString();
-                // 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() {
-                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));
-
-                }
-
-            };
-
-            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
-                AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
-            }
-        }
-    }
-
-    /**
-     * 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);
-            ContentHandler handler = new BodyContentHandler();
-
-            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
-                AUTO_DETECT_PARSER.parse(tis, handler, metadata, getParseContext());
-
-                // Should get the metadata
-                assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
-                        "Mime-type checking for " + filename);
-
-                // But that's about it
-            }
-        }
-    }
-
-    @Test
-    public void testVariousPPTX() throws Exception {
-        Metadata metadata = new Metadata();
-        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);
-        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("<p>Bullet " + row, xml);
-        }
-        assertContains("Here is a numbered list:", xml);
-        for (int row = 1; row <= 3; row++) {
-            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);
-
-        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 testCommentPPTX() throws Exception {
-        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", getParseContext()).xml);
-    }
-
-    @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.
-        ParseContext context = getParseContext();
-        OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
-        config.setIncludeSlideMasterContent(false);
-        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 {
-        assertContains("Text that I added to the master slide",
-                getXML("testPPT_masterText.pptx", getParseContext()).xml);
-
-        //now test turning off master content
-        ParseContext context = getParseContext();
-        OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
-        config.setIncludeSlideMasterContent(false);
-
-        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", getParseContext()).xml);
-
-        //now test turning off master content
-        ParseContext context = getParseContext();
-        OfficeParserConfig config = getOrCreateOfficeParserConfig(context);
-        config.setIncludeSlideMasterContent(false);
-
-        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", getParseContext()).xml);
-    }
-
-    //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", 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", 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\" />");
-        assertTrue(a != -1);
-        assertTrue(b != -1);
-        assertTrue(a < b);
-    }
-
-    @Test
-    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", getParseContext()).xml;
-        assertContains("peach", xml);
-        assertContains("March\tApril", xml);
-        assertNotContained("chartSpace", xml);
-    }
-
-    @Test
-    public void testPPTXGroups() throws Exception {
-        List<Metadata> metadataList =
-                getRecursiveMetadata("testPPT_groups.pptx", getParseContext());
-        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 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
deleted file mode 100644
index 5d9fd42ec6..0000000000
--- 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
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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-&gt;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
index ac533400e0..173922f388 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXTest.java
@@ -16,21 +16,27 @@
  */
 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.assertThrows;
 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.Map;
 
 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.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.OfficeOpenXMLCore;
@@ -42,31 +48,465 @@ 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.).
+ * Tests for the DOCX parser.
  */
-public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
+public class OOXMLDocxSAXTest extends TikaTest {
 
-    @Override
-    ParseContext getParseContext() {
+    // ---- tests from AbstractOOXMLDocxTest ----
+
+    @Test
+    public void testWord() throws Exception {
+        XMLResult xmlResult = getXML("testWORD.docx");
+        assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+                xmlResult.metadata.get(Metadata.CONTENT_TYPE));
+        assertEquals("Sample Word Document", xmlResult.metadata.get(TikaCoreProperties.TITLE));
+        assertEquals("Keith Bennett", xmlResult.metadata.get(TikaCoreProperties.CREATOR));
+        assertTrue(xmlResult.xml.contains("Sample Word Document"));
+    }
+
+    @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"));
+        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);
+        assertContains("<div class=\"endnote\">", xmlResult.xml);
+    }
+
+    @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"));
+        assertTrue(xml.contains("<h1 class=\"title\">"));
+        assertContains("<h1>Heading Level 1</h1>", xml);
+        assertTrue(xml.contains("<h2>Heading Level 2</h2>"));
+        assertContains("Heading Level 3", xml);
+        assertContains("<a name=\"OnLevel3\" />", xml);
+        assertTrue(xml.contains("<b>BOLD</b>"));
+        assertTrue(xml.contains("<i>ITALIC</i>"));
+        assertTrue(xml.contains("<table>"));
+        assertTrue(xml.contains("<td>"));
+        assertTrue(xml.contains("<a href=\"http://tika.apache.org/\">Tika</a>"));
+        assertContains("<a href=\"#OnMainHeading\">The Main Heading Bookmark</a>", xml);
+        assertTrue(xml.contains("<p class=\"signature\">This one"));
+
+        result = getXML("testWORD_3imgs.docx");
+        xml = result.xml;
+
+        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);
+        assertTrue(xml.contains("<p>The end!</p>"));
+    }
+
+    @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));
+        assertTrue(mainContent.contains("<img"));
+    }
+
+    @Test
+    public void testNullHeaders() throws Exception {
+        XMLResult xmlResult = getXML("NullHeader.docx");
+        assertEquals(false, xmlResult.xml.isEmpty(), "Should have found some text");
+    }
+
+    @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);
+    }
+
+    @Test
+    public void testBoldHyperlink() throws Exception {
+        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 {
+        assertContains("bold",
+                getXML("testWORD_totalTimeOutOfRange.docx").xml);
+    }
+
+    @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 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 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 testWordCustomProperties() throws Exception {
+        Metadata metadata = getXML("testWORD_custom_props.docx").metadata;
+        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 testEmbeddedPDF() throws Exception {
+        String xml = getXML("testWORD_embedded_pdf.docx").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");
+        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);
+    }
+
+    @Test
+    public void testWordNullStyle() throws Exception {
+        String xml = getXML("testWORD_null_style.docx").xml;
+        assertContains("Test av styrt dokument", xml);
+    }
+
+    @Test
+    public void testNoFormat() throws Exception {
+        assertContains("This is a piece of text that causes an exception",
+                getXML("testWORD_no_format.docx").xml);
+    }
+
+    @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);
+    }
+
+    @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);
+    }
+
+    @Test
+    public void testDOCXOverrideParagraphNumbering() throws Exception {
+        String xml = getXML("testWORD_override_list_numbering.docx").xml;
+
+        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);
+
+        assertContains("1.a.I 1.a.I", xml);
+        assertContains("<p>1.b.III 1.b.III</p>", xml);
+        assertContains("2.a.I 2.a.I", xml);
+        assertContains("<p>2.b 2.b</p>", xml);
+
+        assertContains("(1)) (1))", xml);
+        assertContains("2.17 2.17", xml);
+        assertContains("2.18.2.1 2.18.2.1", xml);
+        assertContains("<p>2 2</p>", xml);
+
+        assertContains("<p>1 1</p>", xml);
+        assertContains("<p>A A</p>", xml);
+        assertContains("<p>B B</p>", xml);
+        assertContains("<p>C C</p>", xml);
+        assertContains("<p>4 4</p>", xml);
+
+        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 testDiagramData() throws Exception {
+        assertContains("From here",
+                getXML("testWORD_diagramData.docx").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 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();
-        OfficeParserConfig officeParserConfig = new OfficeParserConfig();
-        officeParserConfig.setUseSAXDocxExtractor(true);
-        parseContext.set(OfficeParserConfig.class, officeParserConfig);
-        return parseContext;
+        parseContext.set(OfficeParserConfig.class, config);
+        String xml = getXML("testWORD_phonetic.docx", parseContext).xml;
+        assertContains("\u6771\u4EAC", xml);
+        assertNotContained("\u3068", xml);
+    }
+
+    @Test
+    public void testVarious() throws Exception {
+        Metadata metadata = new Metadata();
+        String content = getText("testWORD_various.docx", metadata);
+        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("Bullet " + row, content);
+        }
+        assertContains("Here is a numbered list:", content);
+        for (int row = 1; row <= 3; row++) {
+            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);
+        assertEquals("Subject is here", metadata.get(DublinCore.SUBJECT));
+        assertContains("Subject is here",
+                Arrays.asList(metadata.getValues(TikaCoreProperties.SUBJECT)));
+        assertContains("Suddenly some Japanese text:", content);
+        assertContains("\uff08\uff27\uff28\uff31\uff09", content);
+        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 testInstrTextHyperlink() throws Exception {
+        String xml = getXML("testInstrLink.docx").xml;
+        assertContains("<a href=\"https://exmaple.com/file\">", xml);
+        assertContains("Access Document(s)", xml);
+    }
+
+    @Test
+    public void testExternalRefFieldCodes() throws Exception {
+        List<Metadata> metadataList =
+                getRecursiveMetadata("testExternalRefs.docx");
+        Metadata m = metadataList.get(0);
+        assertEquals("true", m.get(Office.HAS_FIELD_HYPERLINKS));
+        String xml = getXML("testExternalRefs.docx").xml;
+        assertContains("class=\"external-ref-INCLUDEPICTURE\"", xml);
+        assertContains("http://example.com/tracking.png", xml);
+        assertContains("class=\"external-ref-INCLUDETEXT\"", xml);
+        assertContains("http://example.org/payload.txt", xml);
+        assertContains("class=\"external-ref-IMPORT\"", xml);
+        assertContains("http://example.net/exploit.wmf", xml);
+        assertContains("class=\"external-ref-LINK\"", xml);
+        assertContains("http://test.invalid/cmd.docx", xml);
+    }
+
+    @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);
+        String txt = getText("testWORD_various.docx");
+        assertContainsCount("italic", txt, 3);
+        assertNotContained("ita ", txt);
+        assertContainsCount("underline", txt, 2);
+        assertNotContained("unde ", txt);
+    }
+
+    @Test
+    public void testContiguousHTMLFormatting() throws Exception {
+        String xml = getXML("testWORD_bold_character_runs.docx").xml;
+        assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"),
+                "Bold text wasn't contiguous: " + xml);
+        xml = getXML("testWORD_bold_character_runs2.docx").xml;
+        assertTrue(xml.contains("F<b>oob</b>a<b>r</b>"),
+                "Bold text wasn't contiguous: " + xml);
+    }
+
+    @Test
+    public void testTurningOffTextBoxExtraction() throws Exception {
+        ParseContext pc = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setIncludeShapeBasedContent(false);
+        pc.set(OfficeParserConfig.class, config);
+        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 = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setIncludeHeadersAndFooters(false);
+        pc.set(OfficeParserConfig.class, config);
+        String xml = getXML("testWORD_various.docx", pc).xml;
+        assertNotContained("This is the header text.", xml);
+        assertNotContained("This is the footer text.", 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);
+            System.setErr(origErr);
+            String err = errContent.toString(UTF_8.name());
+            assertTrue(err.isEmpty(),
+                    "expected no error msg, but got >" + err + "<");
+        }
+    }
+
+    @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 testDotx() throws Exception {
+        List<Metadata> metadataList =
+                getRecursiveMetadata("testWORD_template.docx");
+        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);
+    }
+
+    // ---- SAX-specific tests ----
+
     @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;
+        String xml = getXML("testWORD_various.docx").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());
+                getRecursiveMetadata("testWORD_2006ml.docx");
 
         assertEquals(8, metadataList.size());
         Metadata m = metadataList.get(0);
@@ -146,7 +586,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testPicturesInVariousPlaces() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testWORD_embedded_pics.docx", getParseContext());
+                getRecursiveMetadata("testWORD_embedded_pics.docx");
 
         assertEquals(3, metadataList.size());
         String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
@@ -170,7 +610,6 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
         ParseContext pc = new ParseContext();
         OfficeParserConfig officeParserConfig = new OfficeParserConfig();
         officeParserConfig.setIncludeDeletedContent(true);
-        officeParserConfig.setUseSAXDocxExtractor(true);
         officeParserConfig.setIncludeMoveFromContent(true);
         pc.set(OfficeParserConfig.class, officeParserConfig);
 
@@ -187,7 +626,6 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
 
         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);
@@ -198,7 +636,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
         for (Map.Entry<String, String> e : tests.entrySet()) {
             boolean exc = false;
             try {
-                getXML(e.getKey(), getParseContext());
+                getXML(e.getKey());
             } catch (EncryptedDocumentException ex) {
                 exc = true;
             }
@@ -208,7 +646,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
 
     @Test
     public void testDOCXParagraphNumbering() throws Exception {
-        String xml = getXML("testWORD_numbered_list.docx", getParseContext()).xml;
+        String xml = getXML("testWORD_numbered_list.docx").xml;
         assertContains("add a list here", xml);
         assertContains("1) This", xml);
         assertContains("a) Is", xml);
@@ -240,7 +678,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
                 "org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor");
 
         List<Metadata> metadataList =
-                getRecursiveMetadata("testWORD_macros.docm", getParseContext());
+                getRecursiveMetadata("testWORD_macros.docm");
         for (Metadata metadata : metadataList) {
             if (metadata.get(Metadata.CONTENT_TYPE).equals("text/x-vbasic")) {
                 fail("Shouldn't have extracted macros as default");
@@ -251,7 +689,6 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
         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);
@@ -280,7 +717,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testEmbedded() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testWORD_embeded.docx", getParseContext());
+                getRecursiveMetadata("testWORD_embeded.docx");
         Metadata main = metadataList.get(0);
         String content = main.get(TikaCoreProperties.TIKA_CONTENT);
         assertContains(
@@ -291,9 +728,6 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
 
     @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();
@@ -304,7 +738,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testTruncatedSAXDocx() throws Exception {
         assertThrows(TikaException.class, () -> {
-            getRecursiveMetadata("testWORD_truncated.docx", getParseContext());
+            getRecursiveMetadata("testWORD_truncated.docx");
         });
     }
 
@@ -313,12 +747,12 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testHoverAndVmlHyperlinks() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testHoverAndVml.docx", getParseContext());
+                getRecursiveMetadata("testHoverAndVml.docx");
         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;
+        String xml = getXML("testHoverAndVml.docx").xml;
         assertContains("class=\"external-ref-hlinkHover\"", xml);
         assertContains("http://hover.example.com/phishing", xml);
         assertContains("class=\"external-ref-vml-shape-href\"", xml);
@@ -328,7 +762,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testMailMerge() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testMailMerge.docx", getParseContext());
+                getRecursiveMetadata("testMailMerge.docx");
         Metadata m = metadataList.get(0);
         assertEquals("true", m.get(Office.HAS_MAIL_MERGE));
     }
@@ -336,11 +770,11 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testAttachedTemplate() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testAttachedTemplate.docx", getParseContext());
+                getRecursiveMetadata("testAttachedTemplate.docx");
         Metadata m = metadataList.get(0);
         assertEquals("true", m.get(Office.HAS_ATTACHED_TEMPLATE));
 
-        String xml = getXML("testAttachedTemplate.docx", getParseContext()).xml;
+        String xml = getXML("testAttachedTemplate.docx").xml;
         assertContains("class=\"external-ref-attachedTemplate\"", xml);
         assertContains("example.com/templates", xml);
     }
@@ -348,11 +782,11 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testSubdocument() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testSubdocument.docx", getParseContext());
+                getRecursiveMetadata("testSubdocument.docx");
         Metadata m = metadataList.get(0);
         assertEquals("true", m.get(Office.HAS_SUBDOCUMENTS));
 
-        String xml = getXML("testSubdocument.docx", getParseContext()).xml;
+        String xml = getXML("testSubdocument.docx").xml;
         assertContains("class=\"external-ref-subDocument\"", xml);
         assertContains("example.org/chapters", xml);
     }
@@ -360,16 +794,11 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
     @Test
     public void testFrameset() throws Exception {
         List<Metadata> metadataList =
-                getRecursiveMetadata("testFrameset.docx", getParseContext());
+                getRecursiveMetadata("testFrameset.docx");
         Metadata m = metadataList.get(0);
         assertEquals("true", m.get(Office.HAS_FRAMESETS));
     }
 
-    /**
-     * Test with external DOCX files known to trigger "prefix not bound"
-     * from missing namespace declarations in footnote/endnote fragments.
-     * Enable by setting system property "tika.test.docx.namespace" to a file path.
-     */
     @Test
     public void testNamespaceInFragments() throws Exception {
         String filePath = System.getProperty("tika.test.docx.namespace");
@@ -385,7 +814,7 @@ public class OOXMLDocxSAXTest extends AbstractOOXMLDocxTest {
         org.xml.sax.ContentHandler handler =
                 new org.apache.tika.sax.BodyContentHandler(-1);
         try (TikaInputStream tis = TikaInputStream.get(f.toPath())) {
-            parser.parse(tis, handler, metadata, getParseContext());
+            parser.parse(tis, handler, metadata, new ParseContext());
         }
         String[] warnings = metadata.getValues(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING);
         for (String w : warnings) {
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
deleted file mode 100644
index 8b9ca989d9..0000000000
--- 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
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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
index c9996d0b91..7c3664b98d 100644
--- 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
@@ -22,13 +22,13 @@ 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.TikaTest;
 import org.apache.tika.config.loader.TikaLoader;
 import org.apache.tika.exception.EncryptedDocumentException;
 import org.apache.tika.io.TikaInputStream;
@@ -42,49 +42,294 @@ 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.
+ * Tests for the PPTX parser.
  */
-public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
+public class OOXMLPptxSAXTest extends TikaTest {
 
-    @Override
-    ParseContext getParseContext() {
-        ParseContext parseContext = new ParseContext();
-        OfficeParserConfig officeParserConfig = new OfficeParserConfig();
-        officeParserConfig.setUseSAXPptxExtractor(true);
-        parseContext.set(OfficeParserConfig.class, officeParserConfig);
-        return parseContext;
+    // ---- tests from AbstractOOXMLPptxTest ----
+
+    @Test
+    public void testPowerPoint() throws Exception {
+        String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm"};
+
+        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();
+
+            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
+                AUTO_DETECT_PARSER.parse(tis, handler, metadata, new ParseContext());
+
+                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));
+
+                String content = handler.toString();
+                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
-    public void basicTest() throws Exception {
+    public void testPowerPointMetadataEarly() throws Exception {
+        String[] extensions = new String[]{"pptx", "pptm", "ppsm", "ppsx", "potm"};
+
+        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();
+
+            final int currentI = i;
+            ContentHandler handler = new BodyContentHandler() {
+                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));
+                }
+            };
+
+            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
+                AUTO_DETECT_PARSER.parse(tis, handler, metadata, new ParseContext());
+            }
+        }
+    }
+
+    @Test
+    public void testUnsupportedPowerPoint() throws Exception {
+        String[] extensions = new String[]{"xps", "thmx"};
+        String[] mimeTypes = new String[]{"application/vnd.ms-xpsdocument",
+                "application/vnd.openxmlformats-officedocument"};
+
+        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);
+            ContentHandler handler = new BodyContentHandler();
+
+            try (TikaInputStream tis = getResourceAsStream("/test-documents/" + filename)) {
+                AUTO_DETECT_PARSER.parse(tis, handler, metadata, new ParseContext());
+                assertEquals(mimeTypes[i], metadata.get(Metadata.CONTENT_TYPE),
+                        "Mime-type checking for " + filename);
+            }
+        }
+    }
+
+    @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("<p>Bullet " + row, xml);
+        }
+        assertContains("Here is a numbered list:", xml);
+        for (int row = 1; row <= 3; row++) {
+            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);
+        assertContains("Suddenly some Japanese text:", xml);
+        assertContains("\uff08\uff27\uff28\uff31\uff09", xml);
+        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 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 {
+        assertContains("Master footer is here",
+                getXML("testPPT_masterFooter.pptx").xml);
+    }
+
+    @Test
+    @Disabled("can't tell why this isn't working")
+    public void testTurningOffMasterContent() throws Exception {
+        ParseContext context = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setIncludeSlideMasterContent(false);
+        context.set(OfficeParserConfig.class, config);
+        String xml = getXML("testPPT_masterFooter.pptx", context).xml;
+        assertNotContained("Master footer", xml);
+    }
 
+    @Test
+    public void testMasterText() throws Exception {
+        assertContains("Text that I added to the master slide",
+                getXML("testPPT_masterText.pptx").xml);
+
+        ParseContext context = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setIncludeSlideMasterContent(false);
+        context.set(OfficeParserConfig.class, config);
+        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").xml);
+
+        ParseContext context = new ParseContext();
+        OfficeParserConfig config = new OfficeParserConfig();
+        config.setIncludeSlideMasterContent(false);
+        context.set(OfficeParserConfig.class, config);
+        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").xml);
+    }
+
+    @Test
+    public void testPPTXAutodate() throws Exception {
+        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 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 testPPTXDiagramData() throws Exception {
+        assertContains("President",
+                getXML("testPPT_diagramData.pptx").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_various2.pptx", getParseContext());
+                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);
+        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));
+    }
 
-        assertEquals(15, metadataList.size(), "right number of attachments");
+    @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));
+    }
 
-        String mainContent = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+    // ---- SAX-specific tests ----
 
-        assertContains("This slide is hidden", mainContent);//TODO: parameterize this
+    @Test
+    public void basicTest() throws Exception {
+        List<Metadata> metadataList =
+                getRecursiveMetadata("testPPT_various2.pptx");
 
-        //basic content
-        assertContains("FirstBullet", mainContent);
+        assertEquals(15, metadataList.size(), "right number of attachments");
 
-        //hyperlink
+        String mainContent = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+        assertContains("This slide is hidden", mainContent);
+        assertContains("FirstBullet", mainContent);
         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);
@@ -93,41 +338,27 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
         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;
+        String xml = getXML("testPPTX_overlappingRelations.pptx").xml;
         assertContains("POI cannot read this", xml);
         assertContains("Has a relationship to another slide", xml);
         assertContains("can read this too", xml);
@@ -136,14 +367,7 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
     @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());
+        getXML("testPPT_custom_props.pptx", metadata);
         assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation",
                 metadata.get(Metadata.CONTENT_TYPE));
         assertEquals("JOUVIN ETIENNE", metadata.get(TikaCoreProperties.CREATOR));
@@ -160,11 +384,10 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
         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;
+        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\" />");
@@ -178,39 +401,33 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
         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;
+        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
     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());
+                getRecursiveMetadata("testPPT_macros.pptm");
 
-        //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();
@@ -221,11 +438,9 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
                 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();
@@ -244,9 +459,6 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
         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())) {
@@ -257,7 +469,6 @@ public class OOXMLPptxSAXTest extends AbstractOOXMLPptxTest {
         }
 
         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())) {
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/VSDXParserTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/ooxml/VSDXParserTest.java
new file mode 100644
index 0000000000..4153293b24
--- /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/VSDXParserTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+public class VSDXParserTest extends TikaTest {
+
+    @Test
+    public void testBasicTextExtraction() throws Exception {
+        List<Metadata> metadataList = getRecursiveMetadata("testVISIO.vsdx");
+        String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+        assertEquals("application/vnd.ms-visio.drawing",
+                metadataList.get(0).get(Metadata.CONTENT_TYPE));
+        assertContains("test", content);
+        assertContains("This is a test.", content);
+        assertContains("Nothing fancy.", content);
+    }
+
+    @Test
+    public void testTextExtraction() throws Exception {
+        // POI test file with multiple shapes containing text
+        List<Metadata> metadataList = getRecursiveMetadata("testVISIO_text.vsdx");
+        String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+        assertContains("Text here", content);
+        assertContains("Text there", content);
+        assertContains("Text, text, everywhere!", content);
+        assertContains("Router here", content);
+    }
+
+    @Test
+    public void testRealWorldVSDX() throws Exception {
+        // POI test file 60489 - real-world Visio diagram
+        List<Metadata> metadataList = getRecursiveMetadata("testVISIO_60489.vsdx");
+        String content = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
+        assertContains("HousingConnections.ca", content);
+        assertContains("Content Authors", content);
+        assertContains("Submit Application", content);
+    }
+}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-docx.json b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-docx.json
index 0a22fa62f4..800a8d621f 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-docx.json
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-docx.json
@@ -1,10 +1,5 @@
 {
   "parsers": [
-    "default-parser",
-    {
-      "ooxml-parser": {
-        "useSAXDocxExtractor": true
-      }
-    }
+    "default-parser"
   ]
 }
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-macros.json b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-macros.json
index e5b644cb7a..86ecdcdb33 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-macros.json
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/configs/tika-config-sax-macros.json
@@ -3,9 +3,7 @@
     "default-parser",
     {
       "ooxml-parser": {
-        "extractMacros": true,
-        "useSAXDocxExtractor": true,
-        "useSAXPptxExtractor": true
+        "extractMacros": true
       }
     },
     {
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_60489.vsdx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_60489.vsdx
new file mode 100644
index 0000000000..659ecdd585
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_60489.vsdx differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_text.vsdx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_text.vsdx
new file mode 100644
index 0000000000..39b6401772
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/testVISIO_text.vsdx 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
deleted file mode 100644
index 26967ab18d..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLDocxPackageTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java
deleted file mode 100644
index 8b844f1d28..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/AbstractOOXMLPptxPackageTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.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 AbstractOOXMLPptxPackageTest extends TikaTest {
-
-    abstract ParseContext getParseContext();
-
-    @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/OOXMLDocxDOMPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMPackageTest.java
deleted file mode 100644
index 3f6b0f5804..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxDOMPackageTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.assertNull;
-
-import java.util.List;
-
-import org.junit.jupiter.api.Test;
-
-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;
-
-/**
- * DOM-specific docx package tests.
- */
-public class OOXMLDocxDOMPackageTest extends AbstractOOXMLDocxPackageTest {
-
-    @Override
-    ParseContext getParseContext() {
-        return new ParseContext();
-    }
-
-    @Test
-    public void testEMFAssociatedWithAttachments() throws Exception {
-        //TIKA-3968
-        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));
-        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));
-    }
-}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
index 33712f21ba..a08a20572a 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLDocxSAXPackageTest.java
@@ -23,32 +23,40 @@ 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.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;
 
 /**
- * SAX-specific docx package tests.
+ * Docx package tests.
  */
-public class OOXMLDocxSAXPackageTest extends AbstractOOXMLDocxPackageTest {
+public class OOXMLDocxSAXPackageTest extends TikaTest {
 
-    @Override
-    ParseContext getParseContext() {
-        ParseContext parseContext = new ParseContext();
-        OfficeParserConfig officeParserConfig = new OfficeParserConfig();
-        officeParserConfig.setUseSAXDocxExtractor(true);
-        parseContext.set(OfficeParserConfig.class, officeParserConfig);
-        return parseContext;
+    @Test
+    public void testAltFileMHTChunk() throws Exception {
+        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 {
+        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 testEMFAssociatedWithAttachments() throws Exception {
         //TIKA-3968
         List<Metadata> metadataList =
-                getRecursiveMetadata("testWORD_EMFAndAttachments.docx", getParseContext());
+                getRecursiveMetadata("testWORD_EMFAndAttachments.docx");
 
         assertEquals("true", metadataList.get(1).get(EMFParser.EMF_ICON_ONLY));
         assertEquals("true", metadataList.get(3).get(EMFParser.EMF_ICON_ONLY));
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java
deleted file mode 100644
index 007e6c8531..0000000000
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxDOMPackageTest.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.tika.parser.ParseContext;
-
-public class OOXMLPptxDOMPackageTest extends AbstractOOXMLPptxPackageTest {
-
-    @Override
-    ParseContext getParseContext() {
-        return new ParseContext();
-    }
-}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
index 26b217b19e..f7023ceb52 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/src/test/java/org/apache/tika/parser/microsoft/ooxml/OOXMLPptxSAXPackageTest.java
@@ -16,17 +16,23 @@
  */
 package org.apache.tika.parser.microsoft.ooxml;
 
-import org.apache.tika.parser.ParseContext;
-import org.apache.tika.parser.microsoft.OfficeParserConfig;
+import java.util.List;
 
-public class OOXMLPptxSAXPackageTest extends AbstractOOXMLPptxPackageTest {
+import org.junit.jupiter.api.Test;
 
-    @Override
-    ParseContext getParseContext() {
-        ParseContext parseContext = new ParseContext();
-        OfficeParserConfig officeParserConfig = new OfficeParserConfig();
-        officeParserConfig.setUseSAXPptxExtractor(true);
-        parseContext.set(OfficeParserConfig.class, officeParserConfig);
-        return parseContext;
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+public class OOXMLPptxSAXPackageTest 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));
     }
 }