You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2026/08/26 12:21:42 UTC

(commons-xml) 01/08: Sort members

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

garydgregory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit f6583c76edb116407510971ba27c09abb14956a5
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Aug 26 07:23:51 2026 -0400

    Sort members
---
 .../commons/xml/FallbackIgnoreEntityResolver2.java |  50 +--
 .../xml/FallbackIgnoreLSResourceResolver.java      |  14 +-
 .../commons/xml/FallbackIgnoreURIResolver.java     |   8 +-
 .../commons/xml/FallbackIgnoreXMLResolver.java     |   8 +-
 .../commons/xml/HardeningDocumentBuilder.java      |  42 +--
 .../xml/HardeningDocumentBuilderFactory.java       |  10 +-
 .../org/apache/commons/xml/HardeningException.java |  30 +-
 .../org/apache/commons/xml/HardeningSAXParser.java |  34 +-
 .../commons/xml/HardeningSAXParserFactory.java     |  10 +-
 .../apache/commons/xml/HardeningSchemaFactory.java |  62 ++--
 .../org/apache/commons/xml/HardeningTemplates.java |  14 +-
 .../commons/xml/HardeningTemplatesHandler.java     |  64 ++--
 .../apache/commons/xml/HardeningTransformer.java   |  52 +--
 .../commons/xml/HardeningTransformerFactory.java   | 142 ++++----
 .../commons/xml/HardeningTransformerHandler.java   |  98 +++---
 .../commons/xml/HardeningValidatorHandler.java     |  88 ++---
 .../commons/xml/HardeningXMLInputFactory.java      | 168 +++++-----
 .../org/apache/commons/xml/HardeningXMLReader.java |  20 +-
 .../org/apache/commons/xml/HardeningXPath.java     |  12 +-
 .../apache/commons/xml/HardeningXPathFactory.java  |  12 +-
 .../java/org/apache/commons/xml/SaxonProvider.java |  42 +--
 .../commons/xml/AssociatedStylesheetTest.java      |  16 +-
 .../org/apache/commons/xml/AttackTestSupport.java  |  38 +--
 .../org/apache/commons/xml/DenyUnresolvedTest.java |  10 +-
 .../commons/xml/EntityResolverFloorTest.java       | 372 ++++++++++-----------
 .../org/apache/commons/xml/ResetHardeningTest.java |  28 +-
 .../apache/commons/xml/SaxSurfaceTestSupport.java  |  26 +-
 .../commons/xml/SaxonAlternateFactoryTest.java     |  18 +-
 .../apache/commons/xml/SchemaLocationDomTest.java  |  52 +--
 .../commons/xml/SchemaLocationPropertyTest.java    |  76 ++---
 .../apache/commons/xml/SchemaLocationSaxTest.java  |  56 ++--
 .../apache/commons/xml/ShadingFootprintTest.java   | 120 +++----
 .../apache/commons/xml/TemplatesHandlerTest.java   |  14 +-
 .../apache/commons/xml/TransformerHandlerTest.java |  20 +-
 .../xml/UnsupportedXmlImplementationTest.java      |  30 +-
 .../java/org/apache/commons/xml/XIncludeTest.java  | 194 +++++------
 .../java/org/apache/commons/xml/XMLFilterTest.java |  48 +--
 .../apache/commons/xml/XPathInputSourceTest.java   |  14 +-
 .../org/apache/commons/xml/XmlFactoriesTest.java   |  44 +--
 39 files changed, 1078 insertions(+), 1078 deletions(-)

diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
index dc04506..0b036b9 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreEntityResolver2.java
@@ -52,11 +52,6 @@
  */
 class FallbackIgnoreEntityResolver2 extends DefaultHandler2 {
 
-    /**
-     * Caller-supplied resolver consulted first, or {@code null} for a pure ignore-all floor.
-     */
-    private EntityResolver delegate;
-
     /**
      * Resolves {@code systemId} against {@code baseURI}.
      *
@@ -76,16 +71,12 @@ private static String absolutize(final String baseURI, final String systemId) {
         }
     }
 
-    FallbackIgnoreEntityResolver2(final EntityResolver delegate) {
-        this.delegate = delegate;
-    }
-
     /**
-     * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls.
-     *
-     * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
+     * Caller-supplied resolver consulted first, or {@code null} for a pure ignore-all floor.
      */
-    final void setDelegate(final EntityResolver delegate) {
+    private EntityResolver delegate;
+
+    FallbackIgnoreEntityResolver2(final EntityResolver delegate) {
         this.delegate = delegate;
     }
 
@@ -93,18 +84,6 @@ final EntityResolver getDelegate() {
         return delegate;
     }
 
-    @Override
-    public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
-        return resolveEntity(null, publicId, null, systemId);
-    }
-
-    @Override
-    public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId)
-            throws SAXException, IOException {
-        final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId);
-        return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId);
-    }
-
     /**
      * Outcome when neither the caller delegate nor this resolver provides the entity. Resolves to empty content by default, so the external resource is neither
      * fetched nor leaked and the parse continues with no replacement text. The returned source echoes the requested identifiers (with {@code systemId}
@@ -129,6 +108,18 @@ protected InputSource onUnresolved(final String name, final String publicId, fin
         return empty;
     }
 
+    @Override
+    public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
+        return resolveEntity(null, publicId, null, systemId);
+    }
+
+    @Override
+    public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId)
+            throws SAXException, IOException {
+        final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId);
+        return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId);
+    }
+
     private InputSource resolveWithDelegate(final String name, final String publicId, final String baseURI,
             final String systemId) throws SAXException, IOException {
         if (delegate != null) {
@@ -138,4 +129,13 @@ private InputSource resolveWithDelegate(final String name, final String publicId
         }
         return null;
     }
+
+    /**
+     * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls.
+     *
+     * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
+     */
+    final void setDelegate(final EntityResolver delegate) {
+        this.delegate = delegate;
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
index ab70517..3b98643 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreLSResourceResolver.java
@@ -38,12 +38,6 @@ final class FallbackIgnoreLSResourceResolver implements LSResourceResolver {
     /** DOM Level 3 Load/Save implementation used to build the empty input for unresolved lookups. */
     private static final DOMImplementationLS DOM_LS = domImplementationLS();
 
-    private LSResourceResolver delegate;
-
-    FallbackIgnoreLSResourceResolver(final LSResourceResolver delegate) {
-        this.delegate = delegate;
-    }
-
     private static DOMImplementationLS domImplementationLS() {
         try {
             return (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
@@ -52,7 +46,9 @@ private static DOMImplementationLS domImplementationLS() {
         }
     }
 
-    void setDelegate(final LSResourceResolver delegate) {
+    private LSResourceResolver delegate;
+
+    FallbackIgnoreLSResourceResolver(final LSResourceResolver delegate) {
         this.delegate = delegate;
     }
 
@@ -80,4 +76,8 @@ public LSInput resolveResource(final String type, final String namespaceURI, fin
         empty.setBaseURI(baseURI);
         return empty;
     }
+
+    void setDelegate(final LSResourceResolver delegate) {
+        this.delegate = delegate;
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
index b903606..a462872 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreURIResolver.java
@@ -78,10 +78,6 @@ private static Document newEmptyDocument() {
         this.emptySource = emptySource != null ? emptySource : () -> new DOMSource(EMPTY_DOCUMENT);
     }
 
-    void setDelegate(final URIResolver delegate) {
-        this.delegate = delegate;
-    }
-
     URIResolver getDelegate() {
         return delegate;
     }
@@ -98,4 +94,8 @@ public Source resolve(final String href, final String base) throws TransformerEx
         }
         return emptySource.get();
     }
+
+    void setDelegate(final URIResolver delegate) {
+        this.delegate = delegate;
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
index 1242be1..94c24c8 100644
--- a/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
+++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java
@@ -45,10 +45,6 @@ final class FallbackIgnoreXMLResolver implements XMLResolver {
         this.delegate = delegate;
     }
 
-    void setDelegate(final XMLResolver delegate) {
-        this.delegate = delegate;
-    }
-
     XMLResolver getDelegate() {
         return delegate;
     }
@@ -64,4 +60,8 @@ public Object resolveEntity(final String publicID, final String systemID, final
         }
         return EMPTY;
     }
+
+    void setDelegate(final XMLResolver delegate) {
+        this.delegate = delegate;
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
index 8343921..b9bf44e 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java
@@ -48,22 +48,15 @@ final class HardeningDocumentBuilder extends DocumentBuilder {
     }
 
     @Override
-    public void setEntityResolver(final EntityResolver resolver) {
-        floor.setDelegate(resolver);
+    public DOMImplementation getDOMImplementation() {
+        return delegate.getDOMImplementation();
     }
 
     @Override
-    public void reset() {
-        delegate.reset();
-        floor.setDelegate(null);
-        delegate.setEntityResolver(floor);
-    }
-
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
-    @Override
-    public Document parse(final InputSource is) throws SAXException, IOException {
-        return delegate.parse(is);
+    public Schema getSchema() {
+        return delegate.getSchema();
     }
+    // </editor-fold>
 
     @Override
     public boolean isNamespaceAware() {
@@ -81,23 +74,30 @@ public boolean isXIncludeAware() {
     }
 
     @Override
-    public void setErrorHandler(final ErrorHandler eh) {
-        delegate.setErrorHandler(eh);
+    public Document newDocument() {
+        return delegate.newDocument();
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public Document newDocument() {
-        return delegate.newDocument();
+    public Document parse(final InputSource is) throws SAXException, IOException {
+        return delegate.parse(is);
     }
 
     @Override
-    public DOMImplementation getDOMImplementation() {
-        return delegate.getDOMImplementation();
+    public void reset() {
+        delegate.reset();
+        floor.setDelegate(null);
+        delegate.setEntityResolver(floor);
     }
 
     @Override
-    public Schema getSchema() {
-        return delegate.getSchema();
+    public void setEntityResolver(final EntityResolver resolver) {
+        floor.setDelegate(resolver);
+    }
+
+    @Override
+    public void setErrorHandler(final ErrorHandler eh) {
+        delegate.setErrorHandler(eh);
     }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
index 2ae105f..6a18dbd 100644
--- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java
@@ -39,11 +39,6 @@ final class HardeningDocumentBuilderFactory extends DocumentBuilderFactory {
         this.delegate = delegate;
     }
 
-    @Override
-    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
-        return new HardeningDocumentBuilder(delegate.newDocumentBuilder());
-    }
-
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public Object getAttribute(final String name) {
@@ -95,6 +90,11 @@ public boolean isXIncludeAware() {
         return delegate.isXIncludeAware();
     }
 
+    @Override
+    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        return new HardeningDocumentBuilder(delegate.newDocumentBuilder());
+    }
+
     @Override
     public void setAttribute(final String name, final Object value) {
         delegate.setAttribute(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningException.java b/src/main/java/org/apache/commons/xml/HardeningException.java
index bc7c1de..4dd123c 100644
--- a/src/main/java/org/apache/commons/xml/HardeningException.java
+++ b/src/main/java/org/apache/commons/xml/HardeningException.java
@@ -35,8 +35,19 @@ final class HardeningException extends IllegalStateException {
 
     private static final long serialVersionUID = 1L;
 
-    HardeningException(final String message, final Throwable cause) {
-        super(message, cause);
+    /**
+     * Builds the standard "forbidden" message shared by every resolver floor when {@link #throwOnUnresolved()} rejects an unresolved reference.
+     *
+     * @param type      the resource kind, or {@code null} if not applicable.
+     * @param namespace the namespace (or, for Woodstox, the entity name), or {@code null}.
+     * @param publicId  the public identifier, or {@code null} if none.
+     * @param systemId  the system identifier of the denied resource.
+     * @param baseURI   the base URI for relative resolution, or {@code null}.
+     * @return the message naming the denied lookup and the enabling property.
+     */
+    static String forbidden(final String type, final String namespace, final String publicId, final String systemId, final String baseURI) {
+        return String.format("External resource fetch forbidden by %s: type=%s, namespace=%s, publicId=%s, systemId=%s, baseURI=%s",
+                XmlFactories.THROW_ON_UNRESOLVED, type, namespace, publicId, systemId, baseURI);
     }
 
     /**
@@ -63,18 +74,7 @@ static boolean throwOnUnresolved() {
         return Boolean.getBoolean(XmlFactories.THROW_ON_UNRESOLVED);
     }
 
-    /**
-     * Builds the standard "forbidden" message shared by every resolver floor when {@link #throwOnUnresolved()} rejects an unresolved reference.
-     *
-     * @param type      the resource kind, or {@code null} if not applicable.
-     * @param namespace the namespace (or, for Woodstox, the entity name), or {@code null}.
-     * @param publicId  the public identifier, or {@code null} if none.
-     * @param systemId  the system identifier of the denied resource.
-     * @param baseURI   the base URI for relative resolution, or {@code null}.
-     * @return the message naming the denied lookup and the enabling property.
-     */
-    static String forbidden(final String type, final String namespace, final String publicId, final String systemId, final String baseURI) {
-        return String.format("External resource fetch forbidden by %s: type=%s, namespace=%s, publicId=%s, systemId=%s, baseURI=%s",
-                XmlFactories.THROW_ON_UNRESOLVED, type, namespace, publicId, systemId, baseURI);
+    HardeningException(final String message, final Throwable cause) {
+        super(message, cause);
     }
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningSAXParser.java b/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
index 5a79e74..5e176b7 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParser.java
@@ -49,14 +49,6 @@ final class HardeningSAXParser extends SAXParser {
         this.delegate = delegate;
     }
 
-    @Override
-    public XMLReader getXMLReader() throws SAXException {
-        if (hardenedReader == null) {
-            hardenedReader = SAXParserHardener.hardenReader(delegate.getXMLReader());
-        }
-        return hardenedReader;
-    }
-
     @Override
     @SuppressWarnings("deprecation")
     public Parser getParser() throws SAXException {
@@ -68,15 +60,6 @@ public Parser getParser() throws SAXException {
         return hardenedParser;
     }
 
-    @Override
-    public void reset() {
-        delegate.reset();
-        // The JAXP reset contract reverts the delegate to its just-created state, which strips the post-creation reader hardening.
-        // We reset the cached readers, so hardening can be applied again.
-        hardenedReader = null;
-        hardenedParser = null;
-    }
-
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
@@ -88,6 +71,14 @@ public Schema getSchema() {
         return delegate.getSchema();
     }
 
+    @Override
+    public XMLReader getXMLReader() throws SAXException {
+        if (hardenedReader == null) {
+            hardenedReader = SAXParserHardener.hardenReader(delegate.getXMLReader());
+        }
+        return hardenedReader;
+    }
+
     @Override
     public boolean isNamespaceAware() {
         return delegate.isNamespaceAware();
@@ -103,6 +94,15 @@ public boolean isXIncludeAware() {
         return delegate.isXIncludeAware();
     }
 
+    @Override
+    public void reset() {
+        delegate.reset();
+        // The JAXP reset contract reverts the delegate to its just-created state, which strips the post-creation reader hardening.
+        // We reset the cached readers, so hardening can be applied again.
+        hardenedReader = null;
+        hardenedParser = null;
+    }
+
     @Override
     public void setProperty(final String name, final Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
         delegate.setProperty(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
index e0ea507..19f79a9 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java
@@ -42,11 +42,6 @@ final class HardeningSAXParserFactory extends SAXParserFactory {
         this.delegate = delegate;
     }
 
-    @Override
-    public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
-        return new HardeningSAXParser(delegate.newSAXParser());
-    }
-
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public boolean getFeature(final String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
@@ -73,6 +68,11 @@ public boolean isXIncludeAware() {
         return delegate.isXIncludeAware();
     }
 
+    @Override
+    public SAXParser newSAXParser() throws ParserConfigurationException, SAXException {
+        return new HardeningSAXParser(delegate.newSAXParser());
+    }
+
     @Override
     public void setFeature(final String name, final boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
         delegate.setFeature(name, value);
diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
index a21ee58..31cc9c0 100644
--- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java
@@ -53,37 +53,6 @@
  */
 final class HardeningSchemaFactory extends SchemaFactory {
 
-    private final SchemaFactory delegate;
-
-    private final FallbackIgnoreLSResourceResolver floor = new FallbackIgnoreLSResourceResolver(null);
-
-    HardeningSchemaFactory(final SchemaFactory delegate) {
-        this.delegate = delegate;
-        // Compile-time block for xs:import/include/redefine; the wrappers carry the rest (per-product resolver, source rewriting, limits via the reader).
-        delegate.setResourceResolver(floor);
-    }
-
-    @Override
-    public void setResourceResolver(final LSResourceResolver resourceResolver) {
-        // Route a caller resolver through the floor instead of replacing it, so the ignore-all lower bound cannot be removed.
-        floor.setDelegate(resourceResolver);
-    }
-
-    @Override
-    public LSResourceResolver getResourceResolver() {
-        return floor.getDelegate();
-    }
-
-    @Override
-    public Schema newSchema() throws SAXException {
-        return new HardeningSchema(delegate.newSchema());
-    }
-
-    @Override
-    public Schema newSchema(final Source[] schemas) throws SAXException {
-        return new HardeningSchema(delegate.newSchema(harden(schemas)));
-    }
-
     private static Source[] harden(final Source[] schemas) throws SAXException {
         final Source[] hardened = new Source[schemas.length];
         try {
@@ -96,6 +65,16 @@ private static Source[] harden(final Source[] schemas) throws SAXException {
         return hardened;
     }
 
+    private final SchemaFactory delegate;
+
+    private final FallbackIgnoreLSResourceResolver floor = new FallbackIgnoreLSResourceResolver(null);
+
+    HardeningSchemaFactory(final SchemaFactory delegate) {
+        this.delegate = delegate;
+        // Compile-time block for xs:import/include/redefine; the wrappers carry the rest (per-product resolver, source rewriting, limits via the reader).
+        delegate.setResourceResolver(floor);
+    }
+
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public ErrorHandler getErrorHandler() {
@@ -112,11 +91,26 @@ public Object getProperty(final String name) throws SAXNotRecognizedException, S
         return delegate.getProperty(name);
     }
 
+    @Override
+    public LSResourceResolver getResourceResolver() {
+        return floor.getDelegate();
+    }
+
     @Override
     public boolean isSchemaLanguageSupported(final String schemaLanguage) {
         return delegate.isSchemaLanguageSupported(schemaLanguage);
     }
 
+    @Override
+    public Schema newSchema() throws SAXException {
+        return new HardeningSchema(delegate.newSchema());
+    }
+
+    @Override
+    public Schema newSchema(final Source[] schemas) throws SAXException {
+        return new HardeningSchema(delegate.newSchema(harden(schemas)));
+    }
+
     @Override
     public void setErrorHandler(final ErrorHandler errorHandler) {
         delegate.setErrorHandler(errorHandler);
@@ -132,4 +126,10 @@ public void setProperty(final String name, final Object object) throws SAXNotRec
         delegate.setProperty(name, object);
     }
     // </editor-fold>
+
+    @Override
+    public void setResourceResolver(final LSResourceResolver resourceResolver) {
+        // Route a caller resolver through the floor instead of replacing it, so the ignore-all lower bound cannot be removed.
+        floor.setDelegate(resourceResolver);
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningTemplates.java b/src/main/java/org/apache/commons/xml/HardeningTemplates.java
index f598aa8..af8ceeb 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTemplates.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTemplates.java
@@ -60,6 +60,13 @@ Templates getDelegate() {
         return delegate;
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
+    @Override
+    public Properties getOutputProperties() {
+        return delegate.getOutputProperties();
+    }
+    // </editor-fold>
+
     @Override
     public Transformer newTransformer() throws TransformerConfigurationException {
         final Transformer transformer = delegate.newTransformer();
@@ -68,11 +75,4 @@ public Transformer newTransformer() throws TransformerConfigurationException {
         }
         return new HardeningTransformer(transformer, uriResolver, emptySource);
     }
-
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
-    @Override
-    public Properties getOutputProperties() {
-        return delegate.getOutputProperties();
-    }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java b/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
index dfcddaa..9600ac1 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTemplatesHandler.java
@@ -53,76 +53,76 @@ final class HardeningTemplatesHandler implements TemplatesHandler {
     }
 
     @Override
-    public Templates getTemplates() {
-        // Null before the stylesheet's endDocument (and on a failed compile in some implementations).
-        final Templates templates = delegate.getTemplates();
-        return templates == null ? null : new HardeningTemplates(templates, uriResolver, emptySource);
+    public void characters(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.characters(ch, start, length);
     }
 
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public void setSystemId(final String systemID) {
-        delegate.setSystemId(systemID);
+    public void endDocument() throws SAXException {
+        delegate.endDocument();
     }
 
     @Override
-    public String getSystemId() {
-        return delegate.getSystemId();
+    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+        delegate.endElement(uri, localName, qName);
     }
 
     @Override
-    public void setDocumentLocator(final Locator locator) {
-        delegate.setDocumentLocator(locator);
+    public void endPrefixMapping(final String prefix) throws SAXException {
+        delegate.endPrefixMapping(prefix);
     }
 
     @Override
-    public void startDocument() throws SAXException {
-        delegate.startDocument();
+    public String getSystemId() {
+        return delegate.getSystemId();
     }
 
     @Override
-    public void endDocument() throws SAXException {
-        delegate.endDocument();
+    public Templates getTemplates() {
+        // Null before the stylesheet's endDocument (and on a failed compile in some implementations).
+        final Templates templates = delegate.getTemplates();
+        return templates == null ? null : new HardeningTemplates(templates, uriResolver, emptySource);
     }
 
     @Override
-    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
-        delegate.startPrefixMapping(prefix, uri);
+    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.ignorableWhitespace(ch, start, length);
     }
 
     @Override
-    public void endPrefixMapping(final String prefix) throws SAXException {
-        delegate.endPrefixMapping(prefix);
+    public void processingInstruction(final String target, final String data) throws SAXException {
+        delegate.processingInstruction(target, data);
     }
 
     @Override
-    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
-        delegate.startElement(uri, localName, qName, atts);
+    public void setDocumentLocator(final Locator locator) {
+        delegate.setDocumentLocator(locator);
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
-        delegate.endElement(uri, localName, qName);
+    public void setSystemId(final String systemID) {
+        delegate.setSystemId(systemID);
     }
 
     @Override
-    public void characters(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.characters(ch, start, length);
+    public void skippedEntity(final String name) throws SAXException {
+        delegate.skippedEntity(name);
     }
+    // </editor-fold>
 
     @Override
-    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.ignorableWhitespace(ch, start, length);
+    public void startDocument() throws SAXException {
+        delegate.startDocument();
     }
 
     @Override
-    public void processingInstruction(final String target, final String data) throws SAXException {
-        delegate.processingInstruction(target, data);
+    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+        delegate.startElement(uri, localName, qName, atts);
     }
 
     @Override
-    public void skippedEntity(final String name) throws SAXException {
-        delegate.skippedEntity(name);
+    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+        delegate.startPrefixMapping(prefix, uri);
     }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformer.java b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
index e406138..beda636 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformer.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformer.java
@@ -53,32 +53,6 @@ final class HardeningTransformer extends Transformer {
         delegate.setURIResolver(floor);
     }
 
-    @Override
-    public void reset() {
-        delegate.reset();
-        floor.setDelegate(uriResolver);
-        delegate.setURIResolver(floor);
-    }
-
-    @Override
-    public void setURIResolver(final URIResolver resolver) {
-        floor.setDelegate(resolver);
-    }
-
-    @Override
-    public URIResolver getURIResolver() {
-        return floor.getDelegate();
-    }
-
-    @Override
-    public void transform(final Source xmlSource, final Result outputTarget) throws TransformerException {
-        try {
-            delegate.transform(SAXParserHardener.hardenSource(xmlSource), outputTarget);
-        } catch (final TransformerConfigurationException e) {
-            throw new TransformerException(e);
-        }
-    }
-
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public void clearParameters() {
@@ -105,6 +79,18 @@ public Object getParameter(final String name) {
         return delegate.getParameter(name);
     }
 
+    @Override
+    public URIResolver getURIResolver() {
+        return floor.getDelegate();
+    }
+
+    @Override
+    public void reset() {
+        delegate.reset();
+        floor.setDelegate(uriResolver);
+        delegate.setURIResolver(floor);
+    }
+
     @Override
     public void setErrorListener(final ErrorListener listener) {
         delegate.setErrorListener(listener);
@@ -125,4 +111,18 @@ public void setParameter(final String name, final Object value) {
         delegate.setParameter(name, value);
     }
     // </editor-fold>
+
+    @Override
+    public void setURIResolver(final URIResolver resolver) {
+        floor.setDelegate(resolver);
+    }
+
+    @Override
+    public void transform(final Source xmlSource, final Result outputTarget) throws TransformerException {
+        try {
+            delegate.transform(SAXParserHardener.hardenSource(xmlSource), outputTarget);
+        } catch (final TransformerConfigurationException e) {
+            throw new TransformerException(e);
+        }
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
index cc25ddd..84775d4 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java
@@ -71,6 +71,46 @@
  */
 final class HardeningTransformerFactory extends SAXTransformerFactory {
 
+    /**
+     * Parses a reader-less source into a DOM through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link DOMSource}
+     * carrying its system id, so the consumer walks the tree instead of provisioning its own reader. Any other source is left to
+     * {@link SAXParserHardener#hardenSource(Source)}.
+     *
+     * @param source The source to scan for an associated stylesheet.
+     * @return A {@link DOMSource} for a reader-less source, otherwise the result of {@link SAXParserHardener#hardenSource(Source)}.
+     * @throws TransformerConfigurationException if the source cannot be parsed.
+     */
+    private static Source hardenSourceToDom(final Source source) throws TransformerConfigurationException {
+        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
+            final InputSource inputSource = SAXSource.sourceToInputSource(source);
+            if (inputSource != null) {
+                try {
+                    final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
+                    factory.setNamespaceAware(true);
+                    final Document document = factory.newDocumentBuilder().parse(inputSource);
+                    return new DOMSource(document, inputSource.getSystemId());
+                } catch (final ParserConfigurationException | SAXException | IOException e) {
+                    throw new TransformerConfigurationException("Failed to parse the source for associated-stylesheet lookup", e);
+                }
+            }
+        }
+        return SAXParserHardener.hardenSource(source);
+    }
+
+    /**
+     * Whether the delegate is Apache Xalan (either its interpretive or its XSLTC factory), whose {@code getAssociatedStylesheet} ignores a SAXSource reader.
+     *
+     * @param factory The delegate factory.
+     * @return Whether the delegate is an {@code org.apache.xalan.} implementation.
+     */
+    private static boolean isXalan(final SAXTransformerFactory factory) {
+        return factory.getClass().getName().startsWith("org.apache.xalan.");
+    }
+
+    private static Templates unwrap(final Templates templates) {
+        return templates instanceof HardeningTemplates ? ((HardeningTemplates) templates).getDelegate() : templates;
+    }
+
     private final SAXTransformerFactory delegate;
 
     /** Empty-{@link Source} supplier for the resolver floor, threaded onto every produced Templates/Transformer; {@code null} means the default empty DOM. */
@@ -91,57 +131,36 @@ final class HardeningTransformerFactory extends SAXTransformerFactory {
     }
 
     @Override
-    public void setURIResolver(final URIResolver resolver) {
-        floor.setDelegate(resolver);
+    public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
+            throws TransformerConfigurationException {
+        // Xalan's getAssociatedStylesheet drops a SAXSource's reader and self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849).
+        final Source hardened = isXalan(delegate) ? hardenSourceToDom(source) : SAXParserHardener.hardenSource(source);
+        return delegate.getAssociatedStylesheet(hardened, media, title, charset);
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public URIResolver getURIResolver() {
-        return floor.getDelegate();
+    public Object getAttribute(final String name) {
+        return delegate.getAttribute(name);
     }
 
     @Override
-    public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
-            throws TransformerConfigurationException {
-        // Xalan's getAssociatedStylesheet drops a SAXSource's reader and self-provisions its own to scan for xml-stylesheet PIs (XALANJ-2849).
-        final Source hardened = isXalan(delegate) ? hardenSourceToDom(source) : SAXParserHardener.hardenSource(source);
-        return delegate.getAssociatedStylesheet(hardened, media, title, charset);
+    public ErrorListener getErrorListener() {
+        return delegate.getErrorListener();
     }
 
-    /**
-     * Whether the delegate is Apache Xalan (either its interpretive or its XSLTC factory), whose {@code getAssociatedStylesheet} ignores a SAXSource reader.
-     *
-     * @param factory The delegate factory.
-     * @return Whether the delegate is an {@code org.apache.xalan.} implementation.
-     */
-    private static boolean isXalan(final SAXTransformerFactory factory) {
-        return factory.getClass().getName().startsWith("org.apache.xalan.");
+    @Override
+    public boolean getFeature(final String name) {
+        return delegate.getFeature(name);
     }
 
-    /**
-     * Parses a reader-less source into a DOM through a hardened, namespace-aware {@link javax.xml.parsers.DocumentBuilder} and returns a {@link DOMSource}
-     * carrying its system id, so the consumer walks the tree instead of provisioning its own reader. Any other source is left to
-     * {@link SAXParserHardener#hardenSource(Source)}.
-     *
-     * @param source The source to scan for an associated stylesheet.
-     * @return A {@link DOMSource} for a reader-less source, otherwise the result of {@link SAXParserHardener#hardenSource(Source)}.
-     * @throws TransformerConfigurationException if the source cannot be parsed.
-     */
-    private static Source hardenSourceToDom(final Source source) throws TransformerConfigurationException {
-        if (source instanceof StreamSource || source instanceof SAXSource && ((SAXSource) source).getXMLReader() == null) {
-            final InputSource inputSource = SAXSource.sourceToInputSource(source);
-            if (inputSource != null) {
-                try {
-                    final DocumentBuilderFactory factory = DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
-                    factory.setNamespaceAware(true);
-                    final Document document = factory.newDocumentBuilder().parse(inputSource);
-                    return new DOMSource(document, inputSource.getSystemId());
-                } catch (final ParserConfigurationException | SAXException | IOException e) {
-                    throw new TransformerConfigurationException("Failed to parse the source for associated-stylesheet lookup", e);
-                }
-            }
-        }
-        return SAXParserHardener.hardenSource(source);
+    @Override
+    public URIResolver getURIResolver() {
+        return floor.getDelegate();
+    }
+
+    private TransformerHandler hardenHandler(final TransformerHandler handler) {
+        return handler == null ? null : new HardeningTransformerHandler(handler, getURIResolver(), emptySource);
     }
 
     @Override
@@ -150,6 +169,12 @@ public Templates newTemplates(final Source source) throws TransformerConfigurati
         return templates == null ? null : new HardeningTemplates(templates, getURIResolver(), emptySource);
     }
 
+    @Override
+    public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
+        final TemplatesHandler handler = delegate.newTemplatesHandler();
+        return handler == null ? null : new HardeningTemplatesHandler(handler, getURIResolver(), emptySource);
+    }
+
     @Override
     public Transformer newTransformer() throws TransformerConfigurationException {
         // Identity transformer: still parses runtime sources, so wrap it to harden Transformer.transform(Source, Result).
@@ -179,12 +204,6 @@ public TransformerHandler newTransformerHandler(final Templates templates) throw
         return hardenHandler(delegate.newTransformerHandler(unwrap(templates)));
     }
 
-    @Override
-    public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException {
-        final TemplatesHandler handler = delegate.newTemplatesHandler();
-        return handler == null ? null : new HardeningTemplatesHandler(handler, getURIResolver(), emptySource);
-    }
-
     @Override
     public XMLFilter newXMLFilter(final Source source) throws TransformerConfigurationException {
         final Templates templates = newTemplates(source);
@@ -197,30 +216,6 @@ public XMLFilter newXMLFilter(final Templates templates) throws TransformerConfi
                 : new HardeningTemplates(templates, getURIResolver(), emptySource));
     }
 
-    private TransformerHandler hardenHandler(final TransformerHandler handler) {
-        return handler == null ? null : new HardeningTransformerHandler(handler, getURIResolver(), emptySource);
-    }
-
-    private static Templates unwrap(final Templates templates) {
-        return templates instanceof HardeningTemplates ? ((HardeningTemplates) templates).getDelegate() : templates;
-    }
-
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
-    @Override
-    public Object getAttribute(final String name) {
-        return delegate.getAttribute(name);
-    }
-
-    @Override
-    public ErrorListener getErrorListener() {
-        return delegate.getErrorListener();
-    }
-
-    @Override
-    public boolean getFeature(final String name) {
-        return delegate.getFeature(name);
-    }
-
     @Override
     public void setAttribute(final String name, final Object value) {
         delegate.setAttribute(name, value);
@@ -236,4 +231,9 @@ public void setFeature(final String name, final boolean value) throws Transforme
         delegate.setFeature(name, value);
     }
     // </editor-fold>
+
+    @Override
+    public void setURIResolver(final URIResolver resolver) {
+        floor.setDelegate(resolver);
+    }
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java b/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
index 6281802..6c44417 100644
--- a/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningTransformerHandler.java
@@ -51,44 +51,39 @@ final class HardeningTransformerHandler implements TransformerHandler {
     }
 
     @Override
-    public Transformer getTransformer() {
-        return transformer;
-    }
-
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
-    @Override
-    public void setResult(final Result result) {
-        delegate.setResult(result);
+    public void characters(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.characters(ch, start, length);
     }
 
     @Override
-    public void setSystemId(final String systemID) {
-        delegate.setSystemId(systemID);
+    public void comment(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.comment(ch, start, length);
     }
+    // </editor-fold>
 
     @Override
-    public String getSystemId() {
-        return delegate.getSystemId();
+    public void endCDATA() throws SAXException {
+        delegate.endCDATA();
     }
 
     @Override
-    public void setDocumentLocator(final Locator locator) {
-        delegate.setDocumentLocator(locator);
+    public void endDocument() throws SAXException {
+        delegate.endDocument();
     }
 
     @Override
-    public void startDocument() throws SAXException {
-        delegate.startDocument();
+    public void endDTD() throws SAXException {
+        delegate.endDTD();
     }
 
     @Override
-    public void endDocument() throws SAXException {
-        delegate.endDocument();
+    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+        delegate.endElement(uri, localName, qName);
     }
 
     @Override
-    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
-        delegate.startPrefixMapping(prefix, uri);
+    public void endEntity(final String name) throws SAXException {
+        delegate.endEntity(name);
     }
 
     @Override
@@ -97,23 +92,23 @@ public void endPrefixMapping(final String prefix) throws SAXException {
     }
 
     @Override
-    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
-        delegate.startElement(uri, localName, qName, atts);
+    public String getSystemId() {
+        return delegate.getSystemId();
     }
 
     @Override
-    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
-        delegate.endElement(uri, localName, qName);
+    public Transformer getTransformer() {
+        return transformer;
     }
 
     @Override
-    public void characters(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.characters(ch, start, length);
+    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.ignorableWhitespace(ch, start, length);
     }
 
     @Override
-    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.ignorableWhitespace(ch, start, length);
+    public void notationDecl(final String name, final String publicId, final String systemId) throws SAXException {
+        delegate.notationDecl(name, publicId, systemId);
     }
 
     @Override
@@ -122,53 +117,58 @@ public void processingInstruction(final String target, final String data) throws
     }
 
     @Override
-    public void skippedEntity(final String name) throws SAXException {
-        delegate.skippedEntity(name);
+    public void setDocumentLocator(final Locator locator) {
+        delegate.setDocumentLocator(locator);
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public void notationDecl(final String name, final String publicId, final String systemId) throws SAXException {
-        delegate.notationDecl(name, publicId, systemId);
+    public void setResult(final Result result) {
+        delegate.setResult(result);
     }
 
     @Override
-    public void unparsedEntityDecl(final String name, final String publicId, final String systemId, final String notationName) throws SAXException {
-        delegate.unparsedEntityDecl(name, publicId, systemId, notationName);
+    public void setSystemId(final String systemID) {
+        delegate.setSystemId(systemID);
     }
 
     @Override
-    public void startDTD(final String name, final String publicId, final String systemId) throws SAXException {
-        delegate.startDTD(name, publicId, systemId);
+    public void skippedEntity(final String name) throws SAXException {
+        delegate.skippedEntity(name);
     }
 
     @Override
-    public void endDTD() throws SAXException {
-        delegate.endDTD();
+    public void startCDATA() throws SAXException {
+        delegate.startCDATA();
     }
 
     @Override
-    public void startEntity(final String name) throws SAXException {
-        delegate.startEntity(name);
+    public void startDocument() throws SAXException {
+        delegate.startDocument();
     }
 
     @Override
-    public void endEntity(final String name) throws SAXException {
-        delegate.endEntity(name);
+    public void startDTD(final String name, final String publicId, final String systemId) throws SAXException {
+        delegate.startDTD(name, publicId, systemId);
     }
 
     @Override
-    public void startCDATA() throws SAXException {
-        delegate.startCDATA();
+    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+        delegate.startElement(uri, localName, qName, atts);
     }
 
     @Override
-    public void endCDATA() throws SAXException {
-        delegate.endCDATA();
+    public void startEntity(final String name) throws SAXException {
+        delegate.startEntity(name);
     }
 
     @Override
-    public void comment(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.comment(ch, start, length);
+    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+        delegate.startPrefixMapping(prefix, uri);
+    }
+
+    @Override
+    public void unparsedEntityDecl(final String name, final String publicId, final String systemId, final String notationName) throws SAXException {
+        delegate.unparsedEntityDecl(name, publicId, systemId, notationName);
     }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
index ea25b5b..8cb9c44 100644
--- a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
+++ b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java
@@ -47,29 +47,28 @@ final class HardeningValidatorHandler extends ValidatorHandler {
     }
 
     @Override
-    public void setResourceResolver(final LSResourceResolver resourceResolver) {
-        floor.setDelegate(resourceResolver);
+    public void characters(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.characters(ch, start, length);
     }
 
     @Override
-    public LSResourceResolver getResourceResolver() {
-        return floor.getDelegate();
+    public void endDocument() throws SAXException {
+        delegate.endDocument();
     }
 
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public void setContentHandler(final ContentHandler receiver) {
-        delegate.setContentHandler(receiver);
+    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
+        delegate.endElement(uri, localName, qName);
     }
 
     @Override
-    public ContentHandler getContentHandler() {
-        return delegate.getContentHandler();
+    public void endPrefixMapping(final String prefix) throws SAXException {
+        delegate.endPrefixMapping(prefix);
     }
 
     @Override
-    public void setErrorHandler(final ErrorHandler errorHandler) {
-        delegate.setErrorHandler(errorHandler);
+    public ContentHandler getContentHandler() {
+        return delegate.getContentHandler();
     }
 
     @Override
@@ -78,83 +77,84 @@ public ErrorHandler getErrorHandler() {
     }
 
     @Override
-    public TypeInfoProvider getTypeInfoProvider() {
-        return delegate.getTypeInfoProvider();
+    public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return delegate.getFeature(name);
     }
 
     @Override
-    public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
-        return delegate.getFeature(name);
+    public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+        return delegate.getProperty(name);
     }
 
     @Override
-    public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
-        delegate.setFeature(name, value);
+    public LSResourceResolver getResourceResolver() {
+        return floor.getDelegate();
     }
 
     @Override
-    public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
-        return delegate.getProperty(name);
+    public TypeInfoProvider getTypeInfoProvider() {
+        return delegate.getTypeInfoProvider();
     }
 
     @Override
-    public void setProperty(final String name, final Object object) throws SAXNotRecognizedException, SAXNotSupportedException {
-        delegate.setProperty(name, object);
+    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
+        delegate.ignorableWhitespace(ch, start, length);
     }
 
     @Override
-    public void setDocumentLocator(final Locator locator) {
-        delegate.setDocumentLocator(locator);
+    public void processingInstruction(final String target, final String data) throws SAXException {
+        delegate.processingInstruction(target, data);
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public void startDocument() throws SAXException {
-        delegate.startDocument();
+    public void setContentHandler(final ContentHandler receiver) {
+        delegate.setContentHandler(receiver);
     }
 
     @Override
-    public void endDocument() throws SAXException {
-        delegate.endDocument();
+    public void setDocumentLocator(final Locator locator) {
+        delegate.setDocumentLocator(locator);
     }
 
     @Override
-    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
-        delegate.startPrefixMapping(prefix, uri);
+    public void setErrorHandler(final ErrorHandler errorHandler) {
+        delegate.setErrorHandler(errorHandler);
     }
 
     @Override
-    public void endPrefixMapping(final String prefix) throws SAXException {
-        delegate.endPrefixMapping(prefix);
+    public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+        delegate.setFeature(name, value);
     }
 
     @Override
-    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
-        delegate.startElement(uri, localName, qName, atts);
+    public void setProperty(final String name, final Object object) throws SAXNotRecognizedException, SAXNotSupportedException {
+        delegate.setProperty(name, object);
     }
 
     @Override
-    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
-        delegate.endElement(uri, localName, qName);
+    public void setResourceResolver(final LSResourceResolver resourceResolver) {
+        floor.setDelegate(resourceResolver);
     }
 
     @Override
-    public void characters(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.characters(ch, start, length);
+    public void skippedEntity(final String name) throws SAXException {
+        delegate.skippedEntity(name);
     }
+    // </editor-fold>
 
     @Override
-    public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
-        delegate.ignorableWhitespace(ch, start, length);
+    public void startDocument() throws SAXException {
+        delegate.startDocument();
     }
 
     @Override
-    public void processingInstruction(final String target, final String data) throws SAXException {
-        delegate.processingInstruction(target, data);
+    public void startElement(final String uri, final String localName, final String qName, final Attributes atts) throws SAXException {
+        delegate.startElement(uri, localName, qName, atts);
     }
 
     @Override
-    public void skippedEntity(final String name) throws SAXException {
-        delegate.skippedEntity(name);
+    public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+        delegate.startPrefixMapping(prefix, uri);
     }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
index 94cf4b5..a806018 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java
@@ -51,6 +51,17 @@
  */
 final class HardeningXMLInputFactory extends XMLInputFactory {
 
+    private static boolean isResolverProperty(final String name) {
+        return XMLInputFactory.RESOLVER.equals(name)
+                || StaxHardener.WSTX_DTD_RESOLVER.equals(name)
+                || StaxHardener.WSTX_ENTITY_RESOLVER.equals(name)
+                || StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
+    }
+
+    private static XMLResolver unwrap(final XMLResolver resolver) {
+        return resolver instanceof FallbackIgnoreXMLResolver ? ((FallbackIgnoreXMLResolver) resolver).getDelegate() : resolver;
+    }
+
     private final XMLInputFactory delegate;
 
     HardeningXMLInputFactory(final XMLInputFactory delegate) {
@@ -59,73 +70,48 @@ final class HardeningXMLInputFactory extends XMLInputFactory {
     }
 
     @Override
-    public void setXMLResolver(final XMLResolver resolver) {
-        setResolverProperty(XMLInputFactory.RESOLVER, resolver);
+    public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
+        return delegate.createFilteredReader(reader, filter);
     }
 
     @Override
-    public XMLResolver getXMLResolver() {
-        return unwrap(delegate.getXMLResolver());
+    public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
+        return delegate.createFilteredReader(reader, filter);
     }
 
     @Override
-    public void setProperty(final String name, final Object value) {
-        // If a resolver property has a value of the wrong type, pass it to the delegate to generate an appropriate exception.
-        if (isResolverProperty(name) && (value == null || value instanceof XMLResolver)) {
-            setResolverProperty(name, (XMLResolver) value);
-        } else {
-            delegate.setProperty(name, value);
-        }
+    public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
+        return delegate.createXMLEventReader(stream);
     }
 
     @Override
-    public Object getProperty(final String name) {
-        if (isResolverProperty(name)) {
-            return unwrap((XMLResolver) delegate.getProperty(name));
-        }
-        return delegate.getProperty(name);
+    public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
+        return delegate.createXMLEventReader(stream, encoding);
     }
 
-    /**
-     * Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook.
-     *
-     * @param name     The resolver-valued property being set.
-     * @param resolver The caller's resolver, or their own {@link FallbackIgnoreXMLResolver} to take control.
-     */
-    private void setResolverProperty(final String name, final XMLResolver resolver) {
-        if (resolver instanceof FallbackIgnoreXMLResolver) {
-            // The caller supplies their own floor: hand it to the delegate as-is.
-            delegate.setProperty(name, resolver);
-        } else {
-            final Object current = delegate.getProperty(name);
-            if (current instanceof FallbackIgnoreXMLResolver) {
-                ((FallbackIgnoreXMLResolver) current).setDelegate(resolver);
-            } else {
-                delegate.setProperty(name, new FallbackIgnoreXMLResolver(resolver));
-            }
-        }
+    @Override
+    public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
+        return delegate.createXMLEventReader(reader);
     }
 
-    private static boolean isResolverProperty(final String name) {
-        return XMLInputFactory.RESOLVER.equals(name)
-                || StaxHardener.WSTX_DTD_RESOLVER.equals(name)
-                || StaxHardener.WSTX_ENTITY_RESOLVER.equals(name)
-                || StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
+    @Override
+    public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
+        return delegate.createXMLEventReader(source);
     }
 
-    private static XMLResolver unwrap(final XMLResolver resolver) {
-        return resolver instanceof FallbackIgnoreXMLResolver ? ((FallbackIgnoreXMLResolver) resolver).getDelegate() : resolver;
+    @Override
+    public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
+        return delegate.createXMLEventReader(systemId, stream);
     }
 
-    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
-        return delegate.createXMLStreamReader(reader);
+    public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
+        return delegate.createXMLEventReader(systemId, reader);
     }
 
     @Override
-    public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
-        return delegate.createXMLStreamReader(source);
+    public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
+        return delegate.createXMLEventReader(reader);
     }
 
     @Override
@@ -138,64 +124,89 @@ public XMLStreamReader createXMLStreamReader(final InputStream stream, final Str
         return delegate.createXMLStreamReader(stream, encoding);
     }
 
+    // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
-    public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
-        return delegate.createXMLStreamReader(systemId, stream);
+    public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
+        return delegate.createXMLStreamReader(reader);
     }
 
     @Override
-    public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
-        return delegate.createXMLStreamReader(systemId, reader);
+    public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
+        return delegate.createXMLStreamReader(source);
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
-        return delegate.createXMLEventReader(reader);
+    public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
+        return delegate.createXMLStreamReader(systemId, stream);
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
-        return delegate.createXMLEventReader(systemId, reader);
+    public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
+        return delegate.createXMLStreamReader(systemId, reader);
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
-        return delegate.createXMLEventReader(reader);
+    public XMLEventAllocator getEventAllocator() {
+        return delegate.getEventAllocator();
     }
+    // </editor-fold>
 
     @Override
-    public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
-        return delegate.createXMLEventReader(source);
+    public Object getProperty(final String name) {
+        if (isResolverProperty(name)) {
+            return unwrap((XMLResolver) delegate.getProperty(name));
+        }
+        return delegate.getProperty(name);
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
-        return delegate.createXMLEventReader(stream);
+    public XMLReporter getXMLReporter() {
+        return delegate.getXMLReporter();
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
-        return delegate.createXMLEventReader(stream, encoding);
+    public XMLResolver getXMLResolver() {
+        return unwrap(delegate.getXMLResolver());
     }
 
     @Override
-    public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
-        return delegate.createXMLEventReader(systemId, stream);
+    public boolean isPropertySupported(final String name) {
+        return delegate.isPropertySupported(name);
     }
 
     @Override
-    public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
-        return delegate.createFilteredReader(reader, filter);
+    public void setEventAllocator(final XMLEventAllocator allocator) {
+        delegate.setEventAllocator(allocator);
     }
 
     @Override
-    public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
-        return delegate.createFilteredReader(reader, filter);
+    public void setProperty(final String name, final Object value) {
+        // If a resolver property has a value of the wrong type, pass it to the delegate to generate an appropriate exception.
+        if (isResolverProperty(name) && (value == null || value instanceof XMLResolver)) {
+            setResolverProperty(name, (XMLResolver) value);
+        } else {
+            delegate.setProperty(name, value);
+        }
     }
 
-    @Override
-    public XMLReporter getXMLReporter() {
-        return delegate.getXMLReporter();
+    /**
+     * Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook.
+     *
+     * @param name     The resolver-valued property being set.
+     * @param resolver The caller's resolver, or their own {@link FallbackIgnoreXMLResolver} to take control.
+     */
+    private void setResolverProperty(final String name, final XMLResolver resolver) {
+        if (resolver instanceof FallbackIgnoreXMLResolver) {
+            // The caller supplies their own floor: hand it to the delegate as-is.
+            delegate.setProperty(name, resolver);
+        } else {
+            final Object current = delegate.getProperty(name);
+            if (current instanceof FallbackIgnoreXMLResolver) {
+                ((FallbackIgnoreXMLResolver) current).setDelegate(resolver);
+            } else {
+                delegate.setProperty(name, new FallbackIgnoreXMLResolver(resolver));
+            }
+        }
     }
 
     @Override
@@ -204,18 +215,7 @@ public void setXMLReporter(final XMLReporter reporter) {
     }
 
     @Override
-    public boolean isPropertySupported(final String name) {
-        return delegate.isPropertySupported(name);
-    }
-
-    @Override
-    public void setEventAllocator(final XMLEventAllocator allocator) {
-        delegate.setEventAllocator(allocator);
-    }
-
-    @Override
-    public XMLEventAllocator getEventAllocator() {
-        return delegate.getEventAllocator();
+    public void setXMLResolver(final XMLResolver resolver) {
+        setResolverProperty(XMLInputFactory.RESOLVER, resolver);
     }
-    // </editor-fold>
 }
diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
index 7621571..7899ed5 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java
@@ -51,16 +51,6 @@ class HardeningXMLReader implements XMLReader {
         delegate.setEntityResolver(floor);
     }
 
-    @Override
-    public void setEntityResolver(final EntityResolver resolver) {
-        floor.setDelegate(resolver);
-    }
-
-    @Override
-    public EntityResolver getEntityResolver() {
-        return floor.getDelegate();
-    }
-
     // <editor-fold defaultstate="collapsed" desc="Trivial delegation">
     @Override
     public ContentHandler getContentHandler() {
@@ -72,6 +62,11 @@ public DTDHandler getDTDHandler() {
         return delegate.getDTDHandler();
     }
 
+    @Override
+    public EntityResolver getEntityResolver() {
+        return floor.getDelegate();
+    }
+
     @Override
     public ErrorHandler getErrorHandler() {
         return delegate.getErrorHandler();
@@ -107,6 +102,11 @@ public void setDTDHandler(final DTDHandler handler) {
         delegate.setDTDHandler(handler);
     }
 
+    @Override
+    public void setEntityResolver(final EntityResolver resolver) {
+        floor.setDelegate(resolver);
+    }
+
     @Override
     public void setErrorHandler(final ErrorHandler handler) {
         delegate.setErrorHandler(handler);
diff --git a/src/main/java/org/apache/commons/xml/HardeningXPath.java b/src/main/java/org/apache/commons/xml/HardeningXPath.java
index 8db5586..13cef3a 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXPath.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXPath.java
@@ -77,6 +77,12 @@ static Document parse(final InputSource source) throws XPathExpressionException
         this.delegate = Objects.requireNonNull(delegate, "delegate");
     }
 
+    @Override
+    public XPathExpression compile(final String expression) throws XPathExpressionException {
+        final XPathExpression compiled = delegate.compile(expression);
+        return compiled == null ? null : new HardeningXPathExpression(compiled);
+    }
+
     @Override
     public String evaluate(final String expression, final InputSource source) throws XPathExpressionException {
         return delegate.evaluate(expression, parse(source));
@@ -87,12 +93,6 @@ public Object evaluate(final String expression, final InputSource source, final
         return delegate.evaluate(expression, parse(source), returnType);
     }
 
-    @Override
-    public XPathExpression compile(final String expression) throws XPathExpressionException {
-        final XPathExpression compiled = delegate.compile(expression);
-        return compiled == null ? null : new HardeningXPathExpression(compiled);
-    }
-
     @Override
     public String evaluate(final String expression, final Object item) throws XPathExpressionException {
         return delegate.evaluate(expression, item);
diff --git a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
index c6d4cb0..a697032 100644
--- a/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
+++ b/src/main/java/org/apache/commons/xml/HardeningXPathFactory.java
@@ -40,12 +40,6 @@ final class HardeningXPathFactory extends XPathFactory {
         this.delegate = Objects.requireNonNull(delegate, "delegate");
     }
 
-    @Override
-    public XPath newXPath() {
-        final XPath xpath = delegate.newXPath();
-        return xpath == null ? null : new HardeningXPath(xpath);
-    }
-
     @Override
     public boolean getFeature(final String name) throws XPathFactoryConfigurationException {
         return delegate.getFeature(name);
@@ -56,6 +50,12 @@ public boolean isObjectModelSupported(final String objectModel) {
         return delegate.isObjectModelSupported(objectModel);
     }
 
+    @Override
+    public XPath newXPath() {
+        final XPath xpath = delegate.newXPath();
+        return xpath == null ? null : new HardeningXPath(xpath);
+    }
+
     @Override
     public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
         delegate.setFeature(name, value);
diff --git a/src/main/java/org/apache/commons/xml/SaxonProvider.java b/src/main/java/org/apache/commons/xml/SaxonProvider.java
index 2b600ba..f2af427 100644
--- a/src/main/java/org/apache/commons/xml/SaxonProvider.java
+++ b/src/main/java/org/apache/commons/xml/SaxonProvider.java
@@ -43,18 +43,6 @@
  */
 final class SaxonProvider {
 
-    /**
-     * Tells whether the factory class is Saxon's, by package prefix, so public subclasses such as {@code net.sf.saxon.BasicTransformerFactory} route to the
-     * same locked-down {@link Configuration} as the factory registered for JAXP lookup.
-     *
-     * @param factoryClass The factory implementation class.
-     * @return Whether the class lives in Saxon's open-source or commercial packages.
-     */
-    static boolean isSaxon(final Class<?> factoryClass) {
-        final String name = factoryClass.getName();
-        return name.startsWith("net.sf.saxon.") || name.startsWith("com.saxonica.");
-    }
-
     /**
      * A Saxon {@link Configuration} carrying the vendor-specific restrictions that the standard JAXP knobs cannot express.
      *
@@ -123,15 +111,6 @@ private static XPathFactory configure(final XPathFactory factory) {
         }
     }
 
-    /**
-     * The empty-{@link Source} shape Saxon's consumers expect, for the {@link FallbackIgnoreURIResolver} floor the TrAX wrapper installs.
-     *
-     * @return a supplier for Saxon's empty {@link Source}.
-     */
-    static Supplier<Source> emptySourceSupplier() {
-        return EmptySource::getInstance;
-    }
-
     static TransformerFactory configure(final TransformerFactory factory) {
         try {
             return SaxonProviderConfigurer.configure(factory);
@@ -156,6 +135,27 @@ static XPathFactory configure(final XPathFactory factory) {
         }
     }
 
+    /**
+     * The empty-{@link Source} shape Saxon's consumers expect, for the {@link FallbackIgnoreURIResolver} floor the TrAX wrapper installs.
+     *
+     * @return a supplier for Saxon's empty {@link Source}.
+     */
+    static Supplier<Source> emptySourceSupplier() {
+        return EmptySource::getInstance;
+    }
+
+    /**
+     * Tells whether the factory class is Saxon's, by package prefix, so public subclasses such as {@code net.sf.saxon.BasicTransformerFactory} route to the
+     * same locked-down {@link Configuration} as the factory registered for JAXP lookup.
+     *
+     * @param factoryClass The factory implementation class.
+     * @return Whether the class lives in Saxon's open-source or commercial packages.
+     */
+    static boolean isSaxon(final Class<?> factoryClass) {
+        final String name = factoryClass.getName();
+        return name.startsWith("net.sf.saxon.") || name.startsWith("com.saxonica.");
+    }
+
     private SaxonProvider() {
     }
 }
diff --git a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
index c4bb03f..3a553fd 100644
--- a/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
+++ b/src/test/java/org/apache/commons/xml/AssociatedStylesheetTest.java
@@ -41,6 +41,14 @@
 @Tag("trax")
 class AssociatedStylesheetTest {
 
+    /** The PI was found (non-null); where the engine exposes a system id, it points at the declared stylesheet. */
+    private static void assertAssociatedStylesheet(final Source associated) {
+        assertNotNull(associated, "expected the associated stylesheet PI to be found");
+        if (associated.getSystemId() != null) {
+            assertTrue(associated.getSystemId().endsWith("included.xsl"), "unexpected associated stylesheet: " + associated.getSystemId());
+        }
+    }
+
     private static TransformerFactory hardenedFactory() {
         final TransformerFactory factory = XmlFactories.newTransformerFactory();
         factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
@@ -65,14 +73,6 @@ void hardenedGetAssociatedStylesheetReturnsStylesheet() throws TransformerConfig
         assertAssociatedStylesheet(associated);
     }
 
-    /** The PI was found (non-null); where the engine exposes a system id, it points at the declared stylesheet. */
-    private static void assertAssociatedStylesheet(final Source associated) {
-        assertNotNull(associated, "expected the associated stylesheet PI to be found");
-        if (associated.getSystemId() != null) {
-            assertTrue(associated.getSystemId().endsWith("included.xsl"), "unexpected associated stylesheet: " + associated.getSystemId());
-        }
-    }
-
     @Test
     void unconfiguredGetAssociatedStylesheetFetchesExternalDtd() {
         // Leak/discrimination control: the unconfigured engine attempts to fetch the unreachable external DTD and fails. Android's KXmlParser does not fetch
diff --git a/src/test/java/org/apache/commons/xml/AttackTestSupport.java b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
index b7c4811..a5d87b2 100644
--- a/src/test/java/org/apache/commons/xml/AttackTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/AttackTestSupport.java
@@ -746,11 +746,6 @@ private static String blockedDescription(final String description) {
         return "Hardening did not block " + description + "; parse completed successfully.";
     }
 
-    /** Parses the payload through the supplied reader and returns the accumulated character data, used by the SAX-based {@code DoesNotLeak} helpers. */
-    static String captureCharacters(final XMLReader reader, final String payload) throws Exception {
-        return captureCharacters(reader, inputSource(payload));
-    }
-
     /** Parses the source through the supplied reader, with {@link #STRICT_REPORTER} installed, and returns the accumulated character data. */
     static String captureCharacters(final XMLReader reader, final InputSource source) throws Exception {
         final StringBuilder text = new StringBuilder();
@@ -759,6 +754,11 @@ static String captureCharacters(final XMLReader reader, final InputSource source
         return text.toString();
     }
 
+    /** Parses the payload through the supplied reader and returns the accumulated character data, used by the SAX-based {@code DoesNotLeak} helpers. */
+    static String captureCharacters(final XMLReader reader, final String payload) throws Exception {
+        return captureCharacters(reader, inputSource(payload));
+    }
+
     /** Parses the payload through a {@link XMLEventReader} and returns the accumulated character and CDATA data, used by the StAX-based {@code DoesNotLeak} helper. */
     static String captureStaxEventText(final XMLInputFactory factory, final String payload) throws Exception {
         final StringBuilder text = new StringBuilder();
@@ -883,13 +883,6 @@ private static void liftEntityLimits(final XMLReader reader) {
         }
     }
 
-    /**
-     * Builds a {@link SAXSource} wrapping the payload, without an explicit parser; used by the unconfigured-side TrAX controls.
-     */
-    private static SAXSource permissiveSaxSource(final String xml) {
-        return new SAXSource(permissiveReader(), new InputSource(new StringReader(xml)));
-    }
-
     /**
      * A permissive, namespace-aware {@link XMLReader} with every entity-expansion limit lifted, for the unconfigured-side controls.
      *
@@ -907,6 +900,13 @@ static XMLReader permissiveReader() {
         return IS_ANDROID ? new PermissiveExpatReader(reader) : reader;
     }
 
+    /**
+     * Builds a {@link SAXSource} wrapping the payload, without an explicit parser; used by the unconfigured-side TrAX controls.
+     */
+    private static SAXSource permissiveSaxSource(final String xml) {
+        return new SAXSource(permissiveReader(), new InputSource(new StringReader(xml)));
+    }
+
     private static boolean probeAndroid() {
         try {
             Class.forName("android.os.Build");
@@ -985,20 +985,20 @@ private static Templates strictTemplates(final TransformerFactory factory, final
     }
 
     /**
-     * Builds an identity {@link Transformer} from {@code factory} with {@link #STRICT_REPORTER} installed on both the factory and the resulting transformer.
+     * Builds a {@link Transformer} from {@code templates} with {@link #STRICT_REPORTER} installed as its error listener.
      */
-    private static Transformer strictTransformer(final TransformerFactory factory) throws TransformerConfigurationException {
-        factory.setErrorListener(STRICT_REPORTER);
-        final Transformer transformer = factory.newTransformer();
+    private static Transformer strictTransformer(final Templates templates) throws TransformerConfigurationException {
+        final Transformer transformer = templates.newTransformer();
         transformer.setErrorListener(STRICT_REPORTER);
         return transformer;
     }
 
     /**
-     * Builds a {@link Transformer} from {@code templates} with {@link #STRICT_REPORTER} installed as its error listener.
+     * Builds an identity {@link Transformer} from {@code factory} with {@link #STRICT_REPORTER} installed on both the factory and the resulting transformer.
      */
-    private static Transformer strictTransformer(final Templates templates) throws TransformerConfigurationException {
-        final Transformer transformer = templates.newTransformer();
+    private static Transformer strictTransformer(final TransformerFactory factory) throws TransformerConfigurationException {
+        factory.setErrorListener(STRICT_REPORTER);
+        final Transformer transformer = factory.newTransformer();
         transformer.setErrorListener(STRICT_REPORTER);
         return transformer;
     }
diff --git a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
index fb84f99..975ab47 100644
--- a/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
+++ b/src/test/java/org/apache/commons/xml/DenyUnresolvedTest.java
@@ -38,16 +38,16 @@ class DenyUnresolvedTest {
 
     private static final String SYSTEM_ID = "http://invalid.example.invalid/external.dtd";
 
-    @BeforeEach
-    void enableThrowOnUnresolved() {
-        System.setProperty(XmlFactories.THROW_ON_UNRESOLVED, "true");
-    }
-
     @AfterEach
     void clearThrowOnUnresolved() {
         System.clearProperty(XmlFactories.THROW_ON_UNRESOLVED);
     }
 
+    @BeforeEach
+    void enableThrowOnUnresolved() {
+        System.setProperty(XmlFactories.THROW_ON_UNRESOLVED, "true");
+    }
+
     @Test
     void floorsThrowOnUnresolved() {
         assertThrows(SAXException.class, () -> new FallbackIgnoreEntityResolver2(null).resolveEntity(null, SYSTEM_ID),
diff --git a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
index 529f6ea..e8fe90f 100644
--- a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
+++ b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java
@@ -74,12 +74,57 @@ class EntityResolverFloorTest {
     private static final EntityResolver ENTITY_ALLOW_LIST = (publicId, systemId) ->
             ALLOWED.equals(systemId) ? new InputSource(new URL(systemId).openStream()) : null;
 
+    /**
+     * Allow-all resolver: it denies nothing, resolving whatever {@code systemId} it is handed by opening it as a URL. It nonetheless cannot resolve a bare
+     * relative reference such as {@code referenced.xml}, because a plain {@link EntityResolver} (unlike {@link org.xml.sax.ext.EntityResolver2}) is given no
+     * base URI and the SAX2 contract promises it an already-absolutized {@code systemId}. So the resolution fails not from any deny decision but because the
+     * resolver was never handed the whole URL: it succeeds only if the floor absolutizes the XInclude href against the base before consulting the caller.
+     */
+    private static final EntityResolver RESOLVE_ALL = (publicId, systemId) -> {
+        final InputSource source = new InputSource(new URL(systemId).openStream());
+        source.setSystemId(systemId);
+        return source;
+    };
+
+    /** Absolute URL of the host document whose {@code xi:include} references {@code referenced.xml} by a relative href. */
+    private static final String XINCLUDE_HOST = AttackTestSupport.resourceUrl("with-xinclude.xml").toString();
+
+    /** Resolves only {@link #ALLOWED} to its content stream; returns {@code null} for anything else. */
+    private static final XMLResolver STAX_ALLOW_LIST = (publicID, systemID, baseURI, namespace) -> {
+        if (!ALLOWED.equals(systemID)) {
+            return null;
+        }
+        try {
+            return new URL(systemID).openStream();
+        } catch (final java.io.IOException e) {
+            throw new XMLStreamException(e);
+        }
+    };
+
+    /** Absolute location of the imported schema the allow-list resolver permits. */
+    private static final String ALLOWED_SCHEMA = AttackTestSupport.resourceUrl("included.xsd").toString();
+
+    /** Resolves only the {@code included.xsd} import; returns {@code null} for anything else. */
+    private static final LSResourceResolver SCHEMA_ALLOW_LIST = (type, namespaceURI, publicId, systemId, baseURI) ->
+            systemId != null && systemId.endsWith("included.xsd") ? lsInput(ALLOWED_SCHEMA) : null;
+
+    /** Resolves only the {@code included.xsl} import; returns {@code null} for anything else. */
+    private static final URIResolver XSL_ALLOW_LIST = (href, base) ->
+            href != null && href.endsWith("included.xsl") ? AttackTestSupport.resourceSource("included.xsl") : null;
+
     private static String entityPayload(final String entitySystemId) {
         return "<?xml version=\"1.0\"?>\n"
                 + "<!DOCTYPE root [\n  <!ENTITY xxe SYSTEM \"" + entitySystemId + "\">\n]>\n"
                 + "<root>&xxe;</root>";
     }
 
+    private static XMLInputFactory externalEntityStaxFactory() {
+        final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
+        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
+        factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
+        return factory;
+    }
+
     private static DocumentBuilder hardenedBuilder() throws Exception {
         final DocumentBuilder builder = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder();
         builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
@@ -92,15 +137,52 @@ private static XMLReader hardenedReader() throws Exception {
         return reader;
     }
 
-    @Test
-    @Tag("dom")
-    void domResolvesAllowListed() throws Exception {
-        Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "platform DOM does not resolve user-defined entities");
-        final DocumentBuilder builder = hardenedBuilder();
-        builder.setEntityResolver(ENTITY_ALLOW_LIST);
-        final Document doc = builder.parse(AttackTestSupport.inputSource(entityPayload(ALLOWED)));
-        assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
-                "allow-listed external entity should resolve through the caller's resolver");
+    /**
+     * A hardened {@link TransformerFactory} with a re-throwing error listener. XSLTC and Xalan enforce the block through the
+     * {@link FallbackIgnoreURIResolver} floor; Saxon enforces it through the ignore-all resolver floor on its {@code Configuration}. Either way a caller-set
+     * resolver that returns {@code null} cannot re-open the fetch. The strict listener turns any reported-and-recovered error into a test failure, so an
+     * implementation cannot quietly recover from a floor resolution while the test asserts clean completion.
+     */
+    private static TransformerFactory hardenedTransformerFactory() {
+        final TransformerFactory factory = XmlFactories.newTransformerFactory();
+        factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
+        return factory;
+    }
+
+    /** An {@link LSInput} naming the resource but carrying no content: a redirect the implementation fetches itself, like an identifier-only {@code InputSource}. */
+    private static LSInput identifierOnlyLsInput(final String systemId) {
+        return assertDoesNotThrow(() -> {
+            final DOMImplementationLS ls = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
+            final LSInput input = ls.createLSInput();
+            input.setSystemId(systemId);
+            return input;
+        }, "Failed to build identifier-only LSInput for " + systemId);
+    }
+
+    private static LSInput lsInput(final String systemId) {
+        return assertDoesNotThrow(() -> {
+            final LSInput input = identifierOnlyLsInput(systemId);
+            input.setByteStream(new URL(systemId).openStream());
+            return input;
+        }, "Failed to build LSInput for " + systemId);
+    }
+
+    private static DocumentBuilder xIncludeAwareBuilder() throws Exception {
+        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+        factory.setNamespaceAware(true);
+        AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+        builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        return builder;
+    }
+
+    private static XMLReader xIncludeAwareReader() throws Exception {
+        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+        factory.setNamespaceAware(true);
+        AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
+        final XMLReader reader = factory.newSAXParser().getXMLReader();
+        reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        return reader;
     }
 
     @Test
@@ -120,28 +202,24 @@ void domDoesNotLeakUnlisted() throws Exception {
     }
 
     @Test
-    @Tag("sax")
-    void saxReaderResolvesAllowListed() throws Exception {
-        final XMLReader reader = hardenedReader();
-        reader.setEntityResolver(ENTITY_ALLOW_LIST);
-        final String text = AttackTestSupport.captureCharacters(reader, entityPayload(ALLOWED));
-        assertTrue(text.contains(AttackTestSupport.LEAKED_MARKER),
+    @Tag("dom")
+    void domResolvesAllowListed() throws Exception {
+        Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "platform DOM does not resolve user-defined entities");
+        final DocumentBuilder builder = hardenedBuilder();
+        builder.setEntityResolver(ENTITY_ALLOW_LIST);
+        final Document doc = builder.parse(AttackTestSupport.inputSource(entityPayload(ALLOWED)));
+        assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
                 "allow-listed external entity should resolve through the caller's resolver");
     }
 
     @Test
-    @Tag("sax")
-    void saxReaderDoesNotLeakUnlisted() throws Exception {
-        final XMLReader reader = hardenedReader();
-        reader.setEntityResolver(ENTITY_ALLOW_LIST);
-        // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
-        final String text;
-        try {
-            text = AttackTestSupport.captureCharacters(reader, entityPayload(UNLISTED));
-        } catch (final SAXException blocked) {
-            return; // Acceptable: rejected at parse rather than resolved to empty.
-        }
-        assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "unlisted external entity leaked:\n" + text);
+    @Tag("dom")
+    void domResolvesRelativeXIncludeSibling() throws Exception {
+        final DocumentBuilder builder = xIncludeAwareBuilder();
+        builder.setEntityResolver(RESOLVE_ALL);
+        final Document doc = builder.parse(XINCLUDE_HOST);
+        assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
+                "relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
     }
 
     @Test
@@ -159,26 +237,29 @@ void saxParseWithHandlerDoesNotBypass() throws Exception {
         assertFalse(text.toString().contains(AttackTestSupport.LEAKED_MARKER), "parse(source, handler) leaked the external entity:\n" + text);
     }
 
-    /**
-     * Allow-all resolver: it denies nothing, resolving whatever {@code systemId} it is handed by opening it as a URL. It nonetheless cannot resolve a bare
-     * relative reference such as {@code referenced.xml}, because a plain {@link EntityResolver} (unlike {@link org.xml.sax.ext.EntityResolver2}) is given no
-     * base URI and the SAX2 contract promises it an already-absolutized {@code systemId}. So the resolution fails not from any deny decision but because the
-     * resolver was never handed the whole URL: it succeeds only if the floor absolutizes the XInclude href against the base before consulting the caller.
-     */
-    private static final EntityResolver RESOLVE_ALL = (publicId, systemId) -> {
-        final InputSource source = new InputSource(new URL(systemId).openStream());
-        source.setSystemId(systemId);
-        return source;
-    };
+    @Test
+    @Tag("sax")
+    void saxReaderDoesNotLeakUnlisted() throws Exception {
+        final XMLReader reader = hardenedReader();
+        reader.setEntityResolver(ENTITY_ALLOW_LIST);
+        // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
+        final String text;
+        try {
+            text = AttackTestSupport.captureCharacters(reader, entityPayload(UNLISTED));
+        } catch (final SAXException blocked) {
+            return; // Acceptable: rejected at parse rather than resolved to empty.
+        }
+        assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "unlisted external entity leaked:\n" + text);
+    }
 
     @Test
-    @Tag("dom")
-    void domResolvesRelativeXIncludeSibling() throws Exception {
-        final DocumentBuilder builder = xIncludeAwareBuilder();
-        builder.setEntityResolver(RESOLVE_ALL);
-        final Document doc = builder.parse(XINCLUDE_HOST);
-        assertTrue(doc.getDocumentElement().getTextContent().contains(AttackTestSupport.LEAKED_MARKER),
-                "relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
+    @Tag("sax")
+    void saxReaderResolvesAllowListed() throws Exception {
+        final XMLReader reader = hardenedReader();
+        reader.setEntityResolver(ENTITY_ALLOW_LIST);
+        final String text = AttackTestSupport.captureCharacters(reader, entityPayload(ALLOWED));
+        assertTrue(text.contains(AttackTestSupport.LEAKED_MARKER),
+                "allow-listed external entity should resolve through the caller's resolver");
     }
 
     @Test
@@ -191,64 +272,48 @@ void saxResolvesRelativeXIncludeSibling() throws Exception {
                 "relative XInclude sibling should resolve through the caller's resolver after the floor absolutizes the href");
     }
 
-    /** Absolute URL of the host document whose {@code xi:include} references {@code referenced.xml} by a relative href. */
-    private static final String XINCLUDE_HOST = AttackTestSupport.resourceUrl("with-xinclude.xml").toString();
-
-    private static DocumentBuilder xIncludeAwareBuilder() throws Exception {
-        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
-        factory.setNamespaceAware(true);
-        AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
-        final DocumentBuilder builder = factory.newDocumentBuilder();
-        builder.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        return builder;
-    }
-
-    private static XMLReader xIncludeAwareReader() throws Exception {
-        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
-        factory.setNamespaceAware(true);
-        AttackTestSupport.assumeDoesNotThrow(() -> factory.setXIncludeAware(true));
-        final XMLReader reader = factory.newSAXParser().getXMLReader();
-        reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        return reader;
+    @Test
+    @Tag("schema")
+    void schemaDeniesUnlisted() {
+        assertParseFails(() -> {
+            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> null);
+            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+        }, "Schema import", SAXException.class, SecurityException.class);
     }
 
-    /** Resolves only {@link #ALLOWED} to its content stream; returns {@code null} for anything else. */
-    private static final XMLResolver STAX_ALLOW_LIST = (publicID, systemID, baseURI, namespace) -> {
-        if (!ALLOWED.equals(systemID)) {
-            return null;
-        }
-        try {
-            return new URL(systemID).openStream();
-        } catch (final java.io.IOException e) {
-            throw new XMLStreamException(e);
-        }
-    };
-
-    private static XMLInputFactory externalEntityStaxFactory() {
-        final XMLInputFactory factory = XmlFactories.newXMLInputFactory();
-        factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
-        factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
-        return factory;
+    @Test
+    @Tag("schema")
+    void schemaFetchesIdentifierOnlyOptIn() {
+        // A non-null return is an opt-in even without content: the implementation fetches the named resource itself, mirroring the entity floor's contract.
+        assertParseSucceeds(() -> {
+            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) ->
+                    systemId != null && systemId.endsWith("included.xsd") ? identifierOnlyLsInput(ALLOWED_SCHEMA) : null);
+            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+        }, "Schema import via identifier-only LSInput");
     }
 
     @Test
-    @Tag("stax")
-    void staxResolvesAllowListed() throws Exception {
-        final XMLInputFactory factory = externalEntityStaxFactory();
-        factory.setXMLResolver(STAX_ALLOW_LIST);
-        assertTrue(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
-                "allow-listed external entity should resolve through the caller's resolver");
+    @Tag("schema")
+    void schemaResolvesAllowListed() {
+        // with-import.xsd references an element defined only in the imported included.xsd, so it compiles only if the import is resolved.
+        assertParseSucceeds(() -> {
+            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            factory.setResourceResolver(SCHEMA_ALLOW_LIST);
+            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
+        }, "Schema import via caller resolver");
     }
 
     @Test
     @Tag("stax")
-    void staxDoesNotLeakUnlisted() throws Exception {
+    void staxCallerCannotRemoveFloor() throws Exception {
+        // A caller resolver that resolves nothing must not re-open external fetches: the floor still resolves the reference to empty rather than fetching it.
         final XMLInputFactory factory = externalEntityStaxFactory();
-        factory.setXMLResolver(STAX_ALLOW_LIST);
-        // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
+        factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> null);
         try {
-            assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(UNLISTED)).contains(AttackTestSupport.LEAKED_MARKER),
-                    "unlisted external entity leaked");
+            assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
+                    "floor was bypassed and the entity leaked");
         } catch (final XMLStreamException blocked) {
             // Acceptable: rejected at parse rather than resolved to empty.
         }
@@ -256,13 +321,13 @@ void staxDoesNotLeakUnlisted() throws Exception {
 
     @Test
     @Tag("stax")
-    void staxCallerCannotRemoveFloor() throws Exception {
-        // A caller resolver that resolves nothing must not re-open external fetches: the floor still resolves the reference to empty rather than fetching it.
+    void staxDoesNotLeakUnlisted() throws Exception {
         final XMLInputFactory factory = externalEntityStaxFactory();
-        factory.setXMLResolver((publicID, systemID, baseURI, namespace) -> null);
+        factory.setXMLResolver(STAX_ALLOW_LIST);
+        // The caller returns null for the unlisted entity, so the floor resolves it to empty rather than fetching it.
         try {
-            assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
-                    "floor was bypassed and the entity leaked");
+            assertFalse(AttackTestSupport.captureStaxEventText(factory, entityPayload(UNLISTED)).contains(AttackTestSupport.LEAKED_MARKER),
+                    "unlisted external entity leaked");
         } catch (final XMLStreamException blocked) {
             // Acceptable: rejected at parse rather than resolved to empty.
         }
@@ -277,75 +342,13 @@ void staxGetXMLResolverReportsCallerUnwrapped() {
         assertSame(caller, factory.getXMLResolver(), "getXMLResolver should report the caller's resolver, not the floor wrapper");
     }
 
-    /** Absolute location of the imported schema the allow-list resolver permits. */
-    private static final String ALLOWED_SCHEMA = AttackTestSupport.resourceUrl("included.xsd").toString();
-
-    /** Resolves only the {@code included.xsd} import; returns {@code null} for anything else. */
-    private static final LSResourceResolver SCHEMA_ALLOW_LIST = (type, namespaceURI, publicId, systemId, baseURI) ->
-            systemId != null && systemId.endsWith("included.xsd") ? lsInput(ALLOWED_SCHEMA) : null;
-
-    private static LSInput lsInput(final String systemId) {
-        return assertDoesNotThrow(() -> {
-            final LSInput input = identifierOnlyLsInput(systemId);
-            input.setByteStream(new URL(systemId).openStream());
-            return input;
-        }, "Failed to build LSInput for " + systemId);
-    }
-
-    /** An {@link LSInput} naming the resource but carrying no content: a redirect the implementation fetches itself, like an identifier-only {@code InputSource}. */
-    private static LSInput identifierOnlyLsInput(final String systemId) {
-        return assertDoesNotThrow(() -> {
-            final DOMImplementationLS ls = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
-            final LSInput input = ls.createLSInput();
-            input.setSystemId(systemId);
-            return input;
-        }, "Failed to build identifier-only LSInput for " + systemId);
-    }
-
-    @Test
-    @Tag("schema")
-    void schemaResolvesAllowListed() {
-        // with-import.xsd references an element defined only in the imported included.xsd, so it compiles only if the import is resolved.
-        assertParseSucceeds(() -> {
-            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            factory.setResourceResolver(SCHEMA_ALLOW_LIST);
-            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
-        }, "Schema import via caller resolver");
-    }
-
     @Test
-    @Tag("schema")
-    void schemaDeniesUnlisted() {
-        assertParseFails(() -> {
-            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) -> null);
-            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
-        }, "Schema import", SAXException.class, SecurityException.class);
-    }
-
-    @Test
-    @Tag("schema")
-    void schemaFetchesIdentifierOnlyOptIn() {
-        // A non-null return is an opt-in even without content: the implementation fetches the named resource itself, mirroring the entity floor's contract.
-        assertParseSucceeds(() -> {
-            final SchemaFactory factory = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            factory.setResourceResolver((type, namespaceURI, publicId, systemId, baseURI) ->
-                    systemId != null && systemId.endsWith("included.xsd") ? identifierOnlyLsInput(ALLOWED_SCHEMA) : null);
-            factory.newSchema(AttackTestSupport.resourceSource("with-import.xsd"));
-        }, "Schema import via identifier-only LSInput");
-    }
-
-    /** Resolves only the {@code included.xsl} import; returns {@code null} for anything else. */
-    private static final URIResolver XSL_ALLOW_LIST = (href, base) ->
-            href != null && href.endsWith("included.xsl") ? AttackTestSupport.resourceSource("included.xsl") : null;
-
-    @Test
-    @Tag("trax")
-    void transformerResolvesAllowListed() {
-        // with-import.xsl imports included.xsl, so it compiles only if the import is resolved.
-        final TransformerFactory factory = hardenedTransformerFactory();
-        factory.setURIResolver(XSL_ALLOW_LIST);
-        assertParseSucceeds(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-import.xsl")), "Stylesheet import via caller resolver");
+    @Tag("stax")
+    void staxResolvesAllowListed() throws Exception {
+        final XMLInputFactory factory = externalEntityStaxFactory();
+        factory.setXMLResolver(STAX_ALLOW_LIST);
+        assertTrue(AttackTestSupport.captureStaxEventText(factory, entityPayload(ALLOWED)).contains(AttackTestSupport.LEAKED_MARKER),
+                "allow-listed external entity should resolve through the caller's resolver");
     }
 
     @Test
@@ -361,6 +364,20 @@ void transformerDoesNotLeakUnlisted() {
         assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "unlisted stylesheet import leaked");
     }
 
+    @Test
+    @Tag("trax")
+    void transformerParsesOptedInDocumentHardened() {
+        // Same contract on the runtime document() channel, which reaches a different internal reader than the compile-time import.
+        final TransformerFactory factory = hardenedTransformerFactory();
+        factory.setURIResolver((href, base) ->
+                href != null && href.endsWith("referenced.xml") ? AttackTestSupport.resourceSource("referenced-with-entity.xml") : null);
+        // Same undeclared-entity outcome as the import above: skipped, never expanded.
+        final StringWriter sink = new StringWriter();
+        assertDoesNotThrow(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer()
+                .transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink)));
+        assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "opted-in document() resource leaked its external entity");
+    }
+
     @Test
     @Tag("trax")
     void transformerParsesOptedInImportHardened() {
@@ -378,27 +395,10 @@ void transformerParsesOptedInImportHardened() {
 
     @Test
     @Tag("trax")
-    void transformerParsesOptedInDocumentHardened() {
-        // Same contract on the runtime document() channel, which reaches a different internal reader than the compile-time import.
+    void transformerResolvesAllowListed() {
+        // with-import.xsl imports included.xsl, so it compiles only if the import is resolved.
         final TransformerFactory factory = hardenedTransformerFactory();
-        factory.setURIResolver((href, base) ->
-                href != null && href.endsWith("referenced.xml") ? AttackTestSupport.resourceSource("referenced-with-entity.xml") : null);
-        // Same undeclared-entity outcome as the import above: skipped, never expanded.
-        final StringWriter sink = new StringWriter();
-        assertDoesNotThrow(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-document.xsl")).newTransformer()
-                .transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink)));
-        assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "opted-in document() resource leaked its external entity");
-    }
-
-    /**
-     * A hardened {@link TransformerFactory} with a re-throwing error listener. XSLTC and Xalan enforce the block through the
-     * {@link FallbackIgnoreURIResolver} floor; Saxon enforces it through the ignore-all resolver floor on its {@code Configuration}. Either way a caller-set
-     * resolver that returns {@code null} cannot re-open the fetch. The strict listener turns any reported-and-recovered error into a test failure, so an
-     * implementation cannot quietly recover from a floor resolution while the test asserts clean completion.
-     */
-    private static TransformerFactory hardenedTransformerFactory() {
-        final TransformerFactory factory = XmlFactories.newTransformerFactory();
-        factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
-        return factory;
+        factory.setURIResolver(XSL_ALLOW_LIST);
+        assertParseSucceeds(() -> factory.newTemplates(AttackTestSupport.resourceSource("with-import.xsl")), "Stylesheet import via caller resolver");
     }
 }
diff --git a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
index 93c0de9..687df35 100644
--- a/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
+++ b/src/test/java/org/apache/commons/xml/ResetHardeningTest.java
@@ -86,20 +86,6 @@ void saxParserResetKeepsEntityResolverFloor() throws Exception {
         assertFalse(text.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked after reset:\n" + text);
     }
 
-    @Test
-    @Tag("schema")
-    void validatorResetKeepsResourceResolverFloor() throws Exception {
-        // A Schema built without sources validates against the instance's xsi:schemaLocation hints, so the resolver floor is the only barrier between the
-        // validator and the external schema fetch.
-        final Validator validator = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema().newValidator();
-        AttackTestSupport.assumeDoesNotThrow(validator::reset);
-        validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        // schema-location-instance.xml hints at schema-location.xsd, which declares its root: a validator whose floor was stripped fetches it and validates
-        // cleanly, while the floor resolves the hint to empty content, which fails the validation.
-        AttackTestSupport.assertParseFails(() -> validator.validate(AttackTestSupport.resourceSource("schema-location-instance.xml")),
-                "Validator after reset", SAXException.class, SecurityException.class);
-    }
-
     @Test
     @Tag("trax")
     void transformerResetKeepsUriResolverFloor() throws Exception {
@@ -115,4 +101,18 @@ void transformerResetKeepsUriResolverFloor() throws Exception {
         }
         assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() leaked after reset:\n" + sink);
     }
+
+    @Test
+    @Tag("schema")
+    void validatorResetKeepsResourceResolverFloor() throws Exception {
+        // A Schema built without sources validates against the instance's xsi:schemaLocation hints, so the resolver floor is the only barrier between the
+        // validator and the external schema fetch.
+        final Validator validator = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema().newValidator();
+        AttackTestSupport.assumeDoesNotThrow(validator::reset);
+        validator.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        // schema-location-instance.xml hints at schema-location.xsd, which declares its root: a validator whose floor was stripped fetches it and validates
+        // cleanly, while the floor resolves the hint to empty content, which fails the validation.
+        AttackTestSupport.assertParseFails(() -> validator.validate(AttackTestSupport.resourceSource("schema-location-instance.xml")),
+                "Validator after reset", SAXException.class, SecurityException.class);
+    }
 }
diff --git a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
index 76909d4..3a52ec9 100644
--- a/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
+++ b/src/test/java/org/apache/commons/xml/SaxSurfaceTestSupport.java
@@ -37,16 +37,21 @@
  */
 final class SaxSurfaceTestSupport {
 
+    /** Feeds the input's SAX events into the handler through a hardened, namespace-aware reader. */
+    static void feed(final ContentHandler handler, final InputSource input) throws Exception {
+        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+        factory.setNamespaceAware(true);
+        final XMLReader reader = factory.newSAXParser().getXMLReader();
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
+        reader.parse(input);
+    }
+
     /** The hardened factory, as its runtime {@link SAXTransformerFactory} type. */
     static SAXTransformerFactory hardenedFactory() {
         return (SAXTransformerFactory) XmlFactories.newTransformerFactory();
     }
 
-    /** A benign {@code <root/>} input document. */
-    static InputSource rootInput() {
-        return new InputSource(new StringReader("<root/>"));
-    }
-
     /** Opens a fixture under {@code leaked/} as an {@link InputSource} preserving its system id, so relative hrefs resolve normally. */
     static InputSource resourceInput(final String name) throws IOException {
         final URL url = AttackTestSupport.resourceUrl(name);
@@ -55,14 +60,9 @@ static InputSource resourceInput(final String name) throws IOException {
         return input;
     }
 
-    /** Feeds the input's SAX events into the handler through a hardened, namespace-aware reader. */
-    static void feed(final ContentHandler handler, final InputSource input) throws Exception {
-        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
-        factory.setNamespaceAware(true);
-        final XMLReader reader = factory.newSAXParser().getXMLReader();
-        reader.setContentHandler(handler);
-        reader.setErrorHandler(AttackTestSupport.STRICT_REPORTER);
-        reader.parse(input);
+    /** A benign {@code <root/>} input document. */
+    static InputSource rootInput() {
+        return new InputSource(new StringReader("<root/>"));
     }
 
     private SaxSurfaceTestSupport() {
diff --git a/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java b/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
index 36e15a1..ea21886 100644
--- a/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
+++ b/src/test/java/org/apache/commons/xml/SaxonAlternateFactoryTest.java
@@ -45,15 +45,6 @@ class SaxonAlternateFactoryTest {
 
     private static final String BASIC_FACTORY_CLASS = "net.sf.saxon.BasicTransformerFactory";
 
-    /** Instantiates {@code BasicTransformerFactory} reflectively, so this test compiles and loads without Saxon on the classpath. */
-    private static TransformerFactory basicSaxonFactory() {
-        try {
-            return (TransformerFactory) Class.forName(BASIC_FACTORY_CLASS).getDeclaredConstructor().newInstance();
-        } catch (final ReflectiveOperationException e) {
-            throw new AssertionError("Cannot instantiate " + BASIC_FACTORY_CLASS, e);
-        }
-    }
-
     private static void assumeSaxonPresent() {
         boolean present;
         try {
@@ -65,6 +56,15 @@ private static void assumeSaxonPresent() {
         Assumptions.assumeTrue(present, "Saxon is not on the classpath");
     }
 
+    /** Instantiates {@code BasicTransformerFactory} reflectively, so this test compiles and loads without Saxon on the classpath. */
+    private static TransformerFactory basicSaxonFactory() {
+        try {
+            return (TransformerFactory) Class.forName(BASIC_FACTORY_CLASS).getDeclaredConstructor().newInstance();
+        } catch (final ReflectiveOperationException e) {
+            throw new AssertionError("Cannot instantiate " + BASIC_FACTORY_CLASS, e);
+        }
+    }
+
     /** A {@code collection()} over the test fixtures whose {@code referenced.xml} carries {@link AttackTestSupport#LEAKED_MARKER}. */
     private static String collectionStylesheet() {
         final String collection = AttackTestSupport.resourceUrl("referenced.xml").toString().replaceFirst("referenced\\.xml$", "?select=referenced.xml");
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
index 869af34..7cd4c3b 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationDomTest.java
@@ -55,32 +55,6 @@ class SchemaLocationDomTest {
 
     private static final String INSTANCE = "schema-location-instance.xml";
 
-    @Test
-    void hardenedDoesNotFetchExternalSchema() {
-        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
-        final DocumentBuilderFactory factory = enableXsdValidation(XmlFactories.newDocumentBuilderFactory());
-        // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
-        // parse completes but the schema's default leak attribute is never inlined. Either way the marker must not reach the DOM.
-        try {
-            final Document document = parse(factory);
-            assertNotEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
-                    "Hardened parse must not inline the external schema's default attribute.");
-        } catch (final Exception blocked) {
-            // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or inlined.
-        }
-    }
-
-    @Test
-    void unconfiguredFetchesExternalSchema() throws Exception {
-        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
-        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
-        // Positive control: without hardening the external schema is fetched and its default attribute is inlined into the DOM.
-        final Document document = parse(enableXsdValidation(factory));
-        assertEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
-                "Permissive parse should have fetched the external schema and inlined its default attribute.");
-    }
-
     private static DocumentBuilderFactory enableXsdValidation(final DocumentBuilderFactory factory) {
         factory.setNamespaceAware(true);
         factory.setValidating(true);
@@ -109,4 +83,30 @@ private static boolean supportsSchemaLanguage() {
             return false;
         }
     }
+
+    @Test
+    void hardenedDoesNotFetchExternalSchema() {
+        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+        final DocumentBuilderFactory factory = enableXsdValidation(XmlFactories.newDocumentBuilderFactory());
+        // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
+        // parse completes but the schema's default leak attribute is never inlined. Either way the marker must not reach the DOM.
+        try {
+            final Document document = parse(factory);
+            assertNotEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
+                    "Hardened parse must not inline the external schema's default attribute.");
+        } catch (final Exception blocked) {
+            // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or inlined.
+        }
+    }
+
+    @Test
+    void unconfiguredFetchesExternalSchema() throws Exception {
+        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+        // Positive control: without hardening the external schema is fetched and its default attribute is inlined into the DOM.
+        final Document document = parse(enableXsdValidation(factory));
+        assertEquals(LEAKED_MARKER, document.getDocumentElement().getAttribute("leak"),
+                "Permissive parse should have fetched the external schema and inlined its default attribute.");
+    }
 }
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
index 8fc03db..127c920 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationPropertyTest.java
@@ -68,14 +68,6 @@ class SchemaLocationPropertyTest {
     /** Instance whose root, {@code l:leaked}, is declared by {@code included.xsd} in the {@value #LEAKED_NS} namespace. */
     private static final String NAMESPACED_INSTANCE = "<l:leaked xmlns:l=\"" + LEAKED_NS + "\">x</l:leaked>";
 
-    private static String noNamespaceLocation() {
-        return resourceUrl("no-namespace.xsd").toString();
-    }
-
-    private static String namespacedLocation() {
-        return LEAKED_NS + " " + resourceUrl("included.xsd");
-    }
-
     /**
      * Runs the parser setup, skipping the test (rather than failing it) on parsers that do not accept these
      * schema-validation features/properties, such as Android's KXmlParser and Expat.
@@ -99,18 +91,6 @@ private static DocumentBuilder hardenedValidatingDom(final String property, fina
         });
     }
 
-    private static DocumentBuilder permissiveValidatingDom(final String property, final String value) {
-        return configureOrSkip(() -> {
-            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-            factory.setNamespaceAware(true);
-            factory.setValidating(true);
-            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
-            factory.setAttribute(SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            factory.setAttribute(property, value);
-            return strictDocumentBuilder(factory);
-        });
-    }
-
     private static XMLReader hardenedValidatingSax(final String property, final String value) {
         return configureOrSkip(() -> {
             final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
@@ -123,6 +103,26 @@ private static XMLReader hardenedValidatingSax(final String property, final Stri
         });
     }
 
+    private static String namespacedLocation() {
+        return LEAKED_NS + " " + resourceUrl("included.xsd");
+    }
+
+    private static String noNamespaceLocation() {
+        return resourceUrl("no-namespace.xsd").toString();
+    }
+
+    private static DocumentBuilder permissiveValidatingDom(final String property, final String value) {
+        return configureOrSkip(() -> {
+            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.setValidating(true);
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+            factory.setAttribute(SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            factory.setAttribute(property, value);
+            return strictDocumentBuilder(factory);
+        });
+    }
+
     private static XMLReader permissiveValidatingSax(final String property, final String value) {
         return configureOrSkip(() -> {
             final SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -143,42 +143,42 @@ void hardenedDomRefusesNoNamespaceSchemaLocation() {
         assertParseFails(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation", SAXException.class);
     }
 
-    @Test
-    void permissiveDomFetchesNoNamespaceSchemaLocation() {
-        final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_NO_NS, noNamespaceLocation());
-        assertParseSucceeds(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation (permissive)");
-    }
-
     @Test
     void hardenedDomRefusesSchemaLocation() {
         final DocumentBuilder builder = hardenedValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
         assertParseFails(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation", SAXException.class);
     }
 
-    @Test
-    void permissiveDomFetchesSchemaLocation() {
-        final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
-        assertParseSucceeds(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation (permissive)");
-    }
-
     @Test
     void hardenedSaxRefusesNoNamespaceSchemaLocation() {
         final XMLReader reader = hardenedValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
         assertParseFails(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation", SAXException.class);
     }
 
-    @Test
-    void permissiveSaxFetchesNoNamespaceSchemaLocation() {
-        final XMLReader reader = permissiveValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
-        assertParseSucceeds(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation (permissive)");
-    }
-
     @Test
     void hardenedSaxRefusesSchemaLocation() {
         final XMLReader reader = hardenedValidatingSax(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
         assertParseFails(() -> reader.parse(inputSource(NAMESPACED_INSTANCE)), "SAX external-schemaLocation", SAXException.class);
     }
 
+    @Test
+    void permissiveDomFetchesNoNamespaceSchemaLocation() {
+        final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_NO_NS, noNamespaceLocation());
+        assertParseSucceeds(() -> builder.parse(inputSource(NO_NS_INSTANCE)), "DOM external-noNamespaceSchemaLocation (permissive)");
+    }
+
+    @Test
+    void permissiveDomFetchesSchemaLocation() {
+        final DocumentBuilder builder = permissiveValidatingDom(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
+        assertParseSucceeds(() -> builder.parse(inputSource(NAMESPACED_INSTANCE)), "DOM external-schemaLocation (permissive)");
+    }
+
+    @Test
+    void permissiveSaxFetchesNoNamespaceSchemaLocation() {
+        final XMLReader reader = permissiveValidatingSax(EXTERNAL_NO_NS, noNamespaceLocation());
+        assertParseSucceeds(() -> reader.parse(inputSource(NO_NS_INSTANCE)), "SAX external-noNamespaceSchemaLocation (permissive)");
+    }
+
     @Test
     void permissiveSaxFetchesSchemaLocation() {
         final XMLReader reader = permissiveValidatingSax(EXTERNAL_SCHEMA_LOCATION, namespacedLocation());
diff --git a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
index b7317e1..7d30e35 100644
--- a/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
+++ b/src/test/java/org/apache/commons/xml/SchemaLocationSaxTest.java
@@ -67,34 +67,6 @@ public void startElement(final String uri, final String localName, final String
 
     private static final String INSTANCE = "schema-location-instance.xml";
 
-    @Test
-    void hardenedDoesNotFetchExternalSchema() throws Exception {
-        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
-        final SAXParser parser = newValidatingParser(XmlFactories.newSAXParserFactory());
-        // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
-        // parse completes but the schema's default leak attribute is never augmented onto the element. Either way the marker must not be observed.
-        final LeakCapturingHandler handler = new LeakCapturingHandler();
-        try {
-            parse(parser, handler);
-        } catch (final Exception blocked) {
-            // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or augmented.
-        }
-        assertNull(handler.leak, "Hardened parse must not augment the external schema's default attribute onto the element.");
-    }
-
-    @Test
-    void unconfiguredFetchesExternalSchema() throws Exception {
-        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
-        final SAXParserFactory factory = SAXParserFactory.newInstance();
-        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
-        final SAXParser parser = newValidatingParser(factory);
-        // Positive control: without hardening the external schema is fetched and its default attribute is augmented onto the root element.
-        final LeakCapturingHandler handler = new LeakCapturingHandler();
-        parse(parser, handler);
-        assertEquals(LEAKED_MARKER, handler.leak,
-                "Permissive parse should have fetched the external schema and augmented its default attribute onto the element.");
-    }
-
     private static SAXParser newValidatingParser(final SAXParserFactory factory) throws Exception {
         factory.setNamespaceAware(true);
         factory.setValidating(true);
@@ -124,4 +96,32 @@ private static boolean supportsSchemaLanguage() {
             return false;
         }
     }
+
+    @Test
+    void hardenedDoesNotFetchExternalSchema() throws Exception {
+        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+        final SAXParser parser = newValidatingParser(XmlFactories.newSAXParserFactory());
+        // The schemaLocation reference resolves to empty rather than being fetched. Either the empty schema fails the validating parse (acceptable), or the
+        // parse completes but the schema's default leak attribute is never augmented onto the element. Either way the marker must not be observed.
+        final LeakCapturingHandler handler = new LeakCapturingHandler();
+        try {
+            parse(parser, handler);
+        } catch (final Exception blocked) {
+            // Acceptable: the empty schema was rejected at parse time, so nothing was fetched or augmented.
+        }
+        assertNull(handler.leak, "Hardened parse must not augment the external schema's default attribute onto the element.");
+    }
+
+    @Test
+    void unconfiguredFetchesExternalSchema() throws Exception {
+        assumeTrue(supportsSchemaLanguage(), "parser does not support JAXP 1.2 schema-language XSD validation");
+        final SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
+        final SAXParser parser = newValidatingParser(factory);
+        // Positive control: without hardening the external schema is fetched and its default attribute is augmented onto the root element.
+        final LeakCapturingHandler handler = new LeakCapturingHandler();
+        parse(parser, handler);
+        assertEquals(LEAKED_MARKER, handler.leak,
+                "Permissive parse should have fetched the external schema and augmented its default attribute onto the element.");
+    }
 }
diff --git a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
index 4972cba..53ad525 100644
--- a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
+++ b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java
@@ -100,6 +100,39 @@ class ShadingFootprintTest {
     private static Clazzpath clazzpath;
     private static Path classesDir;
 
+    /**
+     * Sums the uncompressed {@code .class} file sizes of a closure's classes, as they would land in a shaded jar.
+     */
+    private static long bytesOf(final Set<String> simpleNames) {
+        long total = 0;
+        for (final String name : simpleNames) {
+            try {
+                total += Files.size(classesDir.resolve("org/apache/commons/xml/" + name + ".class"));
+            } catch (final IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        return total;
+    }
+
+    /**
+     * Transitive class closure of {@code PKG + simpleName}, restricted to this library's own package and reported by simple name.
+     */
+    private static Set<String> closureOf(final String simpleName) {
+        final Clazz entry = clazzpath.getClazz(PKG + simpleName);
+        if (entry == null) {
+            throw new IllegalStateException("Not on the compiled classpath: " + PKG + simpleName);
+        }
+        final Set<String> names = new TreeSet<>();
+        names.add(strip(entry.getName()));
+        for (final Clazz dependency : entry.getTransitiveDependencies()) {
+            if (dependency.getName().startsWith(PKG)) {
+                names.add(strip(dependency.getName()));
+            }
+        }
+        return names;
+    }
+
     @BeforeAll
     static void indexCompiledClasses() throws Exception {
         classesDir = Paths.get(HardeningException.class.getProtectionDomain().getCodeSource().getLocation().toURI());
@@ -123,29 +156,36 @@ static void reportFootprint() {
         System.out.print(report);
     }
 
-    @Test
-    void documentBuilderHardenerFootprint() {
-        assertEquals(DOCUMENT_BUILDER_HARDENER, closureOf("DocumentBuilderHardener"));
+    /**
+     * {@link #SAX_PARSER_HARDENER} plus the extra names; used where an entry point's closure is the SAX path plus its own classes.
+     */
+    private static Set<String> saxParsersHardenerPlus(final String... more) {
+        final Set<String> union = new TreeSet<>(SAX_PARSER_HARDENER);
+        union.addAll(Arrays.asList(more));
+        return union;
     }
 
-    @Test
-    void saxParserHardenerFootprint() {
-        assertEquals(SAX_PARSER_HARDENER, closureOf("SAXParserHardener"));
+    private static Set<String> set(final String... names) {
+        return new TreeSet<>(Arrays.asList(names));
+    }
+
+    private static String strip(final String qualifiedName) {
+        return qualifiedName.substring(PKG.length());
     }
 
     @Test
-    void staxHardenerFootprint() {
-        assertEquals(STAX_HARDENER, closureOf("StaxHardener"));
+    void documentBuilderHardenerFootprint() {
+        assertEquals(DOCUMENT_BUILDER_HARDENER, closureOf("DocumentBuilderHardener"));
     }
 
     @Test
-    void transformerHardenerFootprint() {
-        assertEquals(TRANSFORMER_HARDENER, closureOf("TransformerHardener"));
+    void onlyXmlFactoriesPullsTheWholeLibrary() {
+        assertEquals(LIBRARY_CLASS_COUNT, closureOf("XmlFactories").size(), "XmlFactories closure size drifted");
     }
 
     @Test
-    void xPathHardenerFootprint() {
-        assertEquals(XPATH_HARDENER, closureOf("XPathHardener"));
+    void saxParserHardenerFootprint() {
+        assertEquals(SAX_PARSER_HARDENER, closureOf("SAXParserHardener"));
     }
 
     @Test
@@ -154,57 +194,17 @@ void schemaHardenerFootprint() {
     }
 
     @Test
-    void onlyXmlFactoriesPullsTheWholeLibrary() {
-        assertEquals(LIBRARY_CLASS_COUNT, closureOf("XmlFactories").size(), "XmlFactories closure size drifted");
-    }
-
-    /**
-     * Transitive class closure of {@code PKG + simpleName}, restricted to this library's own package and reported by simple name.
-     */
-    private static Set<String> closureOf(final String simpleName) {
-        final Clazz entry = clazzpath.getClazz(PKG + simpleName);
-        if (entry == null) {
-            throw new IllegalStateException("Not on the compiled classpath: " + PKG + simpleName);
-        }
-        final Set<String> names = new TreeSet<>();
-        names.add(strip(entry.getName()));
-        for (final Clazz dependency : entry.getTransitiveDependencies()) {
-            if (dependency.getName().startsWith(PKG)) {
-                names.add(strip(dependency.getName()));
-            }
-        }
-        return names;
-    }
-
-    /**
-     * Sums the uncompressed {@code .class} file sizes of a closure's classes, as they would land in a shaded jar.
-     */
-    private static long bytesOf(final Set<String> simpleNames) {
-        long total = 0;
-        for (final String name : simpleNames) {
-            try {
-                total += Files.size(classesDir.resolve("org/apache/commons/xml/" + name + ".class"));
-            } catch (final IOException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return total;
-    }
-
-    private static String strip(final String qualifiedName) {
-        return qualifiedName.substring(PKG.length());
+    void staxHardenerFootprint() {
+        assertEquals(STAX_HARDENER, closureOf("StaxHardener"));
     }
 
-    private static Set<String> set(final String... names) {
-        return new TreeSet<>(Arrays.asList(names));
+    @Test
+    void transformerHardenerFootprint() {
+        assertEquals(TRANSFORMER_HARDENER, closureOf("TransformerHardener"));
     }
 
-    /**
-     * {@link #SAX_PARSER_HARDENER} plus the extra names; used where an entry point's closure is the SAX path plus its own classes.
-     */
-    private static Set<String> saxParsersHardenerPlus(final String... more) {
-        final Set<String> union = new TreeSet<>(SAX_PARSER_HARDENER);
-        union.addAll(Arrays.asList(more));
-        return union;
+    @Test
+    void xPathHardenerFootprint() {
+        assertEquals(XPATH_HARDENER, closureOf("XPathHardener"));
     }
 }
diff --git a/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java b/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
index 348aa80..360ff6c 100644
--- a/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
+++ b/src/test/java/org/apache/commons/xml/TemplatesHandlerTest.java
@@ -53,13 +53,6 @@ private static String compileAndTransform(final TemplatesHandler handler, final
         return sink.toString();
     }
 
-    @Test
-    void hardenedTemplatesHandlerDoesNotLeakInclude() throws Exception {
-        final TemplatesHandler handler = SaxSurfaceTestSupport.hardenedFactory().newTemplatesHandler();
-        assertFalse(compileAndTransform(handler, "with-include.xsl").contains(AttackTestSupport.LEAKED_MARKER),
-                "xsl:include through TemplatesHandler leaked");
-    }
-
     @Test
     void hardenedTemplatesHandlerDoesNotLeakDocument() throws Exception {
         // The f004 product path: the Templates from getTemplates() must produce floored Transformers for runtime document().
@@ -68,6 +61,13 @@ void hardenedTemplatesHandlerDoesNotLeakDocument() throws Exception {
                 "document() through TemplatesHandler.getTemplates() leaked");
     }
 
+    @Test
+    void hardenedTemplatesHandlerDoesNotLeakInclude() throws Exception {
+        final TemplatesHandler handler = SaxSurfaceTestSupport.hardenedFactory().newTemplatesHandler();
+        assertFalse(compileAndTransform(handler, "with-include.xsl").contains(AttackTestSupport.LEAKED_MARKER),
+                "xsl:include through TemplatesHandler leaked");
+    }
+
     @Test
     void unconfiguredTemplatesHandlerLeaksDocument() throws Exception {
         final TemplatesHandler handler = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTemplatesHandler();
diff --git a/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java b/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
index 767b4f4..89060cd 100644
--- a/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
+++ b/src/test/java/org/apache/commons/xml/TransformerHandlerTest.java
@@ -48,6 +48,16 @@ private static String transformViaHandler(final TransformerHandler handler) thro
         return sink.toString();
     }
 
+    @Test
+    void hardenedGetTransformerDoesNotLeakDocument() throws Exception {
+        // The f004 bypass: pull the inner Transformer out of the handler and transform directly; the floor must ride along.
+        final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
+        final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
+        final StringWriter sink = new StringWriter();
+        handler.getTransformer().transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink));
+        assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() through getTransformer() leaked");
+    }
+
     @Test
     void hardenedTransformerHandlerDoesNotLeakDocument() throws Exception {
         final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
@@ -65,16 +75,6 @@ void hardenedTransformerHandlerFromTemplatesDoesNotLeakDocument() throws Excepti
                 "document() through TransformerHandler(Templates) leaked");
     }
 
-    @Test
-    void hardenedGetTransformerDoesNotLeakDocument() throws Exception {
-        // The f004 bypass: pull the inner Transformer out of the handler and transform directly; the floor must ride along.
-        final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
-        final TransformerHandler handler = factory.newTransformerHandler(AttackTestSupport.resourceSource("with-document.xsl"));
-        final StringWriter sink = new StringWriter();
-        handler.getTransformer().transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(sink));
-        assertFalse(sink.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() through getTransformer() leaked");
-    }
-
     @Test
     void unconfiguredTransformerHandlerLeaksDocument() throws Exception {
         final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
diff --git a/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java b/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
index 138eb00..7d9d01e 100644
--- a/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
+++ b/src/test/java/org/apache/commons/xml/UnsupportedXmlImplementationTest.java
@@ -43,28 +43,28 @@ class UnsupportedXmlImplementationTest {
     public static final class FakeDocumentBuilderFactory extends DocumentBuilderFactory {
 
         @Override
-        public DocumentBuilder newDocumentBuilder() {
-            throw new UnsupportedOperationException();
+        public Object getAttribute(final String name) {
+            return null;
         }
 
         @Override
-        public void setAttribute(final String name, final Object value) {
-            // no-op
+        public boolean getFeature(final String name) {
+            return false;
         }
 
         @Override
-        public Object getAttribute(final String name) {
-            return null;
+        public DocumentBuilder newDocumentBuilder() {
+            throw new UnsupportedOperationException();
         }
 
         @Override
-        public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
-            throw new ParserConfigurationException("feature not recognized: " + name);
+        public void setAttribute(final String name, final Object value) {
+            // no-op
         }
 
         @Override
-        public boolean getFeature(final String name) {
-            return false;
+        public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
+            throw new ParserConfigurationException("feature not recognized: " + name);
         }
     }
 
@@ -73,6 +73,11 @@ public boolean getFeature(final String name) {
      */
     public static final class FakeSAXParserFactory extends SAXParserFactory {
 
+        @Override
+        public boolean getFeature(final String name) throws SAXNotSupportedException {
+            throw new SAXNotSupportedException("feature not recognized: " + name);
+        }
+
         @Override
         public SAXParser newSAXParser() {
             throw new UnsupportedOperationException();
@@ -82,11 +87,6 @@ public SAXParser newSAXParser() {
         public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException {
             throw new SAXNotRecognizedException("feature not recognized: " + name);
         }
-
-        @Override
-        public boolean getFeature(final String name) throws SAXNotSupportedException {
-            throw new SAXNotSupportedException("feature not recognized: " + name);
-        }
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/xml/XIncludeTest.java b/src/test/java/org/apache/commons/xml/XIncludeTest.java
index 84a3933..0d4d4a0 100644
--- a/src/test/java/org/apache/commons/xml/XIncludeTest.java
+++ b/src/test/java/org/apache/commons/xml/XIncludeTest.java
@@ -53,15 +53,6 @@
  */
 class XIncludeTest {
 
-    /** Absolute URL of the XML fixture pulled in by {@code parse="xml"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
-    private static final String REFERENCED_XML = resourceUrl("referenced.xml").toString();
-
-    /** Absolute URL of the text fixture pulled in by {@code parse="text"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
-    private static final String REFERENCED_TEXT = resourceUrl("referenced.txt").toString();
-
-    /** Content the allow-list resolver returns for an allowed include; its presence proves the caller's resolver was consulted. */
-    private static final String RESOLVED_MARKER = "XINCLUDE-RESOLVED-905bbbce-16ee-4a0c-b165-d1f8c663934c";
-
     /**
      * Allow-lists the two fixture URLs, returning the appropriate in-memory content for each: {@link #RESOLVED_MARKER}
      * wrapped as XML for {@link #REFERENCED_XML}, and as plain text for {@link #REFERENCED_TEXT}. Anything else returns
@@ -85,17 +76,18 @@ public InputSource resolveEntity(final String publicId, final String systemId) {
         }
     }
 
+    /** Absolute URL of the XML fixture pulled in by {@code parse="xml"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
+    private static final String REFERENCED_XML = resourceUrl("referenced.xml").toString();
+
+    /** Absolute URL of the text fixture pulled in by {@code parse="text"} includes; carries {@link AttackTestSupport#LEAKED_MARKER}. */
+    private static final String REFERENCED_TEXT = resourceUrl("referenced.txt").toString();
+
+    /** Content the allow-list resolver returns for an allowed include; its presence proves the caller's resolver was consulted. */
+    private static final String RESOLVED_MARKER = "XINCLUDE-RESOLVED-905bbbce-16ee-4a0c-b165-d1f8c663934c";
+
     /** Resolver that resolves nothing, so the hardening's ignore-all floor must empty every lookup and never leak. */
     private static final EntityResolver NO_OP_RESOLVER = (publicId, systemId) -> null;
 
-    /** XML wrapper for xi:include in the given {@code parse} mode referencing {@code href}. */
-    private static String xiIncludeXml(final String href, final String parseMode) {
-        return "<?xml version=\"1.0\"?>\n"
-                + "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
-                + "  <xi:include href=\"" + href + "\" parse=\"" + parseMode + "\"/>\n"
-                + "</root>";
-    }
-
     /**
      * Enables XInclude on the factory under test, skipping the test when the platform refuses.
      *
@@ -122,60 +114,68 @@ private static void assumeXIncludeAware(final SAXParserFactory factory) {
         }
     }
 
+    /** XML wrapper for xi:include in the given {@code parse} mode referencing {@code href}. */
+    private static String xiIncludeXml(final String href, final String parseMode) {
+        return "<?xml version=\"1.0\"?>\n"
+                + "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n"
+                + "  <xi:include href=\"" + href + "\" parse=\"" + parseMode + "\"/>\n"
+                + "</root>";
+    }
+
     //region Baseline: unhardened JAXP resolves the include
 
     @Test
     @Tag("dom")
-    void baselineDomLeaksParseXml() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+    void baselineDomLeaksParseText() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
 
         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final Document doc = factory.newDocumentBuilder().parse(input);
         final String text = doc.getDocumentElement().getTextContent();
-        assertEquals(LEAKED_MARKER, text.trim(),
-                "Baseline DOM parse=xml should leak marker; got: " + text);
+        assertTrue(text != null && text.contains(LEAKED_MARKER),
+                "Baseline DOM parse=text should leak marker; got: " + text);
     }
 
     @Test
     @Tag("dom")
-    void baselineDomLeaksParseText() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+    void baselineDomLeaksParseXml() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final Document doc = factory.newDocumentBuilder().parse(input);
         final String text = doc.getDocumentElement().getTextContent();
-        assertTrue(text != null && text.contains(LEAKED_MARKER),
-                "Baseline DOM parse=text should leak marker; got: " + text);
+        assertEquals(LEAKED_MARKER, text.trim(),
+                "Baseline DOM parse=xml should leak marker; got: " + text);
     }
 
     @Test
     @Tag("sax")
-    void baselineSaxLeaksParseXml() throws Exception {
-        final String input = xiIncludeXml(REFERENCED_XML, "xml");
+    void baselineSaxLeaksParseText() throws Exception {
+        final String input = xiIncludeXml(REFERENCED_TEXT, "text");
 
         final SAXParserFactory factory = SAXParserFactory.newInstance();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
-        assertEquals(LEAKED_MARKER, captured.trim(),
-                "Baseline SAX parse=xml should leak marker; got: " + captured);
+        assertTrue(captured.contains(LEAKED_MARKER),
+                "Baseline SAX parse=text should leak marker; got: " + captured);
     }
 
     @Test
     @Tag("sax")
-    void baselineSaxLeaksParseText() throws Exception {
-        final String input = xiIncludeXml(REFERENCED_TEXT, "text");
+    void baselineSaxLeaksParseXml() throws Exception {
+        final String input = xiIncludeXml(REFERENCED_XML, "xml");
 
         final SAXParserFactory factory = SAXParserFactory.newInstance();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
-        assertTrue(captured.contains(LEAKED_MARKER),
-                "Baseline SAX parse=text should leak marker; got: " + captured);
+        assertEquals(LEAKED_MARKER, captured.trim(),
+                "Baseline SAX parse=xml should leak marker; got: " + captured);
     }
 
     //endregion
@@ -184,57 +184,59 @@ void baselineSaxLeaksParseText() throws Exception {
 
     @Test
     @Tag("dom")
-    void hardenedDomBlocksParseXml() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+    void hardenedDomBlocksParseText() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
 
         final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        assertThrows(SAXException.class, () -> {
-            final DocumentBuilder builder = factory.newDocumentBuilder();
-            builder.parse(input);
-        }, "Hardened DOM parse=xml should throw");
+        final Document doc = factory.newDocumentBuilder().parse(input);
+        final String text = doc.getDocumentElement().getTextContent();
+        assertFalse(text.contains(LEAKED_MARKER),
+                "Hardened DOM parse=text must resolve the include to empty, not leak; got: " + text);
     }
 
     @Test
     @Tag("dom")
-    void hardenedDomBlocksParseText() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+    void hardenedDomBlocksParseXml() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
         final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        final Document doc = factory.newDocumentBuilder().parse(input);
-        final String text = doc.getDocumentElement().getTextContent();
-        assertFalse(text.contains(LEAKED_MARKER),
-                "Hardened DOM parse=text must resolve the include to empty, not leak; got: " + text);
+        assertThrows(SAXException.class, () -> {
+            final DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.parse(input);
+        }, "Hardened DOM parse=xml should throw");
     }
 
     @Test
-    @Tag("sax")
-    void hardenedSaxBlocksParseXml() throws Exception {
+    @Tag("dom")
+    void hardenedDomNullResolverDoesNotLeak() throws Exception {
         final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
-        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        assertThrows(SAXException.class, () -> {
-            final XMLReader reader = factory.newSAXParser().getXMLReader();
-            reader.parse(input);
-        }, "Hardened SAX parse=xml should throw");
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+        builder.setEntityResolver(NO_OP_RESOLVER);
+        assertThrows(SAXException.class, () -> builder.parse(input),
+                "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
     }
 
     @Test
-    @Tag("sax")
-    void hardenedSaxBlocksParseText() throws Exception {
-        final String input = xiIncludeXml(REFERENCED_TEXT, "text");
+    @Tag("dom")
+    void hardenedDomWithAllowListResolvesParseText() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
 
-        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
+        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
-        assertFalse(captured.contains(LEAKED_MARKER),
-                "Hardened SAX parse=text must resolve the include to empty, not leak; got: " + captured);
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+        builder.setEntityResolver(new AllowListResolver());
+        final Document doc = builder.parse(input);
+        assertEquals(RESOLVED_MARKER, doc.getDocumentElement().getTextContent().trim(),
+                "DOM parse=text with allow-list should resolve to the resolver's content");
     }
 
     //endregion
@@ -257,47 +259,44 @@ void hardenedDomWithAllowListResolvesParseXml() throws Exception {
     }
 
     @Test
-    @Tag("dom")
-    void hardenedDomWithAllowListResolvesParseText() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_TEXT, "text"));
+    @Tag("sax")
+    void hardenedSaxBlocksParseText() throws Exception {
+        final String input = xiIncludeXml(REFERENCED_TEXT, "text");
 
-        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        final DocumentBuilder builder = factory.newDocumentBuilder();
-        builder.setEntityResolver(new AllowListResolver());
-        final Document doc = builder.parse(input);
-        assertEquals(RESOLVED_MARKER, doc.getDocumentElement().getTextContent().trim(),
-                "DOM parse=text with allow-list should resolve to the resolver's content");
+        final String captured = captureCharacters(factory.newSAXParser().getXMLReader(), input);
+        assertFalse(captured.contains(LEAKED_MARKER),
+                "Hardened SAX parse=text must resolve the include to empty, not leak; got: " + captured);
     }
 
     @Test
-    @Tag("dom")
-    void hardenedDomNullResolverDoesNotLeak() throws Exception {
+    @Tag("sax")
+    void hardenedSaxBlocksParseXml() throws Exception {
         final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
-        final DocumentBuilderFactory factory = XmlFactories.newDocumentBuilderFactory();
+        final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
-        final DocumentBuilder builder = factory.newDocumentBuilder();
-        builder.setEntityResolver(NO_OP_RESOLVER);
-        assertThrows(SAXException.class, () -> builder.parse(input),
-                "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
+        assertThrows(SAXException.class, () -> {
+            final XMLReader reader = factory.newSAXParser().getXMLReader();
+            reader.parse(input);
+        }, "Hardened SAX parse=xml should throw");
     }
 
     @Test
     @Tag("sax")
-    void hardenedSaxWithAllowListResolvesParseXml() throws Exception {
-        final String input = xiIncludeXml(REFERENCED_XML, "xml");
+    void hardenedSaxNullResolverDoesNotLeak() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
         final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final XMLReader reader = factory.newSAXParser().getXMLReader();
-        reader.setEntityResolver(new AllowListResolver());
-        final String captured = captureCharacters(reader, input);
-        assertEquals(RESOLVED_MARKER, captured.trim(),
-                "SAX parse=xml with allow-list should resolve to the resolver's content");
+        reader.setEntityResolver(NO_OP_RESOLVER);
+        assertThrows(SAXException.class, () -> reader.parse(input),
+                "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
     }
 
     @Test
@@ -317,16 +316,17 @@ void hardenedSaxWithAllowListResolvesParseText() throws Exception {
 
     @Test
     @Tag("sax")
-    void hardenedSaxNullResolverDoesNotLeak() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+    void hardenedSaxWithAllowListResolvesParseXml() throws Exception {
+        final String input = xiIncludeXml(REFERENCED_XML, "xml");
 
         final SAXParserFactory factory = XmlFactories.newSAXParserFactory();
         factory.setNamespaceAware(true);
         assumeXIncludeAware(factory);
         final XMLReader reader = factory.newSAXParser().getXMLReader();
-        reader.setEntityResolver(NO_OP_RESOLVER);
-        assertThrows(SAXException.class, () -> reader.parse(input),
-                "a resolver that returns null must not leak: the ignore-all floor blocks the real href");
+        reader.setEntityResolver(new AllowListResolver());
+        final String captured = captureCharacters(reader, input);
+        assertEquals(RESOLVED_MARKER, captured.trim(),
+                "SAX parse=xml with allow-list should resolve to the resolver's content");
     }
 
     //endregion
@@ -335,16 +335,17 @@ void hardenedSaxNullResolverDoesNotLeak() throws Exception {
 
     @Test
     @Tag("sax")
-    void hardenReaderBlocksParseXml() throws Exception {
-        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
+    void hardenReaderAllowListResolvesParseXml() throws Exception {
+        final String input = xiIncludeXml(REFERENCED_XML, "xml");
 
-        // Reader from an unhardened factory that already has XInclude enabled
         final SAXParserFactory unhardenedFactory = SAXParserFactory.newInstance();
         unhardenedFactory.setNamespaceAware(true);
         assumeXIncludeAware(unhardenedFactory);
         final XMLReader reader = SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
-        assertThrows(SAXException.class, () -> reader.parse(input),
-                "hardenReader should block XInclude parse=xml on reader with XInclude already enabled");
+        reader.setEntityResolver(new AllowListResolver());
+        final String captured = captureCharacters(reader, input);
+        assertEquals(RESOLVED_MARKER, captured.trim(),
+                "hardenReader + allow-list should resolve to the resolver's content on a reader with XInclude already enabled");
     }
 
     @Test
@@ -363,17 +364,16 @@ void hardenReaderBlocksParseText() throws Exception {
 
     @Test
     @Tag("sax")
-    void hardenReaderAllowListResolvesParseXml() throws Exception {
-        final String input = xiIncludeXml(REFERENCED_XML, "xml");
+    void hardenReaderBlocksParseXml() throws Exception {
+        final InputSource input = inputSource(xiIncludeXml(REFERENCED_XML, "xml"));
 
+        // Reader from an unhardened factory that already has XInclude enabled
         final SAXParserFactory unhardenedFactory = SAXParserFactory.newInstance();
         unhardenedFactory.setNamespaceAware(true);
         assumeXIncludeAware(unhardenedFactory);
         final XMLReader reader = SAXParserHardener.hardenReader(unhardenedFactory.newSAXParser().getXMLReader());
-        reader.setEntityResolver(new AllowListResolver());
-        final String captured = captureCharacters(reader, input);
-        assertEquals(RESOLVED_MARKER, captured.trim(),
-                "hardenReader + allow-list should resolve to the resolver's content on a reader with XInclude already enabled");
+        assertThrows(SAXException.class, () -> reader.parse(input),
+                "hardenReader should block XInclude parse=xml on reader with XInclude already enabled");
     }
 
     //endregion
diff --git a/src/test/java/org/apache/commons/xml/XMLFilterTest.java b/src/test/java/org/apache/commons/xml/XMLFilterTest.java
index 5b91a2d..0a33878 100644
--- a/src/test/java/org/apache/commons/xml/XMLFilterTest.java
+++ b/src/test/java/org/apache/commons/xml/XMLFilterTest.java
@@ -63,12 +63,30 @@ private static String filterAndCapture(final XMLFilter filter, final String inpu
         return text.toString();
     }
 
+    /**
+     * On Android, hand the unconfigured filter a permissive parent so Xalan's {@code TrAXFilter} uses it instead of self-provisioning an Expat reader on which
+     * it enables {@code namespace-prefixes}, a feature Android's libexpat accepts but fails on mid-parse. The parent stays permissive (no floor), so the leak
+     * these controls assert still occurs.
+     */
+    private static void setPermissiveParentOnAndroid(final XMLFilter filter) {
+        if (AttackTestSupport.IS_ANDROID) {
+            filter.setParent(AttackTestSupport.permissiveReader());
+        }
+    }
+
     @Test
     void hardenedFilterDoesNotLeakDocument() throws Exception {
         final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
         assertFalse(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER), "document() through XMLFilter leaked");
     }
 
+    @Test
+    void hardenedFilterDoesNotLeakExternalEntity() throws Exception {
+        // The f003 vector: with no caller-set parent, the input must be parsed by a hardened reader, not a self-provisioned permissive one.
+        final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.streamSource(IDENTITY_XSLT));
+        assertFalse(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER), "external entity through XMLFilter leaked");
+    }
+
     @Test
     void hardenedFilterFromTemplatesDoesNotLeakDocument() throws Exception {
         final SAXTransformerFactory factory = SaxSurfaceTestSupport.hardenedFactory();
@@ -79,10 +97,12 @@ void hardenedFilterFromTemplatesDoesNotLeakDocument() throws Exception {
     }
 
     @Test
-    void hardenedFilterDoesNotLeakExternalEntity() throws Exception {
-        // The f003 vector: with no caller-set parent, the input must be parsed by a hardened reader, not a self-provisioned permissive one.
-        final XMLFilter filter = SaxSurfaceTestSupport.hardenedFactory().newXMLFilter(AttackTestSupport.streamSource(IDENTITY_XSLT));
-        assertFalse(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER), "external entity through XMLFilter leaked");
+    void unconfiguredFilterLeaksDocument() throws Exception {
+        final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
+        final XMLFilter filter = factory.newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
+        setPermissiveParentOnAndroid(filter);
+        assertTrue(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER),
+                "unconfigured XMLFilter should resolve document()");
     }
 
     @Test
@@ -96,24 +116,4 @@ void unconfiguredFilterLeaksExternalEntity() throws Exception {
         assertTrue(filterAndCapture(filter, entityPayload()).contains(AttackTestSupport.LEAKED_MARKER),
                 "unconfigured XMLFilter should resolve the input's external entity");
     }
-
-    @Test
-    void unconfiguredFilterLeaksDocument() throws Exception {
-        final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
-        final XMLFilter filter = factory.newXMLFilter(AttackTestSupport.resourceSource("with-document.xsl"));
-        setPermissiveParentOnAndroid(filter);
-        assertTrue(filterAndCapture(filter, "<root/>").contains(AttackTestSupport.LEAKED_MARKER),
-                "unconfigured XMLFilter should resolve document()");
-    }
-
-    /**
-     * On Android, hand the unconfigured filter a permissive parent so Xalan's {@code TrAXFilter} uses it instead of self-provisioning an Expat reader on which
-     * it enables {@code namespace-prefixes}, a feature Android's libexpat accepts but fails on mid-parse. The parent stays permissive (no floor), so the leak
-     * these controls assert still occurs.
-     */
-    private static void setPermissiveParentOnAndroid(final XMLFilter filter) {
-        if (AttackTestSupport.IS_ANDROID) {
-            filter.setParent(AttackTestSupport.permissiveReader());
-        }
-    }
 }
diff --git a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
index 2c18720..a16bc6a 100644
--- a/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
+++ b/src/test/java/org/apache/commons/xml/XPathInputSourceTest.java
@@ -57,13 +57,6 @@ void hardenedXPathEvaluateDoesNotLeak() throws Exception {
         assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the XPath result: " + result);
     }
 
-    @Test
-    void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
-        // Same declared-entity outcome as above on the compiled-expression entry point.
-        final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
-        assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
-    }
-
     @Test
     void hardenedXPathEvaluatesPlainDocument() throws Exception {
         // Positive control: the hardened pre-parse still evaluates an entity-free document end to end.
@@ -72,6 +65,13 @@ void hardenedXPathEvaluatesPlainDocument() throws Exception {
         assertEquals("plain text", result, "hardened XPath should evaluate a plain document");
     }
 
+    @Test
+    void hardenedXPathExpressionEvaluateDoesNotLeak() throws Exception {
+        // Same declared-entity outcome as above on the compiled-expression entry point.
+        final String result = XmlFactories.newXPathFactory().newXPath().compile(EXPRESSION).evaluate(AttackTestSupport.inputSource(entityPayload()));
+        assertFalse(result.contains(AttackTestSupport.LEAKED_MARKER), "external entity leaked into the compiled XPath result: " + result);
+    }
+
     @Test
     void unconfiguredXPathEvaluateLeaks() throws Exception {
         // Leak control: the unconfigured engine's internal parser resolves the entity, which is exactly what the wrapper exists to prevent.
diff --git a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
index e7d133a..8a016b9 100644
--- a/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
+++ b/src/test/java/org/apache/commons/xml/XmlFactoriesTest.java
@@ -49,12 +49,10 @@ class XmlFactoriesTest {
             "<?xml version=\"1.0\"?>\n<root><child>hello</child></root>\n";
 
     @Test
-    void newDocumentBuilderFactoryReturnsFreshInstance() {
-        final DocumentBuilderFactory a = XmlFactories.newDocumentBuilderFactory();
-        final DocumentBuilderFactory b = XmlFactories.newDocumentBuilderFactory();
-        assertNotNull(a);
-        assertNotNull(b);
-        assertNotSame(a, b);
+    void benignDocumentParses() throws Exception {
+        final Document doc = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(new InputSource(new StringReader(BENIGN_XML)));
+        assertNotNull(doc);
+        assertNotNull(doc.getDocumentElement());
     }
 
     @Test
@@ -72,10 +70,12 @@ void newDocumentBuilderFactoryEnablesSecureProcessing() throws Exception {
     }
 
     @Test
-    void benignDocumentParses() throws Exception {
-        final Document doc = XmlFactories.newDocumentBuilderFactory().newDocumentBuilder().parse(new InputSource(new StringReader(BENIGN_XML)));
-        assertNotNull(doc);
-        assertNotNull(doc.getDocumentElement());
+    void newDocumentBuilderFactoryReturnsFreshInstance() {
+        final DocumentBuilderFactory a = XmlFactories.newDocumentBuilderFactory();
+        final DocumentBuilderFactory b = XmlFactories.newDocumentBuilderFactory();
+        assertNotNull(a);
+        assertNotNull(b);
+        assertNotSame(a, b);
     }
 
     @Test
@@ -88,12 +88,11 @@ void newSAXParserFactoryReturnsFreshInstance() {
     }
 
     @Test
-    void newXMLInputFactoryReturnsFreshInstance() {
-        final XMLInputFactory a = XmlFactories.newXMLInputFactory();
-        final XMLInputFactory b = XmlFactories.newXMLInputFactory();
+    void newSchemaFactoryReturnsFreshInstance() throws Exception {
+        final SchemaFactory a = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        final SchemaFactory b = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
         assertNotSame(a, b);
-        assertEquals(Boolean.TRUE, a.getProperty(XMLInputFactory.SUPPORT_DTD));
-        assertEquals(Boolean.FALSE, a.getProperty(XMLInputFactory.IS_VALIDATING));
+        assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
     }
 
     @Test
@@ -104,17 +103,18 @@ void newTransformerFactoryReturnsFreshInstance() {
     }
 
     @Test
-    void newXPathFactoryReturnsFreshInstance() throws Exception {
-        final XPathFactory a = XmlFactories.newXPathFactory();
-        final XPathFactory b = XmlFactories.newXPathFactory();
+    void newXMLInputFactoryReturnsFreshInstance() {
+        final XMLInputFactory a = XmlFactories.newXMLInputFactory();
+        final XMLInputFactory b = XmlFactories.newXMLInputFactory();
         assertNotSame(a, b);
-        assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
+        assertEquals(Boolean.TRUE, a.getProperty(XMLInputFactory.SUPPORT_DTD));
+        assertEquals(Boolean.FALSE, a.getProperty(XMLInputFactory.IS_VALIDATING));
     }
 
     @Test
-    void newSchemaFactoryReturnsFreshInstance() throws Exception {
-        final SchemaFactory a = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-        final SchemaFactory b = XmlFactories.newSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+    void newXPathFactoryReturnsFreshInstance() throws Exception {
+        final XPathFactory a = XmlFactories.newXPathFactory();
+        final XPathFactory b = XmlFactories.newXPathFactory();
         assertNotSame(a, b);
         assertTrue(a.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
     }