You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2021/10/10 11:26:35 UTC

[maven] 01/01: [MNG-7182] Use the MX xpp parser instead of a STAX transformation

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

slachiewicz pushed a commit to branch MNG-7182
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 0775a4343181d34f316ac371816d3e97215c497a
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Jul 8 07:54:01 2021 +0200

    [MNG-7182] Use the MX xpp parser instead of a STAX transformation
    
    Closes #486
---
 .../aether/ConsumerModelSourceTransformer.java     |  97 +---
 .../DefaultRepositorySystemSessionFactory.java     |  30 +-
 .../building/AbstractModelSourceTransformer.java   | 205 --------
 .../building/BuildModelSourceTransformer.java      |  53 +-
 .../building/DefaultBuildPomXMLFilterFactory.java  |   6 +-
 .../building/DefaultModelSourceTransformer.java    |  10 +-
 .../model/building/ModelSourceTransformer.java     |   8 +-
 .../apache/maven/model/io/DefaultModelReader.java  | 105 ++--
 .../DefaultInheritanceAssemblerTest.java           |  15 +-
 maven-model-transform/pom.xml                      |   4 +
 .../model/transform/AbstractEventXMLFilter.java    | 285 -----------
 .../model/transform/BuildToRawPomXMLFilter.java    |  58 ---
 .../transform/BuildToRawPomXMLFilterFactory.java   |  64 +--
 .../transform/BuildToRawPomXMLFilterListener.java  |  42 --
 .../maven/model/transform/CiFriendlyXMLFilter.java |  70 +--
 .../maven/model/transform/DependencyKey.java       |  87 ----
 .../maven/model/transform/FastForwardFilter.java   |  96 ++--
 .../maven/model/transform/ModulesXMLFilter.java    |  82 +--
 .../maven/model/transform/ParentXMLFilter.java     | 226 ++++-----
 .../model/transform/RawToConsumerPomXMLFilter.java |  62 ---
 .../RawToConsumerPomXMLFilterFactory.java          |  22 +-
 .../transform/ReactorDependencyXMLFilter.java      | 186 +++----
 .../model/transform/RelativePathXMLFilter.java     |  99 ++--
 .../model/transform/pull/BufferingParser.java      | 563 +++++++++++++++++++++
 .../maven/model/transform/pull/XmlUtils.java       | 132 +++++
 .../model/transform/sax/AbstractSAXFilter.java     | 143 ------
 .../model/transform/sax/CommentRenormalizer.java   | 108 ----
 .../maven/model/transform/sax/Factories.java       |  79 ---
 .../apache/maven/model/transform/sax/SAXEvent.java |  34 --
 .../maven/model/transform/sax/SAXEventFactory.java | 144 ------
 .../maven/model/transform/sax/SAXEventUtils.java   |  49 --
 .../model/transform/AbstractXMLFilterTests.java    | 180 +------
 .../model/transform/CiFriendlyXMLFilterTest.java   |  24 +-
 .../model/transform/ConsumerPomXMLFilterTest.java  |  29 +-
 .../model/transform/ModulesXMLFilterTest.java      |  14 +-
 .../maven/model/transform/ParentXMLFilterTest.java | 115 +++--
 .../transform/ReactorDependencyXMLFilterTest.java  |  32 +-
 .../model/transform/RelativePathXMLFilterTest.java |   9 +-
 .../model/transform/sax/ChainedFilterTest.java     | 148 ------
 .../transform/sax/CommentRenormalizerTest.java     |  64 ---
 .../model/transform/sax/SAXEventUtilsTest.java     |  41 --
 41 files changed, 1213 insertions(+), 2607 deletions(-)

diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java b/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
index b720acb..c1e1144 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/ConsumerModelSourceTransformer.java
@@ -23,91 +23,30 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.function.Consumer;
 
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamReader;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-
-import org.apache.maven.model.building.AbstractModelSourceTransformer;
 import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
 import org.apache.maven.model.building.TransformerContext;
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.apache.maven.xml.internal.DefaultConsumerPomXMLFilterFactory;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
-
-class ConsumerModelSourceTransformer extends AbstractModelSourceTransformer
+import org.apache.maven.model.transform.RawToConsumerPomXMLFilterFactory;
+import org.apache.maven.model.transform.pull.XmlUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.xml.XmlStreamReader;
+import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
+import org.codehaus.plexus.util.xml.pull.MXParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+class ConsumerModelSourceTransformer
 {
-    @Override
-    protected AbstractSAXFilter getSAXFilter( Path pomFile,
-                                              TransformerContext context,
-                                              Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws TransformerConfigurationException, SAXException, ParserConfigurationException
-    {
-        return new DefaultConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context,
-                                                                        lexicalHandlerConsumer, true ) ).get( pomFile );
-    }
-
-    /**
-     * This transformer will ensure that encoding and version are kept.
-     * However, it cannot prevent:
-     * <ul>
-     *   <li>attributes will be on one line</li>
-     *   <li>Unnecessary whitespace before the rootelement will be removed</li>
-     * </ul>
-     */
-    @Override
-    protected TransformerHandler getTransformerHandler( Path pomFile )
-        throws IOException, org.apache.maven.model.building.TransformerException
+    public InputStream transform( Path pomFile, TransformerContext context )
+            throws IOException, XmlPullParserException
     {
-        final TransformerHandler transformerHandler;
-
-        final SAXTransformerFactory transformerFactory = getTransformerFactory();
-
-        // Keep same encoding+version
-        try ( InputStream input = Files.newInputStream( pomFile ) )
-        {
-            XMLStreamReader streamReader =
-                XMLInputFactory.newFactory().createXMLStreamReader( input );
-
-            transformerHandler = transformerFactory.newTransformerHandler();
-
-            final String encoding = streamReader.getCharacterEncodingScheme();
-            final String version = streamReader.getVersion();
-
-            Transformer transformer = transformerHandler.getTransformer();
-            transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
-            if ( encoding == null && version == null )
-            {
-                transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
-            }
-            else
-            {
-                transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
+        XmlStreamReader reader = ReaderFactory.newXmlReader( Files.newInputStream( pomFile ) );
+        XmlPullParser parser = new MXParser( EntityReplacementMap.defaultEntityReplacementMap );
+        parser.setInput( reader );
+        parser = new RawToConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context, true ) )
+                .get( parser, pomFile );
 
-                if ( encoding != null )
-                {
-                    transformer.setOutputProperty( OutputKeys.ENCODING, encoding );
-                }
-                if ( version != null )
-                {
-                    transformer.setOutputProperty( OutputKeys.VERSION, version );
-                }
-            }
-        }
-        catch ( XMLStreamException | TransformerConfigurationException e )
-        {
-            throw new org.apache.maven.model.building.TransformerException(
-                               "Failed to detect XML encoding and version", e );
-        }
-        return transformerHandler;
+        return XmlUtils.writeDocument( reader, parser );
     }
 
 }
diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index c0ab9da..a642bec 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -19,6 +19,19 @@ package org.apache.maven.internal.aether;
  * under the License.
  */
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
 import org.apache.maven.RepositoryUtils;
 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
 import org.apache.maven.bridge.MavenRepositorySystem;
@@ -26,7 +39,6 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.feature.Features;
 import org.apache.maven.model.building.TransformerContext;
-import org.apache.maven.model.building.TransformerException;
 import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
 import org.apache.maven.settings.Mirror;
 import org.apache.maven.settings.Proxy;
@@ -37,6 +49,7 @@ import org.apache.maven.settings.crypto.SettingsDecrypter;
 import org.apache.maven.settings.crypto.SettingsDecryptionResult;
 import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 import org.eclipse.aether.ConfigurationProperties;
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.RepositorySystem;
@@ -59,19 +72,6 @@ import org.eclipse.sisu.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Properties;
-
 /**
  * @since 3.3.0
  */
@@ -310,7 +310,7 @@ public class DefaultRepositorySystemSessionFactory
                     {
                         return new ConsumerModelSourceTransformer().transform( pomFile.toPath(), context );
                     }
-                    catch ( TransformerException e )
+                    catch ( XmlPullParserException e )
                     {
                         throw new TransformException( e );
                     }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java
index dba9294..3ea1dbf 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/AbstractModelSourceTransformer.java
@@ -19,35 +19,6 @@ package org.apache.maven.model.building;
  * under the License.
  */
 
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.sax.SAXResult;
-import javax.xml.transform.sax.SAXSource;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.apache.maven.model.transform.sax.CommentRenormalizer;
-import org.apache.maven.model.transform.sax.Factories;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.ext.LexicalHandler;
-
 /**
  * Offers a transformation implementation based on PipelineStreams.
  * Subclasses are responsible for providing the right SAXFilter.
@@ -58,181 +29,5 @@ import org.xml.sax.ext.LexicalHandler;
 public abstract class AbstractModelSourceTransformer
     implements ModelSourceTransformer
 {
-    private static final AtomicInteger TRANSFORM_THREAD_COUNTER = new AtomicInteger();
-
-    private final TransformerFactory transformerFactory = Factories.newTransformerFactory();
-
-    protected abstract AbstractSAXFilter getSAXFilter( Path pomFile,
-                                                       TransformerContext context,
-                                                       Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws TransformerConfigurationException, SAXException, ParserConfigurationException;
-
-    protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
-    {
-        return outputStream;
-    }
-
-    public SAXTransformerFactory getTransformerFactory()
-    {
-        return ( SAXTransformerFactory ) transformerFactory;
-    }
-
-    protected TransformerHandler getTransformerHandler( Path pomFile )
-        throws IOException, org.apache.maven.model.building.TransformerException
-    {
-        return null;
-    }
-
-    @Override
-    public final InputStream transform( Path pomFile, TransformerContext context )
-        throws IOException, org.apache.maven.model.building.TransformerException
-    {
-        final TransformerHandler transformerHandler = getTransformerHandler( pomFile );
-
-        final PipedOutputStream pout = new PipedOutputStream();
-        OutputStream out = filterOutputStream( pout, pomFile );
-
-        final javax.xml.transform.Result result;
-        final Consumer<LexicalHandler> lexConsumer;
-        if ( transformerHandler == null )
-        {
-            result = new StreamResult( out );
-            lexConsumer = null;
-        }
-        else
-        {
-            result = new SAXResult( transformerHandler );
-            lexConsumer = l -> ( (SAXResult) result ).setLexicalHandler( new CommentRenormalizer( l ) );
-            transformerHandler.setResult( new StreamResult( out ) );
-        }
-
-        final AbstractSAXFilter filter;
-        try
-        {
-            filter = getSAXFilter( pomFile, context, lexConsumer );
-            filter.setLexicalHandler( transformerHandler );
-            // By default errors are written to stderr.
-            // Hence set custom errorHandler to reduce noice
-            filter.setErrorHandler( new ErrorHandler()
-            {
-                @Override
-                public void warning( SAXParseException exception )
-                    throws SAXException
-                {
-                    throw exception;
-                }
-
-                @Override
-                public void fatalError( SAXParseException exception )
-                    throws SAXException
-                {
-                    throw exception;
-                }
-
-                @Override
-                public void error( SAXParseException exception )
-                    throws SAXException
-                {
-                    throw exception;
-                }
-            } );
-        }
-        catch ( TransformerConfigurationException | SAXException | ParserConfigurationException e )
-        {
-            throw new org.apache.maven.model.building.TransformerException( e );
-        }
-
-        final SAXSource transformSource =
-            new SAXSource( filter, new org.xml.sax.InputSource( Files.newInputStream( pomFile ) ) );
-
-        IOExceptionHandler eh = new IOExceptionHandler();
-
-        // Ensure pipedStreams are connected before the transformThread starts!!
-        final PipedInputStream pipedInputStream = new PipedInputStream( pout );
-
-        Thread transformThread = new Thread( () ->
-        {
-            try ( PipedOutputStream pos = pout )
-            {
-                transformerFactory.newTransformer().transform( transformSource, result );
-            }
-            catch ( TransformerException | IOException e )
-            {
-                eh.uncaughtException( Thread.currentThread(), e );
-            }
-        }, "TransformThread-" + TRANSFORM_THREAD_COUNTER.incrementAndGet() );
-        transformThread.setUncaughtExceptionHandler( eh );
-        transformThread.setDaemon( true );
-        transformThread.start();
-
-        return new ThreadAwareInputStream( pipedInputStream, eh );
-    }
-
-    private static class IOExceptionHandler
-        implements Thread.UncaughtExceptionHandler, AutoCloseable
-    {
-        private volatile Throwable cause;
-
-        @Override
-        public void uncaughtException( Thread t, Throwable e )
-        {
-            try
-            {
-                throw e;
-            }
-            catch ( TransformerException | IOException | RuntimeException | Error allGood )
-            {
-                // all good
-                this.cause = e;
-            }
-            catch ( Throwable notGood )
-            {
-                throw new AssertionError( "Unexpected Exception", e );
-            }
-        }
-
-        @Override
-        public void close()
-            throws IOException
-        {
-            if ( cause != null )
-            {
-                try
-                {
-                    throw cause;
-                }
-                catch ( IOException | RuntimeException | Error e )
-                {
-                    throw e;
-                }
-                catch ( Throwable t )
-                {
-                    // Any checked exception
-                    throw new RuntimeException( "Failed to transform pom", t );
-                }
-            }
-        }
-    }
-
-    private class ThreadAwareInputStream
-        extends FilterInputStream
-    {
-        final IOExceptionHandler h;
-
-        protected ThreadAwareInputStream( InputStream in, IOExceptionHandler h )
-        {
-            super( in );
-            this.h = h;
-        }
 
-        @Override
-        public void close()
-            throws IOException
-        {
-            try ( IOExceptionHandler eh = h )
-            {
-                super.close();
-            }
-        }
-    }
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
index 699a009..9da284b 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/BuildModelSourceTransformer.java
@@ -19,24 +19,14 @@ package org.apache.maven.model.building;
  * under the License.
  */
 
-import java.io.FilterOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.nio.file.Path;
-import java.util.function.Consumer;
 
-import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
 
 import org.apache.maven.model.transform.BuildToRawPomXMLFilterFactory;
-import org.apache.maven.model.transform.BuildToRawPomXMLFilterListener;
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.eclipse.sisu.Nullable;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * ModelSourceTransformer for the build pom
@@ -46,44 +36,15 @@ import org.xml.sax.ext.LexicalHandler;
  */
 @Named
 @Singleton
-class BuildModelSourceTransformer extends AbstractModelSourceTransformer
+class BuildModelSourceTransformer implements ModelSourceTransformer
 {
-    @Inject
-    @Nullable
-    private BuildToRawPomXMLFilterListener xmlFilterListener;
-
-    protected AbstractSAXFilter getSAXFilter( Path pomFile,
-                                              TransformerContext context,
-                                              Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws TransformerConfigurationException, SAXException, ParserConfigurationException
+    @Override
+    public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
+            throws IOException, TransformerException
     {
         BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory =
-            new DefaultBuildPomXMLFilterFactory( context, lexicalHandlerConsumer, false );
-
-        return buildPomXMLFilterFactory.get( pomFile );
-    }
+                new DefaultBuildPomXMLFilterFactory( context, false );
 
-    @Override
-    protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
-    {
-        OutputStream out;
-        if ( xmlFilterListener != null )
-        {
-            out = new FilterOutputStream( outputStream )
-            {
-                @Override
-                public void write( byte[] b, int off, int len )
-                    throws IOException
-                {
-                    super.write( b, off, len );
-                    xmlFilterListener.write( pomFile, b, off, len );
-                }
-            };
-        }
-        else
-        {
-            out = outputStream;
-        }
-        return out;
+        return buildPomXMLFilterFactory.get( parser, pomFile );
     }
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
index d22eaac..e415461 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultBuildPomXMLFilterFactory.java
@@ -23,13 +23,11 @@ package org.apache.maven.model.building;
 import java.nio.file.Path;
 import java.util.Optional;
 import java.util.function.BiFunction;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
 import org.apache.maven.model.Model;
 import org.apache.maven.model.transform.BuildToRawPomXMLFilterFactory;
 import org.apache.maven.model.transform.RelativeProject;
-import org.xml.sax.ext.LexicalHandler;
 
 /**
  * A BuildPomXMLFilterFactory which is context aware
@@ -44,14 +42,12 @@ public class DefaultBuildPomXMLFilterFactory extends BuildToRawPomXMLFilterFacto
     /**
      *
      * @param context a set of data to extract values from as required for the build pom
-     * @param lexicalHandlerConsumer the lexical handler consumer
      * @param consume {@code true} if this factory is being used for creating the consumer pom, otherwise {@code false}
      */
     public DefaultBuildPomXMLFilterFactory( TransformerContext context,
-                                            Consumer<LexicalHandler> lexicalHandlerConsumer,
                                             boolean consume )
     {
-        super( lexicalHandlerConsumer, consume );
+        super( consume );
         this.context = context;
     }
 
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java
index 7c57f30..e6e4073 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelSourceTransformer.java
@@ -20,10 +20,10 @@ package org.apache.maven.model.building;
  */
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
 import java.nio.file.Path;
 
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+
 /**
  * Default ModelSourceTransformer, provides pomFile as inputStream and ignores the context
  *
@@ -34,10 +34,10 @@ public class DefaultModelSourceTransformer implements ModelSourceTransformer
 {
 
     @Override
-    public InputStream transform( Path pomFile, TransformerContext context )
-        throws IOException, TransformerException
+    public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
+            throws IOException, TransformerException
     {
-        return Files.newInputStream( pomFile );
+        return parser;
     }
 
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
index a2556ce..a2504ef 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelSourceTransformer.java
@@ -20,16 +20,18 @@ package org.apache.maven.model.building;
  */
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.file.Path;
 
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+
 /**
  * The ModelSourceTransformer is a way to transform the local pom while streaming the input.
  *
- * The {@link #transform(Path, TransformerContext)} method uses a Path on purpose, to ensure the
+ * The {@link #transform(XmlPullParser, Path, TransformerContext)} method uses a Path on purpose, to ensure the
  * local pom is the the original source.
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
 public interface ModelSourceTransformer
@@ -42,6 +44,6 @@ public interface ModelSourceTransformer
      * @throws IOException if an I/O error occurs
      * @throws TransformerException if the transformation fails
      */
-    InputStream transform( Path pomFile, TransformerContext context )
+    XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
         throws IOException, TransformerException;
 }
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java b/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
index eeafabe..3501df9 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/io/DefaultModelReader.java
@@ -20,10 +20,12 @@ package org.apache.maven.model.io;
  */
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
 import java.util.Map;
 import java.util.Objects;
 
@@ -35,11 +37,13 @@ import org.apache.maven.model.InputSource;
 import org.apache.maven.model.Model;
 import org.apache.maven.model.building.ModelSourceTransformer;
 import org.apache.maven.model.building.TransformerContext;
-import org.apache.maven.model.building.TransformerException;
 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 import org.apache.maven.model.io.xpp3.MavenXpp3ReaderEx;
 import org.codehaus.plexus.util.ReaderFactory;
 import org.codehaus.plexus.util.xml.XmlStreamReader;
+import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
+import org.codehaus.plexus.util.xml.pull.MXParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 /**
@@ -54,6 +58,10 @@ public class DefaultModelReader
 {
     private final ModelSourceTransformer transformer;
 
+    private Method readMethod;
+
+    private Method readMethodEx;
+
     @Inject
     public DefaultModelReader( ModelSourceTransformer transformer )
     {
@@ -66,28 +74,9 @@ public class DefaultModelReader
     {
         Objects.requireNonNull( input, "input cannot be null" );
 
-        TransformerContext context = getTransformerContext( options );
-
-        final InputStream is;
-        if ( context == null )
-        {
-            is = new FileInputStream( input );
-        }
-        else
-        {
-            try
-            {
-                is = transformer.transform( input.toPath(), context );
-            }
-            catch ( TransformerException e )
-            {
-                throw new IOException( "Failed to transform " + input,  e );
-            }
-        }
-
-        try ( InputStream in = is )
+        try ( XmlStreamReader in = ReaderFactory.newXmlReader( input ) )
         {
-            Model model = read( is, options );
+            Model model = read( in, input.toPath(), options );
 
             model.setPomFile( input );
 
@@ -103,7 +92,7 @@ public class DefaultModelReader
 
         try ( Reader in = input )
         {
-            return read( in, isStrict( options ), getSource( options ) );
+            return read( in, null, options );
         }
     }
 
@@ -115,7 +104,7 @@ public class DefaultModelReader
 
         try ( XmlStreamReader in = ReaderFactory.newXmlReader( input ) )
         {
-            return read( in, isStrict( options ), getSource( options ) );
+            return read( in, null, options );
         }
     }
 
@@ -137,24 +126,80 @@ public class DefaultModelReader
         return (TransformerContext) value;
     }
 
-    private Model read( Reader reader, boolean strict, InputSource source )
+    private Model read( Reader reader, Path pomFile, Map<String, ?> options )
         throws IOException
     {
         try
         {
-            if ( source != null )
+            XmlPullParser parser = new MXParser( EntityReplacementMap.defaultEntityReplacementMap );
+            parser.setInput( reader );
+
+            TransformerContext context = getTransformerContext( options );
+            XmlPullParser transformingParser = context != null
+                    ? transformer.transform( parser, pomFile, context ) : parser;
+
+            InputSource source = getSource( options );
+            boolean strict = isStrict( options );
+            try
             {
-                return new MavenXpp3ReaderEx().read( reader, strict, source );
+                if ( source != null )
+                {
+                    return readModelEx( transformingParser, source, strict );
+                }
+                else
+                {
+                    return readModel( transformingParser, strict );
+                }
             }
-            else
+            catch ( InvocationTargetException e )
             {
-                return new MavenXpp3Reader().read( reader, strict );
+                Throwable cause = e.getCause();
+                if ( cause instanceof Exception )
+                {
+                    throw ( Exception ) cause;
+                }
+                throw e;
             }
         }
         catch ( XmlPullParserException e )
         {
             throw new ModelParseException( e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e );
         }
+        catch ( IOException e )
+        {
+            throw e;
+        }
+        catch ( Exception e )
+        {
+            throw new IOException( "Unable to transform pom", e );
+        }
+    }
+
+    private Model readModel( XmlPullParser parser, boolean strict )
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+    {
+        if ( readMethod == null )
+        {
+            readMethod = MavenXpp3Reader.class.getDeclaredMethod( "read", XmlPullParser.class, boolean.class );
+            readMethod.setAccessible( true );
+        }
+        MavenXpp3Reader mr = new MavenXpp3Reader();
+        Object model = readMethod.invoke( mr, parser, strict );
+        return ( Model ) model;
+    }
+
+    private Model readModelEx( XmlPullParser parser, InputSource source, boolean strict )
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+    {
+        if ( readMethodEx == null )
+        {
+            readMethodEx = MavenXpp3ReaderEx.class.getDeclaredMethod( "read",
+                    XmlPullParser.class, boolean.class, InputSource.class );
+            readMethodEx.setAccessible( true );
+        }
+        MavenXpp3ReaderEx mr = new MavenXpp3ReaderEx();
+        Object model = readMethodEx.invoke( mr, parser, strict, source );
+        return ( Model ) model;
     }
 
 }
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
index 52400d8..b573ad1 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/inheritance/DefaultInheritanceAssemblerTest.java
@@ -22,22 +22,16 @@ package org.apache.maven.model.inheritance;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.function.Consumer;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
-
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
 
 import org.apache.maven.model.Model;
 import org.apache.maven.model.building.AbstractModelSourceTransformer;
 import org.apache.maven.model.building.SimpleProblemCollector;
 import org.apache.maven.model.building.TransformerContext;
+import org.apache.maven.model.building.TransformerException;
 import org.apache.maven.model.io.DefaultModelReader;
 import org.apache.maven.model.io.DefaultModelWriter;
 import org.apache.maven.model.io.ModelWriter;
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.xmlunit.matchers.CompareMatcher;
@@ -64,9 +58,8 @@ public class DefaultInheritanceAssemblerTest
         reader = new DefaultModelReader( new AbstractModelSourceTransformer()
         {
             @Override
-            protected AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context,
-                                                      Consumer<LexicalHandler> lexicalHandlerConsumer )
-                throws TransformerConfigurationException, SAXException, ParserConfigurationException
+            public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
+                    throws IOException, TransformerException
             {
                 return null;
             }
diff --git a/maven-model-transform/pom.xml b/maven-model-transform/pom.xml
index 2839d52..5024085 100644
--- a/maven-model-transform/pom.xml
+++ b/maven-model-transform/pom.xml
@@ -33,6 +33,10 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-params</artifactId>
       <scope>test</scope>
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/AbstractEventXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/AbstractEventXMLFilter.java
deleted file mode 100644
index 711845f..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/AbstractEventXMLFilter.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package org.apache.maven.model.transform;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Queue;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.apache.maven.model.transform.sax.SAXEvent;
-import org.apache.maven.model.transform.sax.SAXEventFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-
-/**
- * Builds up a list of SAXEvents, which will be executed with {@link #executeEvents()}
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-abstract class AbstractEventXMLFilter extends AbstractSAXFilter
-{
-    private Queue<SAXEvent> saxEvents = new ArrayDeque<>();
-
-    private SAXEventFactory eventFactory;
-
-    // characters BEFORE startElement must get state of startingElement
-    // this way removing based on state keeps correct formatting
-    private List<SAXEvent> charactersSegments = new ArrayList<>();
-
-    private boolean lockCharacters = false;
-
-    protected abstract boolean isParsing();
-
-    protected abstract String getState();
-
-    protected boolean acceptEvent( String state )
-    {
-        return true;
-    }
-
-    AbstractEventXMLFilter()
-    {
-        super();
-    }
-
-    AbstractEventXMLFilter( AbstractSAXFilter parent )
-    {
-        super( parent );
-    }
-
-    private SAXEventFactory getEventFactory()
-    {
-        if ( eventFactory == null )
-        {
-            eventFactory = SAXEventFactory.newInstance( getContentHandler(), getLexicalHandler() );
-        }
-        return eventFactory;
-    }
-
-    private void processEvent( final SAXEvent event )
-                    throws SAXException
-    {
-        if ( isParsing() )
-        {
-            final String eventState = getState();
-
-            if ( !lockCharacters )
-            {
-                charactersSegments.forEach( e ->
-                        saxEvents.add( () ->
-                        {
-                            if ( acceptEvent( eventState ) )
-                            {
-                                e.execute();
-                            }
-                        } ) );
-                charactersSegments.clear();
-            }
-
-            saxEvents.add( () ->
-            {
-                if ( acceptEvent( eventState ) )
-                {
-                    event.execute();
-                }
-            } );
-        }
-        else
-        {
-            event.execute();
-        }
-    }
-
-    /**
-     * Should be used to include extra events before a closing element.
-     * This is a lightweight solution to keep the correct indentation.
-     */
-    protected Includer include()
-    {
-        this.lockCharacters = true;
-
-        return () -> lockCharacters = false;
-    }
-
-    protected final void executeEvents() throws SAXException
-    {
-        final String eventState = getState();
-        charactersSegments.forEach( e ->
-                saxEvents.add( () ->
-                {
-                    if ( acceptEvent( eventState ) )
-                    {
-                        e.execute();
-                    }
-                } ) );
-        charactersSegments.clear();
-
-        // not with streams due to checked SAXException
-        while ( !saxEvents.isEmpty() )
-        {
-            saxEvents.poll().execute();
-        }
-    }
-
-    @Override
-    public void setDocumentLocator( Locator locator )
-    {
-        try
-        {
-            processEvent( getEventFactory().setDocumentLocator( locator ) );
-        }
-        catch ( SAXException e )
-        {
-            // noop, setDocumentLocator can never throw a SAXException
-        }
-    }
-
-    @Override
-    public void startDocument() throws SAXException
-    {
-        processEvent( getEventFactory().startDocument() );
-    }
-
-    @Override
-    public void endDocument() throws SAXException
-    {
-        processEvent( getEventFactory().endDocument() );
-    }
-
-    @Override
-    public void startPrefixMapping( String prefix, String uri ) throws SAXException
-    {
-        processEvent( getEventFactory().startPrefixMapping( prefix, uri ) );
-    }
-
-    @Override
-    public void endPrefixMapping( String prefix ) throws SAXException
-    {
-        processEvent( getEventFactory().endPrefixMapping( prefix ) );
-    }
-
-    @Override
-    public void startElement( String uri, String localName, String qName, Attributes atts ) throws SAXException
-    {
-        processEvent( getEventFactory().startElement( uri, localName, qName, atts ) );
-    }
-
-    @Override
-    public void endElement( String uri, String localName, String qName ) throws SAXException
-    {
-        processEvent( getEventFactory().endElement( uri, localName, qName ) );
-    }
-
-    @Override
-    public void characters( char[] ch, int start, int length ) throws SAXException
-    {
-        if ( lockCharacters )
-        {
-            processEvent( getEventFactory().characters( ch, start, length ) );
-        }
-        else if ( isParsing() )
-        {
-            this.charactersSegments.add( getEventFactory().characters( ch, start, length ) );
-        }
-        else
-        {
-            super.characters( ch, start, length );
-        }
-    }
-
-    @Override
-    public void ignorableWhitespace( char[] ch, int start, int length ) throws SAXException
-    {
-        processEvent( getEventFactory().ignorableWhitespace( ch, start, length ) );
-    }
-
-    @Override
-    public void processingInstruction( String target, String data ) throws SAXException
-    {
-        processEvent( getEventFactory().processingInstruction( target, data ) );
-    }
-
-    @Override
-    public void skippedEntity( String name ) throws SAXException
-    {
-        processEvent( getEventFactory().skippedEntity( name ) );
-    }
-
-    @Override
-    public void startDTD( String name, String publicId, String systemId ) throws SAXException
-    {
-        processEvent( getEventFactory().startCDATA() );
-    }
-
-    @Override
-    public void endDTD() throws SAXException
-    {
-        processEvent( getEventFactory().endDTD() );
-    }
-
-    @Override
-    public void startEntity( String name ) throws SAXException
-    {
-        processEvent( getEventFactory().startEntity( name ) );
-    }
-
-    @Override
-    public void endEntity( String name ) throws SAXException
-    {
-        processEvent( getEventFactory().endEntity( name ) );
-    }
-
-    @Override
-    public void startCDATA()
-        throws SAXException
-    {
-        processEvent( getEventFactory().startCDATA() );
-    }
-
-    @Override
-    public void endCDATA()
-        throws SAXException
-    {
-        processEvent( getEventFactory().endCDATA() );
-    }
-
-    @Override
-    public void comment( char[] ch, int start, int length )
-        throws SAXException
-    {
-        processEvent( getEventFactory().comment( ch, start, length ) );
-    }
-
-    /**
-     * AutoCloseable with a close method that doesn't throw an exception
-     *
-     * @author Robert Scholte
-     *
-     */
-    @FunctionalInterface
-    protected interface Includer extends AutoCloseable
-    {
-        void close();
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilter.java
deleted file mode 100644
index 8cc392f..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.apache.maven.model.transform;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.XMLReader;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-
-/**
- * Filter to adjust pom on filesystem before being processed for effective pom.
- * There should only be 1 BuildToRawPomXMLFilter, so the same is being used by both
- * org.apache.maven.model.building.DefaultModelBuilder.transformData(InputStream) and
- * org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory.newFileTransformerManager()
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class BuildToRawPomXMLFilter extends AbstractSAXFilter
-{
-    BuildToRawPomXMLFilter()
-    {
-        super();
-    }
-
-    BuildToRawPomXMLFilter( AbstractSAXFilter parent )
-    {
-        super( parent );
-    }
-
-    /**
-     * Don't allow overwriting parent
-     */
-    @Override
-    public final void setParent( XMLReader parent )
-    {
-        if ( getParent() == null )
-        {
-            super.setParent( parent );
-        }
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterFactory.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterFactory.java
index 3531e2b..3d1f1a2 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterFactory.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterFactory.java
@@ -22,17 +22,9 @@ package org.apache.maven.model.transform;
 import java.nio.file.Path;
 import java.util.Optional;
 import java.util.function.BiFunction;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.apache.maven.model.transform.sax.Factories;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.ext.LexicalHandler;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * Base implementation for providing the BuildToRawPomXML.
@@ -44,74 +36,42 @@ public class BuildToRawPomXMLFilterFactory
 {
     private final boolean consume;
 
-    private final Consumer<LexicalHandler> lexicalHandlerConsumer;
-
-    public BuildToRawPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer )
+    public BuildToRawPomXMLFilterFactory()
     {
-        this( lexicalHandlerConsumer, false );
+        this( false );
     }
 
-    public BuildToRawPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer, boolean consume )
+    public BuildToRawPomXMLFilterFactory( boolean consume )
     {
-        this.lexicalHandlerConsumer = lexicalHandlerConsumer;
         this.consume = consume;
     }
 
     /**
      *
      * @param projectFile will be used by ConsumerPomXMLFilter to get the right filter
-     * @throws SAXException
-     * @throws ParserConfigurationException
-     * @throws TransformerConfigurationException
      */
-    public final BuildToRawPomXMLFilter get( Path projectFile )
-        throws SAXException, ParserConfigurationException, TransformerConfigurationException
+    public final XmlPullParser get( XmlPullParser orgParser, Path projectFile )
+
     {
-        AbstractSAXFilter parent = new AbstractSAXFilter();
-        parent.setParent( getXMLReader() );
-        if ( lexicalHandlerConsumer != null )
-        {
-            lexicalHandlerConsumer.accept( parent );
-        }
+        XmlPullParser parser = orgParser;
 
         if ( getDependencyKeyToVersionMapper() != null )
         {
-            ReactorDependencyXMLFilter reactorDependencyXMLFilter =
-                new ReactorDependencyXMLFilter( getDependencyKeyToVersionMapper() );
-            reactorDependencyXMLFilter.setParent( parent );
-            parent.setLexicalHandler( reactorDependencyXMLFilter );
-            parent = reactorDependencyXMLFilter;
+            parser = new ReactorDependencyXMLFilter( parser, getDependencyKeyToVersionMapper() );
         }
 
         if ( getRelativePathMapper() != null )
         {
-            ParentXMLFilter parentFilter = new ParentXMLFilter( getRelativePathMapper() );
-            parentFilter.setProjectPath( projectFile.getParent() );
-            parentFilter.setParent( parent );
-            parent.setLexicalHandler( parentFilter );
-            parent = parentFilter;
+            parser = new ParentXMLFilter( parser, getRelativePathMapper(), projectFile.getParent() );
         }
 
-        CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( consume );
+        CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( parser, consume );
         getChangelist().ifPresent( ciFriendlyFilter::setChangelist  );
         getRevision().ifPresent( ciFriendlyFilter::setRevision );
         getSha1().ifPresent( ciFriendlyFilter::setSha1 );
+        parser = ciFriendlyFilter;
 
-        if ( ciFriendlyFilter.isSet() )
-        {
-            ciFriendlyFilter.setParent( parent );
-            parent.setLexicalHandler( ciFriendlyFilter );
-            parent = ciFriendlyFilter;
-        }
-
-        return new BuildToRawPomXMLFilter( parent );
-    }
-
-    private XMLReader getXMLReader() throws SAXException, ParserConfigurationException
-    {
-        XMLReader xmlReader = Factories.newXMLReader();
-        xmlReader.setFeature( "http://xml.org/sax/features/namespaces", true );
-        return xmlReader;
+        return parser;
     }
 
     /**
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterListener.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterListener.java
deleted file mode 100644
index ff9cfb6..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/BuildToRawPomXMLFilterListener.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.apache.maven.model.transform;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.nio.file.Path;
-
-/**
- * Listener can be used to capture the result of the transformation of build to raw POM.
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-@FunctionalInterface
-public interface BuildToRawPomXMLFilterListener
-{
-    /**
-     * Captures the result of the XML transformation
-     *
-     * @param pomFile the original to being transformed
-     * @param b the byte array
-     * @param off the offset
-     * @param len the length
-     */
-    void write( Path pomFile, byte[] b, int off, int len );
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/CiFriendlyXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/CiFriendlyXMLFilter.java
index 1a38a98..f981a8f 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/CiFriendlyXMLFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/CiFriendlyXMLFilter.java
@@ -19,37 +19,29 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
+import java.util.List;
 import java.util.function.Function;
 
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import org.apache.maven.model.transform.pull.NodeBufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * Resolves all ci-friendly properties occurrences between version-tags
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
 class CiFriendlyXMLFilter
-    extends AbstractSAXFilter
+    extends NodeBufferingParser
 {
     private final boolean replace;
 
     private Function<String, String> replaceChain = Function.identity();
 
-    private String characters;
-
-    private boolean parseVersion;
-
-    CiFriendlyXMLFilter( boolean replace )
+    CiFriendlyXMLFilter( XmlPullParser xmlPullParser, boolean replace )
     {
-        this.replace = replace;
-    }
-
-    CiFriendlyXMLFilter( AbstractSAXFilter parent, boolean replace )
-    {
-        super( parent );
+        super( xmlPullParser, "version" );
         this.replace = replace;
     }
 
@@ -80,52 +72,16 @@ class CiFriendlyXMLFilter
     }
 
     @Override
-    public void characters( char[] ch, int start, int length )
-        throws SAXException
+    protected void process( List<Event> buffer )
     {
-        if ( parseVersion )
+        for ( Event event : buffer )
         {
-            this.characters = nullSafeAppend( characters, new String( ch, start, length ) );
-        }
-        else
-        {
-            super.characters( ch, start, length );
-        }
-    }
-
-    @Override
-    public void startElement( String uri, String localName, String qName, Attributes atts )
-        throws SAXException
-    {
-        if ( !parseVersion && "version".equals( localName ) )
-        {
-            parseVersion = true;
-        }
-
-        super.startElement( uri, localName, qName, atts );
-    }
-
-    @Override
-    public void endElement( String uri, String localName, String qName )
-        throws SAXException
-    {
-        if ( parseVersion )
-        {
-            // assuming this has the best performance
-            if ( replace && characters != null && characters.contains( "${" ) )
-            {
-                char[] ch = replaceChain.apply( characters ).toCharArray();
-                super.characters( ch, 0, ch.length );
-            }
-            else
+            if ( event.event == TEXT && replace && event.text.contains( "${" ) )
             {
-                char[] ch = characters.toCharArray();
-                super.characters( ch, 0, ch.length );
+                event.text = replaceChain.apply( event.text );
             }
-            characters = null;
-            parseVersion = false;
+            pushEvent( event );
         }
-
-        super.endElement( uri, localName, qName );
     }
+
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/DependencyKey.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/DependencyKey.java
deleted file mode 100644
index 95c40e0..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/DependencyKey.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.apache.maven.model.transform;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.Objects;
-
-/**
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class DependencyKey
-{
-    private final String groupId;
-
-    private final String artifactId;
-
-    private final int hashCode;
-
-    public DependencyKey( String groupId, String artifactId )
-    {
-        this.groupId = groupId;
-        this.artifactId = artifactId;
-
-        this.hashCode = Objects.hash( artifactId, groupId );
-    }
-
-    public String getGroupId()
-    {
-        return groupId;
-    }
-
-    public String getArtifactId()
-    {
-        return artifactId;
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return hashCode;
-    }
-
-    @Override
-    public boolean equals( Object obj )
-    {
-        if ( this == obj )
-        {
-            return true;
-        }
-        if ( obj == null )
-        {
-            return false;
-        }
-        if ( getClass() != obj.getClass() )
-        {
-            return false;
-        }
-
-        DependencyKey other = (DependencyKey) obj;
-
-        if ( !Objects.equals( artifactId, other.artifactId ) )
-        {
-            return false;
-        }
-        return Objects.equals( groupId, other.groupId );
-    }
-
-
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/FastForwardFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/FastForwardFilter.java
index ea0eb9f..91977c0 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/FastForwardFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/FastForwardFilter.java
@@ -19,23 +19,23 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Deque;
 
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLFilter;
+import org.apache.maven.model.transform.pull.BufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 /**
  * This filter will skip all following filters and write directly to the output.
- * Should be used in case of a DOM that should not be effected by other filters, even though the elements match
+ * Should be used in case of a DOM that should not be effected by other filters, even though the elements match.
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
-class FastForwardFilter extends AbstractSAXFilter
+class FastForwardFilter extends BufferingParser
 {
     /**
      * DOM elements of pom
@@ -53,75 +53,51 @@ class FastForwardFilter extends AbstractSAXFilter
 
     private int domDepth = 0;
 
-    private ContentHandler originalHandler;
-
-    FastForwardFilter()
+    FastForwardFilter( XmlPullParser xmlPullParser )
     {
-        super();
-    }
-
-    FastForwardFilter( AbstractSAXFilter parent )
-    {
-        super( parent );
+        super( xmlPullParser );
     }
 
     @Override
-    public void startElement( String uri, String localName, String qName, Attributes atts )
-        throws SAXException
+    protected boolean accept() throws XmlPullParserException, IOException
     {
-        super.startElement( uri, localName, qName, atts );
-        if ( domDepth > 0 )
+        if ( xmlPullParser.getEventType() == START_TAG )
         {
-            domDepth++;
-        }
-        else
-        {
-            final String key = state.peek() + '.' + localName;
-            switch ( key )
+            String localName = xmlPullParser.getName();
+            if ( domDepth > 0 )
             {
-                case "execution.configuration":
-                case "plugin.configuration":
-                case "plugin.goals":
-                case "profile.reports":
-                case "project.reports":
-                case "reportSet.configuration":
-                    domDepth++;
-
-                    originalHandler = getContentHandler();
-
-                    ContentHandler outputContentHandler = getContentHandler();
-                    while ( outputContentHandler instanceof XMLFilter )
-                    {
-                        outputContentHandler = ( (XMLFilter) outputContentHandler ).getContentHandler();
-                    }
-                    setContentHandler( outputContentHandler );
-                    break;
-                default:
-                    break;
+                domDepth++;
+            }
+            else
+            {
+                final String key = state.peek() + '/' + localName;
+                switch ( key )
+                {
+                    case "execution/configuration":
+                    case "plugin/configuration":
+                    case "plugin/goals":
+                    case "profile/reports":
+                    case "project/reports":
+                    case "reportSet/configuration":
+                        domDepth++;
+                        disable();
+                        break;
+                    default:
+                        break;
+                }
             }
-            state.push( localName );
+            state.add( localName );
         }
-    }
-
-    @Override
-    public void endElement( String uri, String localName, String qName )
-        throws SAXException
-    {
-        if ( domDepth > 0 )
+        else if ( xmlPullParser.getEventType() == END_TAG )
         {
             domDepth--;
-
             if ( domDepth == 0 )
             {
-                setContentHandler( originalHandler );
+                enable();
             }
-        }
-        else
-        {
             state.pop();
         }
-        super.endElement( uri, localName, qName );
+        return true;
     }
 
-
 }
\ No newline at end of file
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ModulesXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ModulesXMLFilter.java
index 187fc78..4d3f94a 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ModulesXMLFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ModulesXMLFilter.java
@@ -19,93 +19,29 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import java.util.List;
 
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
+import org.apache.maven.model.transform.pull.NodeBufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * Remove all modules, this is just buildtime information
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
 class ModulesXMLFilter
-    extends AbstractEventXMLFilter
+    extends NodeBufferingParser
 {
-    private boolean parsingModules;
-
-    private String state;
-
-    ModulesXMLFilter()
-    {
-        super();
-    }
-
-    ModulesXMLFilter( AbstractSAXFilter parent )
-    {
-        super( parent );
-    }
-
-    @Override
-    public void startElement( String uri, String localName, String qName, Attributes atts )
-        throws SAXException
-    {
-        if ( !parsingModules && "modules".equals( localName ) )
-        {
-            parsingModules = true;
-        }
-
-        if ( parsingModules )
-        {
-            state = localName;
-        }
-
-        super.startElement( uri, localName, qName, atts );
-    }
-
-    @Override
-    public void endElement( String uri, String localName, String qName )
-        throws SAXException
-    {
-        if ( parsingModules )
-        {
-            switch ( localName )
-            {
-                case "modules":
-                    executeEvents();
-
-                    parsingModules = false;
-                    break;
-                default:
-                    super.endElement( uri, localName, qName );
-                    break;
-            }
-        }
-        else
-        {
-            super.endElement( uri, localName, qName );
-        }
-
-        // for this simple structure resetting to modules it sufficient
-        state = "modules";
-    }
-
-    @Override
-    protected boolean isParsing()
+    ModulesXMLFilter( XmlPullParser xmlPullParser )
     {
-        return parsingModules;
+        super( xmlPullParser, "modules" );
     }
 
-    @Override
-    protected String getState()
+    protected void process( List<Event> buffer )
     {
-        return state;
+        // Do nothing, as we want to delete those nodes completely
     }
 
-    @Override
-    protected boolean acceptEvent( String state )
-    {
-        return false;
-    }
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ParentXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ParentXMLFilter.java
index 926222c..37ac28b 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ParentXMLFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ParentXMLFilter.java
@@ -22,14 +22,13 @@ package org.apache.maven.model.transform;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 
-import org.apache.maven.model.transform.sax.SAXEventUtils;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import org.apache.maven.model.transform.pull.NodeBufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * <p>
@@ -40,167 +39,117 @@ import org.xml.sax.SAXException;
  * </p>
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
 class ParentXMLFilter
-    extends AbstractEventXMLFilter
+    extends NodeBufferingParser
 {
-    private boolean parsingParent;
-
-    // states
-    private String state;
-
-    // whiteSpace after <parent>, to be used to position <version>
-    private String parentWhitespace = "";
-
-    private String groupId;
-
-    private String artifactId;
-
-    private String relativePath;
-
-    private boolean hasVersion;
-
-    private boolean hasRelativePath;
-
-    private Optional<RelativeProject> resolvedParent;
 
     private final Function<Path, Optional<RelativeProject>> relativePathMapper;
 
-    private Path projectPath;
+    private final Path projectPath;
 
     /**
      * @param relativePathMapper
      */
-    ParentXMLFilter( Function<Path, Optional<RelativeProject>> relativePathMapper )
+    ParentXMLFilter( XmlPullParser parser,
+                     Function<Path, Optional<RelativeProject>> relativePathMapper, Path projectPath )
     {
+        super( parser, "parent" );
         this.relativePathMapper = relativePathMapper;
-    }
-
-    public void setProjectPath( Path projectPath )
-    {
         this.projectPath = projectPath;
     }
 
-    @Override
-    protected boolean isParsing()
-    {
-        return parsingParent;
-    }
-
-    @Override
-    protected String getState()
-    {
-        return state;
-    }
-
-    @Override
-    public void startElement( String uri, final String localName, String qName, Attributes atts )
-        throws SAXException
-    {
-        if ( !parsingParent && "parent".equals( localName ) )
-        {
-            parsingParent = true;
-        }
-
-        if ( parsingParent )
-        {
-            state = localName;
-
-            hasVersion |= "version".equals( localName );
-
-            // can be set to empty on purpose to enforce repository download
-            hasRelativePath |= "relativePath".equals( localName );
-        }
-
-        super.startElement( uri, localName, qName, atts );
-    }
-
-    @Override
-    public void characters( char[] ch, int start, int length )
-        throws SAXException
+    protected void process( List<Event> buffer )
     {
-        if ( parsingParent )
+        String tagName = null;
+        String groupId = null;
+        String artifactId = null;
+        String version = null;
+        String relativePath = null;
+        String whitespaceAfterParentStart = "";
+        boolean hasVersion = false;
+        boolean hasRelativePath = false;
+        for ( int i = 0; i < buffer.size(); i++ )
         {
-            final String eventState = state;
-
-            final String charSegment =  new String( ch, start, length );
-
-            switch ( eventState )
+            Event event = buffer.get( i );
+            if ( event.event == START_TAG )
             {
-                case "parent":
-                    parentWhitespace = nullSafeAppend( parentWhitespace, charSegment );
-                    break;
-                case "relativePath":
-                    relativePath = nullSafeAppend( relativePath, charSegment );
-                    break;
-                case "groupId":
-                    groupId = nullSafeAppend( groupId, charSegment );
-                    break;
-                case "artifactId":
-                    artifactId = nullSafeAppend( artifactId, charSegment );
-                    break;
-                default:
-                    break;
+                tagName = event.name;
+                hasVersion |= "version".equals( tagName );
+                hasRelativePath |= "relativePath".equals( tagName );
             }
-        }
-
-        super.characters( ch, start, length );
-    }
-
-    @Override
-    public void endElement( String uri, final String localName, String qName )
-        throws SAXException
-    {
-        if ( parsingParent )
-        {
-            switch ( localName )
+            else if ( event.event == TEXT )
             {
-                case "parent":
-                    if ( !hasVersion && ( !hasRelativePath || relativePath != null ) )
-                    {
-                        resolvedParent =
-                            resolveRelativePath( Paths.get( Objects.toString( relativePath, "../pom.xml" ) ) );
-                    }
-                    else
+                if ( event.text.matches( "\\s+" ) )
+                {
+                    if ( whitespaceAfterParentStart.isEmpty() )
                     {
-                        resolvedParent = Optional.empty();
+                        whitespaceAfterParentStart = event.text;
                     }
-
-                    if ( !hasVersion && resolvedParent.isPresent() )
-                    {
-                        try ( Includer i = super.include() )
-                        {
-                            super.characters( parentWhitespace.toCharArray(), 0,
-                                              parentWhitespace.length() );
-
-                            String versionQName = SAXEventUtils.renameQName( qName, "version" );
-
-                            super.startElement( uri, "version", versionQName, null );
-
-                            String resolvedParentVersion = resolvedParent.get().getVersion();
-
-                            super.characters( resolvedParentVersion.toCharArray(), 0,
-                                                          resolvedParentVersion.length() );
-
-                            super.endElement( uri, "version", versionQName );
-                        }
-                    }
-                    super.executeEvents();
-
-                    parsingParent = false;
-                    break;
-                default:
-                    // marker?
-                    break;
+                }
+                else if ( "groupId".equals( tagName ) )
+                {
+                    groupId = nullSafeAppend( groupId, event.text );
+                }
+                else if ( "artifactId".equals( tagName ) )
+                {
+                    artifactId = nullSafeAppend( artifactId, event.text );
+                }
+                else if ( "relativePath".equals( tagName ) )
+                {
+                    relativePath = nullSafeAppend( relativePath, event.text );
+                }
+                else if ( "version".equals( tagName ) )
+                {
+                    version = nullSafeAppend( version, event.text );
+                }
+            }
+            else if ( event.event == END_TAG && "parent".equals( event.name ) )
+            {
+                Optional<RelativeProject> resolvedParent;
+                if ( !hasVersion && ( !hasRelativePath || relativePath != null ) )
+                {
+                    Path relPath = Paths.get( Objects.toString( relativePath, "../pom.xml" ) );
+                    resolvedParent = resolveRelativePath( relPath, groupId, artifactId );
+                }
+                else
+                {
+                    resolvedParent = Optional.empty();
+                }
+                if ( !hasVersion && resolvedParent.isPresent() )
+                {
+                    int pos = buffer.get( i - 1 ).event == TEXT ? i - 1  : i;
+                    Event e = new Event();
+                    e.event = TEXT;
+                    e.text = whitespaceAfterParentStart;
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = START_TAG;
+                    e.namespace = buffer.get( 0 ).namespace;
+                    e.prefix = buffer.get( 0 ).prefix;
+                    e.name = "version";
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = TEXT;
+                    e.text = resolvedParent.get().getVersion();
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = END_TAG;
+                    e.name = "version";
+                    e.namespace = buffer.get( 0 ).namespace;
+                    e.prefix = buffer.get( 0 ).prefix;
+                    buffer.add( pos++, e );
+                }
+                break;
             }
         }
+        buffer.forEach( this::pushEvent );
+   }
 
-        super.endElement( uri, localName, qName );
-        state = "";
-    }
 
-    protected Optional<RelativeProject> resolveRelativePath( Path relativePath )
+    protected Optional<RelativeProject> resolveRelativePath( Path relativePath, String groupId, String artifactId )
     {
         Path pomPath = projectPath.resolve( relativePath );
         if ( Files.isDirectory( pomPath ) )
@@ -222,4 +171,5 @@ class ParentXMLFilter
         }
         return Optional.empty();
     }
+
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilter.java
deleted file mode 100644
index 767cb0b..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.maven.model.transform;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.XMLReader;
-import org.xml.sax.ext.LexicalHandler;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-
-/**
- * XML Filter to transform pom.xml to consumer pom.
- * This often means stripping of build-specific information.
- * When extra information is required during filtering it is probably a member of the BuildPomXMLFilter
- *
- * This filter is used at one location:
- * - org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory when publishing POM files.
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class RawToConsumerPomXMLFilter extends AbstractSAXFilter
-{
-    RawToConsumerPomXMLFilter( AbstractSAXFilter filter )
-    {
-        super( filter );
-    }
-
-    /**
-     * Don't allow overwriting parent
-     */
-    @Override
-    public final void setParent( XMLReader parent )
-    {
-        if ( getParent() == null )
-        {
-            super.setParent( parent );
-        }
-    }
-
-    @Override
-    public LexicalHandler getLexicalHandler()
-    {
-        return ( (AbstractSAXFilter) getParent() ).getLexicalHandler();
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
index d03b9e8..e36342b 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
@@ -21,14 +21,10 @@ package org.apache.maven.model.transform;
 
 import java.nio.file.Path;
 
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.xml.sax.SAXException;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
- *
+ * @author Guillaume Nodet
  * @author Robert Scholte
  * @since 4.0.0
  */
@@ -41,20 +37,20 @@ public class RawToConsumerPomXMLFilterFactory
         this.buildPomXMLFilterFactory = buildPomXMLFilterFactory;
     }
 
-    public final RawToConsumerPomXMLFilter get( Path projectPath )
-        throws SAXException, ParserConfigurationException, TransformerConfigurationException
+    public final XmlPullParser get( XmlPullParser orgParser, Path projectPath )
     {
-        BuildToRawPomXMLFilter parent = buildPomXMLFilterFactory.get( projectPath );
+        XmlPullParser parser = orgParser;
 
+        parser = buildPomXMLFilterFactory.get( parser, projectPath );
 
         // Ensure that xs:any elements aren't touched by next filters
-        AbstractSAXFilter filter = new FastForwardFilter( parent );
+        parser = new FastForwardFilter( parser );
 
         // Strip modules
-        filter = new ModulesXMLFilter( filter );
+        parser = new ModulesXMLFilter( parser );
         // Adjust relativePath
-        filter = new RelativePathXMLFilter( filter );
+        parser = new RelativePathXMLFilter( parser );
 
-        return new RawToConsumerPomXMLFilter( filter );
+        return parser;
     }
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ReactorDependencyXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ReactorDependencyXMLFilter.java
index b2d3ca4..7f9dd3b 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/ReactorDependencyXMLFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/ReactorDependencyXMLFilter.java
@@ -19,153 +19,95 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
+import java.util.List;
 import java.util.function.BiFunction;
 
-import org.apache.maven.model.transform.sax.SAXEventUtils;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import org.apache.maven.model.transform.pull.NodeBufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * Will apply the version if the dependency is part of the reactor
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
-public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
+public class ReactorDependencyXMLFilter extends NodeBufferingParser
 {
-    private boolean parsingDependency;
-
-    // states
-    private String state;
-
-    // whiteSpace after <dependency>, to be used to position <version>
-    private String dependencyWhitespace;
-
-    private boolean hasVersion;
-
-    private String groupId;
-
-    private String artifactId;
-
     private final BiFunction<String, String, String> reactorVersionMapper;
 
-    public ReactorDependencyXMLFilter( BiFunction<String, String, String> reactorVersionMapper )
+    public ReactorDependencyXMLFilter( XmlPullParser xmlPullParser,
+                                       BiFunction<String, String, String> reactorVersionMapper )
     {
+        super( xmlPullParser, "dependency" );
         this.reactorVersionMapper = reactorVersionMapper;
     }
 
-    @Override
-    public void startElement( String uri, String localName, String qName, Attributes atts )
-        throws SAXException
-    {
-        if ( !parsingDependency && "dependency".equals( localName ) )
-        {
-            parsingDependency = true;
-        }
-
-        if ( parsingDependency )
-        {
-            state = localName;
-
-            hasVersion |= "version".equals( localName );
-        }
-        super.startElement( uri, localName, qName, atts );
-    }
-
-    @Override
-    public void characters( char[] ch, int start, int length )
-        throws SAXException
+    protected void process( List<Event> buffer )
     {
-        if ( parsingDependency )
+        // whiteSpace after <dependency>, to be used to position <version>
+        String dependencyWhitespace = "";
+        boolean hasVersion = false;
+        String groupId = null;
+        String artifactId = null;
+        String tagName = null;
+        for ( int i = 0; i < buffer.size(); i++ )
         {
-            final String eventState = state;
-            String value = new String( ch, start, length );
-            switch ( eventState )
+            Event event = buffer.get( i );
+            if ( event.event == START_TAG )
             {
-                case "dependency":
-                    dependencyWhitespace = nullSafeAppend( dependencyWhitespace, value );
-                    break;
-                case "groupId":
-                    groupId = nullSafeAppend( groupId, value );
-                    break;
-                case "artifactId":
-                    artifactId = nullSafeAppend( artifactId, value );
-                    break;
-                default:
-                    break;
+                tagName = event.name;
+                hasVersion |= "version".equals( tagName );
             }
-        }
-        super.characters( ch, start, length );
-    }
-
-    @Override
-    public void endElement( String uri, final String localName, String qName )
-        throws SAXException
-    {
-        if ( parsingDependency )
-        {
-            switch ( localName )
+            else if ( event.event == TEXT )
             {
-                case "dependency":
-                    if ( !hasVersion )
+                if ( event.text.matches( "\\s+" ) )
+                {
+                    if ( dependencyWhitespace.isEmpty() )
                     {
-                        String version = getVersion();
-
-                        // dependency is not part of reactor, probably it is managed
-                        if ( version != null )
-                        {
-                            try ( Includer i = super.include() )
-                            {
-                                if ( dependencyWhitespace != null )
-                                {
-                                    super.characters( dependencyWhitespace.toCharArray(), 0,
-                                                      dependencyWhitespace.length() );
-
-                                }
-                                String versionQName = SAXEventUtils.renameQName( qName, "version" );
-
-                                super.startElement( uri, "version", versionQName, null );
-                                super.characters( version.toCharArray(), 0, version.length() );
-                                super.endElement( uri, "version", versionQName );
-                            }
-                        }
+                        dependencyWhitespace = event.text;
                     }
-                    super.executeEvents();
-
-                    parsingDependency = false;
-
-                    // reset
-                    hasVersion = false;
-                    dependencyWhitespace = null;
-                    groupId = null;
-                    artifactId = null;
-
-                    break;
-                default:
-                    break;
+                }
+                else if ( "groupId".equals( tagName ) )
+                {
+                    groupId = nullSafeAppend( groupId, event.text );
+                }
+                else if ( "artifactId".equals( tagName ) )
+                {
+                    artifactId = nullSafeAppend( artifactId, event.text );
+                }
+            }
+            else if ( event.event == END_TAG && "dependency".equals( event.name ) )
+            {
+                String version = reactorVersionMapper.apply( groupId, artifactId  );
+                if ( !hasVersion && version != null )
+                {
+                    int pos = buffer.get( i - 1 ).event == TEXT ? i - 1  : i;
+                    Event e = new Event();
+                    e.event = TEXT;
+                    e.text = dependencyWhitespace;
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = START_TAG;
+                    e.namespace = buffer.get( 0 ).namespace;
+                    e.prefix = buffer.get( 0 ).prefix;
+                    e.name = "version";
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = TEXT;
+                    e.text = version;
+                    buffer.add( pos++, e );
+                    e = new Event();
+                    e.event = END_TAG;
+                    e.name = "version";
+                    e.namespace = buffer.get( 0 ).namespace;
+                    e.prefix = buffer.get( 0 ).prefix;
+                    buffer.add( pos++, e );
+                }
+                break;
             }
         }
-
-        super.endElement( uri, localName, qName );
-
-        state = "";
-    }
-
-    private String getVersion()
-    {
-        return reactorVersionMapper.apply( groupId, artifactId  );
-    }
-
-    @Override
-    protected boolean isParsing()
-    {
-        return parsingDependency;
-    }
-
-    @Override
-    protected String getState()
-    {
-        return state;
+        buffer.forEach( this::pushEvent );
     }
 
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RelativePathXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RelativePathXMLFilter.java
index 2ca09ac..3064a29 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RelativePathXMLFilter.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RelativePathXMLFilter.java
@@ -19,90 +19,57 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import java.util.List;
 
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
+import org.apache.maven.model.transform.pull.NodeBufferingParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 /**
  * Remove relativePath element, has no value for consumer pom
  *
  * @author Robert Scholte
+ * @author Guillaume Nodet
  * @since 4.0.0
  */
-class RelativePathXMLFilter
-    extends AbstractEventXMLFilter
+public class RelativePathXMLFilter extends NodeBufferingParser
 {
-    private boolean parsingParent;
 
-    private String state;
-
-    RelativePathXMLFilter()
-    {
-        super();
-    }
-
-    RelativePathXMLFilter( AbstractSAXFilter parent )
+    public RelativePathXMLFilter( XmlPullParser xmlPullParser )
     {
-        super( parent );
+        super( xmlPullParser, "parent" );
     }
 
-    @Override
-    public void startElement( String uri, final String localName, String qName, Attributes atts )
-        throws SAXException
+    protected void process( List<Event> buffer )
     {
-        if ( !parsingParent && "parent".equals( localName ) )
+        boolean skip = false;
+        Event prev = null;
+        for ( Event event : buffer )
         {
-            parsingParent = true;
-        }
-
-        if ( parsingParent )
-        {
-            state = localName;
-        }
-
-        super.startElement( uri, localName, qName, atts );
-    }
-
-    @Override
-    public void endElement( String uri, String localName, String qName )
-        throws SAXException
-    {
-        if ( parsingParent )
-        {
-            switch ( localName )
+            if ( event.event == START_TAG && "relativePath".equals( event.name ) )
             {
-                case "parent":
-                    executeEvents();
-
-                    parsingParent = false;
-                    break;
-                default:
-                    break;
+                skip = true;
+                if ( prev != null && prev.event == TEXT && prev.text.matches( "\\s+" ) )
+                {
+                    prev = null;
+                }
+                event = null;
+            }
+            else if ( event.event == END_TAG && "relativePath".equals( event.name ) )
+            {
+                skip = false;
+                event = null;
+            }
+            else if ( skip )
+            {
+                event = null;
+            }
+            if ( prev != null )
+            {
+                pushEvent( prev );
             }
+            prev = event;
         }
-
-        super.endElement( uri, localName, qName );
-
-        // for this simple structure resetting to parent it sufficient
-        state = "parent";
+        pushEvent( prev );
     }
 
-    @Override
-    protected boolean isParsing()
-    {
-        return parsingParent;
-    }
-
-    @Override
-    protected String getState()
-    {
-        return state;
-    }
-
-    @Override
-    protected boolean acceptEvent( String state )
-    {
-        return !"relativePath".equals( state );
-    }
 }
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java
new file mode 100644
index 0000000..f8235d8
--- /dev/null
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java
@@ -0,0 +1,563 @@
+package org.apache.maven.model.transform.pull;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Objects;
+
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+/**
+ * An xml pull parser filter base implementation.
+ *
+ * @author Guillaume Nodet
+ * @since 4.0.0
+ */
+public class BufferingParser implements XmlPullParser
+{
+
+    protected XmlPullParser xmlPullParser;
+    protected Deque<Event> events;
+    protected Event current;
+    protected boolean disabled;
+
+    @SuppressWarnings( "checkstyle:VisibilityModifier" )
+    public static class Event
+    {
+        public int event;
+        public String name;
+        public String prefix;
+        public String namespace;
+        public boolean empty;
+        public String text;
+        public Attribute[] attributes;
+        public Namespace[] namespaces;
+    }
+
+    @SuppressWarnings( "checkstyle:VisibilityModifier" )
+    public static class Namespace
+    {
+        public String prefix;
+        public String uri;
+    }
+
+    @SuppressWarnings( "checkstyle:VisibilityModifier" )
+    public static class Attribute
+    {
+        public String name;
+        public String prefix;
+        public String namespace;
+        public String type;
+        public String value;
+        public boolean isDefault;
+    }
+
+
+    public BufferingParser( XmlPullParser xmlPullParser )
+    {
+        this.xmlPullParser = xmlPullParser;
+    }
+
+    @Override
+    public void setFeature( String name, boolean state ) throws XmlPullParserException
+    {
+        xmlPullParser.setFeature( name, state );
+    }
+
+    @Override
+    public boolean getFeature( String name )
+    {
+        return xmlPullParser.getFeature( name );
+    }
+
+    @Override
+    public void setProperty( String name, Object value ) throws XmlPullParserException
+    {
+        xmlPullParser.setProperty( name, value );
+    }
+
+    @Override
+    public Object getProperty( String name )
+    {
+        return xmlPullParser.getProperty( name );
+    }
+
+    @Override
+    public void setInput( Reader in ) throws XmlPullParserException
+    {
+        xmlPullParser.setInput( in );
+    }
+
+    @Override
+    public void setInput( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
+    {
+        xmlPullParser.setInput( inputStream, inputEncoding );
+    }
+
+    @Override
+    public String getInputEncoding()
+    {
+        return xmlPullParser.getInputEncoding();
+    }
+
+    @Override
+    public void defineEntityReplacementText( String entityName, String replacementText ) throws XmlPullParserException
+    {
+        xmlPullParser.defineEntityReplacementText( entityName, replacementText );
+    }
+
+    @Override
+    public int getNamespaceCount( int depth ) throws XmlPullParserException
+    {
+//  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
+        return xmlPullParser.getNamespaceCount( depth );
+    }
+
+    @Override
+    public String getNamespacePrefix( int pos ) throws XmlPullParserException
+    {
+//  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
+        return xmlPullParser.getNamespacePrefix( pos );
+    }
+
+    @Override
+    public String getNamespaceUri( int pos ) throws XmlPullParserException
+    {
+//  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
+        return xmlPullParser.getNamespaceUri( pos );
+    }
+
+    @Override
+    public String getNamespace( String prefix )
+    {
+//  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
+        return xmlPullParser.getNamespace( prefix );
+    }
+
+    @Override
+    public int getDepth()
+    {
+//  TODO:      if (current != null) throw new IllegalStateException("Not supported during events replay");
+        return xmlPullParser.getDepth();
+    }
+
+    @Override
+    public String getPositionDescription()
+    {
+        if ( current != null )
+        {
+            throw new IllegalStateException( "Not supported during events replay" );
+        }
+        return xmlPullParser.getPositionDescription();
+    }
+
+    @Override
+    public int getLineNumber()
+    {
+        if ( current != null )
+        {
+            throw new IllegalStateException( "Not supported during events replay" );
+        }
+        return xmlPullParser.getLineNumber();
+    }
+
+    @Override
+    public int getColumnNumber()
+    {
+        if ( current != null )
+        {
+            throw new IllegalStateException( "Not supported during events replay" );
+        }
+        return xmlPullParser.getColumnNumber();
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException
+    {
+        if ( current != null )
+        {
+            if ( current.event == TEXT || current.event == CDSECT )
+            {
+                return current.text.matches( "[ \r\t\n]+" );
+            }
+            else if ( current.event == IGNORABLE_WHITESPACE )
+            {
+                return true;
+            }
+            else
+            {
+                throw new XmlPullParserException( "no content available to check for whitespaces" );
+            }
+        }
+        return xmlPullParser.isWhitespace();
+    }
+
+    @Override
+    public String getText()
+    {
+        return current != null ? current.text : xmlPullParser.getText();
+    }
+
+    @Override
+    public char[] getTextCharacters( int[] holderForStartAndLength )
+    {
+        if ( current != null )
+        {
+            throw new IllegalStateException( "Not supported during events replay" );
+        }
+        return xmlPullParser.getTextCharacters( holderForStartAndLength );
+    }
+
+    @Override
+    public String getNamespace()
+    {
+        return current != null ? current.namespace : xmlPullParser.getNamespace();
+    }
+
+    @Override
+    public String getName()
+    {
+        return current != null ? current.name : xmlPullParser.getName();
+    }
+
+    @Override
+    public String getPrefix()
+    {
+        return current != null ? current.prefix : xmlPullParser.getPrefix();
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException
+    {
+        return current != null ? current.empty : xmlPullParser.isEmptyElementTag();
+    }
+
+    @Override
+    public int getAttributeCount()
+    {
+        if ( current != null )
+        {
+            return current.attributes != null ? current.attributes.length : 0;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeCount();
+        }
+    }
+
+    @Override
+    public String getAttributeNamespace( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].namespace;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeNamespace( index );
+        }
+    }
+
+    @Override
+    public String getAttributeName( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].name;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeName( index );
+        }
+    }
+
+    @Override
+    public String getAttributePrefix( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].prefix;
+        }
+        else
+        {
+            return xmlPullParser.getAttributePrefix( index );
+        }
+    }
+
+    @Override
+    public String getAttributeType( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].type;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeType( index );
+        }
+    }
+
+    @Override
+    public boolean isAttributeDefault( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].isDefault;
+        }
+        else
+        {
+            return xmlPullParser.isAttributeDefault( index );
+        }
+    }
+
+    @Override
+    public String getAttributeValue( int index )
+    {
+        if ( current != null )
+        {
+            return current.attributes[index].value;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeValue( index );
+        }
+    }
+
+    @Override
+    public String getAttributeValue( String namespace, String name )
+    {
+        if ( current != null )
+        {
+            if ( current.attributes != null )
+            {
+                for ( Attribute attr : current.attributes )
+                {
+                    if ( Objects.equals( namespace, attr.namespace )
+                            && Objects.equals( name, attr.name ) )
+                    {
+                        return attr.value;
+                    }
+                }
+            }
+            return null;
+        }
+        else
+        {
+            return xmlPullParser.getAttributeValue( namespace, name );
+        }
+    }
+
+    @Override
+    public void require( int type, String namespace, String name ) throws XmlPullParserException, IOException
+    {
+        if ( current != null )
+        {
+            throw new IllegalStateException( "Not supported during events replay" );
+        }
+        xmlPullParser.require( type, namespace, name );
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException
+    {
+        return current != null ? current.event : xmlPullParser.getEventType();
+    }
+
+    @Override
+    public int next() throws XmlPullParserException, IOException
+    {
+        while ( true )
+        {
+            if ( events != null && !events.isEmpty() )
+            {
+                current = events.removeFirst();
+                return current.event;
+            }
+            else
+            {
+                current = null;
+            }
+            if ( getEventType() == END_DOCUMENT )
+            {
+                throw new XmlPullParserException( "already reached end of XML input", this, null );
+            }
+            int currentEvent = xmlPullParser.next();
+            if ( disabled || accept() )
+            {
+                return currentEvent;
+            }
+        }
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException
+    {
+        while ( true )
+        {
+            if ( events != null && !events.isEmpty() )
+            {
+                current = events.removeFirst();
+                return current.event;
+            }
+            else
+            {
+                current = null;
+            }
+            if ( getEventType() == END_DOCUMENT )
+            {
+                throw new XmlPullParserException( "already reached end of XML input", this, null );
+            }
+            int currentEvent = xmlPullParser.nextToken();
+            if ( accept() )
+            {
+                return currentEvent;
+            }
+        }
+    }
+
+    @Override
+    public int nextTag() throws XmlPullParserException, IOException
+    {
+        int eventType = next();
+        if ( eventType == TEXT && isWhitespace() )
+        { // skip whitespace
+            eventType = next();
+        }
+        if ( eventType != START_TAG && eventType != END_TAG )
+        {
+            throw new XmlPullParserException( "expected START_TAG or END_TAG not "
+                    + TYPES[getEventType()], this, null );
+        }
+        return eventType;
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException
+    {
+        int eventType = getEventType();
+        if ( eventType != START_TAG )
+        {
+            throw new XmlPullParserException( "parser must be on START_TAG to read next text", this, null );
+        }
+        eventType = next();
+        if ( eventType == TEXT )
+        {
+            final String result = getText();
+            eventType = next();
+            if ( eventType != END_TAG )
+            {
+                throw new XmlPullParserException( "TEXT must be immediately followed by END_TAG and not "
+                        + TYPES[getEventType()], this, null );
+            }
+            return result;
+        }
+        else if ( eventType == END_TAG )
+        {
+            return "";
+        }
+        else
+        {
+            throw new XmlPullParserException( "parser must be on START_TAG or TEXT to read text", this, null );
+        }
+    }
+
+    protected Event bufferEvent() throws XmlPullParserException
+    {
+        Event event = new Event();
+        XmlPullParser pp = xmlPullParser;
+        event.event = xmlPullParser.getEventType();
+        switch ( event.event )
+        {
+            case START_DOCUMENT:
+            case END_DOCUMENT:
+                break;
+            case START_TAG:
+                event.name = pp.getName();
+                event.namespace = pp.getNamespace();
+                event.prefix = pp.getPrefix();
+                event.empty = pp.isEmptyElementTag();
+                event.text = pp.getText();
+                break;
+            case END_TAG:
+                event.name = pp.getName();
+                event.namespace = pp.getNamespace();
+                event.prefix = pp.getPrefix();
+                event.text = pp.getText();
+                break;
+            case TEXT:
+            case COMMENT:
+            case IGNORABLE_WHITESPACE:
+                event.text = pp.getText();
+                break;
+            default:
+                break;
+        }
+        return event;
+    }
+
+    protected void pushEvent( Event event )
+    {
+        if ( events == null )
+        {
+            events = new ArrayDeque<>();
+        }
+        events.add( event );
+    }
+
+    protected boolean accept() throws XmlPullParserException, IOException
+    {
+        return true;
+    }
+
+    protected void enable()
+    {
+        disabled = false;
+    }
+
+    protected void disable()
+    {
+        if ( events != null && !events.isEmpty() )
+        {
+            throw new IllegalStateException( "Can not disable filter while processing" );
+        }
+        disabled = true;
+        if ( xmlPullParser instanceof BufferingParser )
+        {
+            ( ( BufferingParser ) xmlPullParser ).disable();
+        }
+    }
+
+    protected static String nullSafeAppend( String originalValue, String charSegment )
+    {
+        if ( originalValue == null )
+        {
+            return charSegment;
+        }
+        else
+        {
+            return originalValue + charSegment;
+        }
+    }
+}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/XmlUtils.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/XmlUtils.java
new file mode 100644
index 0000000..f2243c8
--- /dev/null
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/XmlUtils.java
@@ -0,0 +1,132 @@
+package org.apache.maven.model.transform.pull;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import org.codehaus.plexus.util.xml.XmlStreamReader;
+import org.codehaus.plexus.util.xml.pull.MXSerializer;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.util.xml.pull.XmlSerializer;
+
+public class XmlUtils
+{
+
+    public static ByteArrayInputStream writeDocument( XmlStreamReader reader, XmlPullParser parser )
+            throws IOException, XmlPullParserException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Writer writer = newWriter( reader, baos );
+        writeDocument( parser, writer );
+        return new ByteArrayInputStream( baos.toByteArray() );
+    }
+
+    public static void writeDocument( XmlPullParser parser, Writer writer )
+            throws IOException, XmlPullParserException
+    {
+        XmlSerializer serializer = new MXSerializer();
+        serializer.setOutput( writer );
+
+        while ( parser.nextToken() != XmlPullParser.END_DOCUMENT )
+        {
+            switch ( parser.getEventType() )
+            {
+                case XmlPullParser.START_DOCUMENT:
+                    serializer.startDocument( parser.getInputEncoding(), true );
+                    break;
+                case XmlPullParser.END_DOCUMENT:
+                    serializer.endDocument();
+                case XmlPullParser.START_TAG:
+                    int nsStart = parser.getNamespaceCount( parser.getDepth() - 1 );
+                    int nsEnd = parser.getNamespaceCount( parser.getDepth() );
+                    for ( int i = nsStart; i < nsEnd; i++ )
+                    {
+                        String prefix = parser.getNamespacePrefix( i );
+                        String ns = parser.getNamespaceUri( i );
+                        serializer.setPrefix( prefix, ns );
+                    }
+                    serializer.startTag( parser.getNamespace(), parser.getName() );
+                    for ( int i = 0; i < parser.getAttributeCount(); i++ )
+                    {
+                        serializer.attribute( parser.getAttributeNamespace( i ),
+                                              parser.getAttributeName( i ),
+                                              parser.getAttributeValue( i ) );
+                    }
+                    break;
+                case XmlPullParser.END_TAG:
+                    serializer.endTag( parser.getNamespace(), parser.getName() );
+                    break;
+                case XmlPullParser.TEXT:
+                    serializer.text( normalize( parser.getText() ) );
+                    break;
+                case XmlPullParser.CDSECT:
+                    serializer.cdsect( parser.getText() );
+                    break;
+                case XmlPullParser.ENTITY_REF:
+                    serializer.entityRef( parser.getName() );
+                    break;
+                case XmlPullParser.IGNORABLE_WHITESPACE:
+                    serializer.ignorableWhitespace( normalize( parser.getText() ) );
+                    break;
+                case XmlPullParser.PROCESSING_INSTRUCTION:
+                    serializer.processingInstruction( parser.getText() );
+                    break;
+                case XmlPullParser.COMMENT:
+                    serializer.comment( normalize( parser.getText() ) );
+                    break;
+                case XmlPullParser.DOCDECL:
+                    serializer.docdecl( normalize( parser.getText() ) );
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        serializer.endDocument();
+    }
+
+    private static OutputStreamWriter newWriter( XmlStreamReader reader, ByteArrayOutputStream baos )
+            throws UnsupportedEncodingException
+    {
+        if ( reader.getEncoding() != null )
+        {
+            return new OutputStreamWriter( baos, reader.getEncoding() );
+        }
+        else
+        {
+            return new OutputStreamWriter( baos );
+        }
+    }
+
+    private static String normalize( String input )
+    {
+        if ( input.indexOf( '\n' ) >= 0 && !"\n".equals( System.lineSeparator() ) )
+        {
+            return input.replace( "\n", System.lineSeparator() );
+        }
+        return input;
+    }
+}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/AbstractSAXFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/AbstractSAXFilter.java
deleted file mode 100644
index 7189910..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/AbstractSAXFilter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
-import org.xml.sax.helpers.XMLFilterImpl;
-
-/**
- * XMLFilter with LexicalHandler.
- * Since some filters collect events before processing them, the LexicalHandler events must be collected too.
- * Otherwise the LexicalHandler events might end up before all collected XMLReader events.
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class AbstractSAXFilter extends XMLFilterImpl implements LexicalHandler
-{
-    private LexicalHandler lexicalHandler;
-
-    public AbstractSAXFilter()
-    {
-        super();
-    }
-
-    public AbstractSAXFilter( AbstractSAXFilter parent )
-    {
-        super( parent );
-        parent.setLexicalHandler( this );
-    }
-
-    public LexicalHandler getLexicalHandler()
-    {
-        return lexicalHandler;
-    }
-
-    public void setLexicalHandler( LexicalHandler lexicalHandler )
-    {
-        this.lexicalHandler = lexicalHandler;
-    }
-
-    @Override
-    public void startDTD( String name, String publicId, String systemId )
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.startDTD( name, publicId, systemId );
-        }
-    }
-
-    @Override
-    public void endDTD()
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.endDTD();
-        }
-    }
-
-    @Override
-    public void startEntity( String name )
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.startEntity( name );
-        }
-    }
-
-    @Override
-    public void endEntity( String name )
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.endEntity( name );
-        }
-    }
-
-    @Override
-    public void startCDATA()
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.startCDATA();
-        }
-    }
-
-    @Override
-    public void endCDATA()
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.endCDATA();
-        }
-    }
-
-    @Override
-    public void comment( char[] ch, int start, int length )
-        throws SAXException
-    {
-        if ( lexicalHandler != null )
-        {
-            lexicalHandler.comment( ch, start, length );
-        }
-    }
-
-
-    protected static String nullSafeAppend( String originalValue, String charSegment )
-    {
-        if ( originalValue == null )
-        {
-            return charSegment;
-        }
-        else
-        {
-            return originalValue + charSegment;
-        }
-    }
-
-
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/CommentRenormalizer.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/CommentRenormalizer.java
deleted file mode 100644
index 6483731..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/CommentRenormalizer.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
-
-/**
- * During parsing the line separators are transformed to \n
- * Unlike characters(), comments don't use the systems line separator for serialization.
- * Hence use this class in the LexicalHandler chain to do so
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class CommentRenormalizer implements LexicalHandler
-{
-    private final LexicalHandler lexicalHandler;
-
-    private final String lineSeparator;
-
-    public CommentRenormalizer( LexicalHandler lexicalHandler )
-    {
-        this( lexicalHandler,  System.lineSeparator() );
-    }
-
-    // for testing purpose
-    CommentRenormalizer( LexicalHandler lexicalHandler, String lineSeparator )
-    {
-        this.lexicalHandler = lexicalHandler;
-        this.lineSeparator = lineSeparator;
-    }
-
-    @Override
-    public void comment( char[] ch, int start, int length )
-        throws SAXException
-    {
-        if ( "\n".equals( lineSeparator ) )
-        {
-            lexicalHandler.comment( ch, start, length );
-        }
-        else
-        {
-            char[] ca = new String( ch, start, length ).replaceAll( "\n", lineSeparator ).toCharArray();
-
-            lexicalHandler.comment( ca, 0, ca.length );
-        }
-    }
-
-    @Override
-    public void startDTD( String name, String publicId, String systemId )
-        throws SAXException
-    {
-        lexicalHandler.startDTD( name, publicId, systemId );
-    }
-
-    @Override
-    public void endDTD()
-        throws SAXException
-    {
-        lexicalHandler.endDTD();
-    }
-
-    @Override
-    public void startEntity( String name )
-        throws SAXException
-    {
-        lexicalHandler.startEntity( name );
-    }
-
-    @Override
-    public void endEntity( String name )
-        throws SAXException
-    {
-        lexicalHandler.endEntity( name );
-    }
-
-    @Override
-    public void startCDATA()
-        throws SAXException
-    {
-        lexicalHandler.startCDATA();
-    }
-
-    @Override
-    public void endCDATA()
-        throws SAXException
-    {
-        lexicalHandler.endCDATA();
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/Factories.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/Factories.java
deleted file mode 100644
index 835ab0d..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/Factories.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import javax.xml.XMLConstants;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerFactory;
-
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXNotRecognizedException;
-import org.xml.sax.SAXNotSupportedException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-/**
- * Creates XML related factories with OWASP advices applied
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public final class Factories
-{
-    private Factories()
-    {
-    }
-
-    /**
-     * See
-     * https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#transformerfactory
-     */
-    public static TransformerFactory newTransformerFactory()
-    {
-        TransformerFactory tf = TransformerFactory.newInstance();
-        tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" );
-        tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
-
-        return tf;
-    }
-
-    public static XMLReader newXMLReader() throws SAXException, ParserConfigurationException
-    {
-        XMLReader reader = XMLReaderFactory.createXMLReader();
-
-        try
-        {
-            // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
-            // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
-
-            // Using the XMLReader's setFeature
-            reader.setFeature( "http://xml.org/sax/features/external-general-entities", false );
-        }
-        catch ( SAXNotRecognizedException e )
-        {
-            // Tried an unknown feature.
-        }
-        catch ( SAXNotSupportedException e )
-        {
-            // Tried a feature known to the parser but unsupported.
-        }
-        return reader;
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEvent.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEvent.java
deleted file mode 100644
index fae783b..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEvent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.SAXException;
-
-/**
- * Command pattern to gather events which can be executed later on.
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-@FunctionalInterface
-public interface SAXEvent
-{
-    void execute() throws SAXException;
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventFactory.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventFactory.java
deleted file mode 100644
index c9ae486..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventFactory.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.Locator;
-import org.xml.sax.ext.LexicalHandler;
-
-/**
- * Factory for SAXEvents
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public final class SAXEventFactory
-{
-    private final ContentHandler contentHandler;
-
-    private final LexicalHandler lexicalHandler;
-
-    protected SAXEventFactory( ContentHandler contentHandler, LexicalHandler lexicalHandler )
-    {
-        this.contentHandler = contentHandler;
-        this.lexicalHandler = lexicalHandler;
-    }
-
-    public SAXEvent characters( final char[] ch, final int start, final int length )
-    {
-        final char[] txt = new char[length];
-        System.arraycopy( ch, start, txt, 0, length );
-        return () -> contentHandler.characters( txt, 0, length );
-    }
-
-    public SAXEvent endDocument()
-    {
-        return contentHandler::endDocument;
-    }
-
-    public SAXEvent endElement( final String uri, final String localName, final String qName )
-    {
-        return () -> contentHandler.endElement( uri, localName, qName );
-    }
-
-    public SAXEvent endPrefixMapping( final String prefix )
-    {
-        return () ->  contentHandler.endPrefixMapping( prefix );
-    }
-
-    public SAXEvent ignorableWhitespace( final char[] ch, final int start, final int length )
-    {
-        return () ->  contentHandler.ignorableWhitespace( ch, start, length );
-    }
-
-    public SAXEvent processingInstruction( final String target, final String data )
-    {
-        return () -> contentHandler.processingInstruction( target, data );
-    }
-
-    public SAXEvent setDocumentLocator( final Locator locator )
-    {
-        return () -> contentHandler.setDocumentLocator( locator );
-    }
-
-    public SAXEvent skippedEntity( final String name )
-    {
-        return () -> contentHandler.skippedEntity( name );
-    }
-
-    public SAXEvent startDocument()
-    {
-        return contentHandler::startDocument;
-    }
-
-    public SAXEvent startElement( final String uri, final String localName, final String qName, final Attributes atts )
-    {
-        return () -> contentHandler.startElement( uri, localName, qName, atts );
-    }
-
-    public SAXEvent startPrefixMapping( final String prefix, final String uri )
-    {
-        return () -> contentHandler.startPrefixMapping( prefix, uri );
-    }
-
-    public static SAXEventFactory newInstance( ContentHandler contentHandler, LexicalHandler lexicalHandler )
-    {
-        return new SAXEventFactory( contentHandler, lexicalHandler );
-    }
-
-    public SAXEvent startDTD( String name, String publicId, String systemId )
-    {
-        return () -> lexicalHandler.startDTD( name, publicId, systemId );
-    }
-
-    public SAXEvent endDTD()
-    {
-        return lexicalHandler::endDTD;
-    }
-
-    public SAXEvent startEntity( String name )
-    {
-        return () -> lexicalHandler.startEntity( name );
-    }
-
-    public SAXEvent endEntity( String name )
-    {
-        return () -> lexicalHandler.endEntity( name );
-
-    }
-
-    public SAXEvent startCDATA()
-    {
-        return lexicalHandler::startCDATA;
-    }
-
-    public SAXEvent endCDATA()
-    {
-        return lexicalHandler::endCDATA;
-    }
-
-    public SAXEvent comment( char[] ch, int start, int length )
-    {
-        final char[] txt = new char[length];
-        System.arraycopy( ch, start, txt, 0, length );
-        return () -> lexicalHandler.comment(  txt, 0, length );
-    }
-}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventUtils.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventUtils.java
deleted file mode 100644
index b718d97..0000000
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/sax/SAXEventUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.util.regex.Pattern;
-
-/**
- * Utility class for SAXEvents
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public final class SAXEventUtils
-{
-    private static final Pattern PATTERN = Pattern.compile( "[^:]+$" );
-
-    private SAXEventUtils()
-    {
-    }
-
-    /**
-     * Returns the newLocalName prefixed with the namespace of the oldQName if present
-     *
-     * @param oldQName the QName, used for its namespace
-     * @param newLocalName the preferred localName
-     * @return the new QName
-     */
-    public static String renameQName( String oldQName, String newLocalName )
-    {
-        return PATTERN.matcher( oldQName ).replaceFirst( newLocalName );
-    }
-}
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/AbstractXMLFilterTests.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/AbstractXMLFilterTests.java
index 300d6a1..a18f81a 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/AbstractXMLFilterTests.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/AbstractXMLFilterTests.java
@@ -19,191 +19,41 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
+import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
-import java.io.Writer;
-import java.net.ContentHandler;
-import java.util.function.Consumer;
 
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.sax.SAXResult;
-import javax.xml.transform.sax.SAXSource;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.apache.maven.model.transform.sax.Factories;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.ext.LexicalHandler;
+import org.apache.maven.model.transform.pull.XmlUtils;
+import org.codehaus.plexus.util.xml.pull.MXParser;
+import org.codehaus.plexus.util.xml.pull.MXSerializer;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 public abstract class AbstractXMLFilterTests
 {
-    protected AbstractSAXFilter getFilter() throws TransformerException, SAXException, ParserConfigurationException
+    protected XmlPullParser getFilter(XmlPullParser parser)
     {
         throw new UnsupportedOperationException( "Override one of the getFilter() methods" );
     }
 
-    protected AbstractSAXFilter getFilter( Consumer<LexicalHandler> result )  throws TransformerException, SAXException, ParserConfigurationException
-    {
-        return getFilter();
-    }
-
-    protected String omitXmlDeclaration()
-    {
-        return "yes";
-    }
-
-    protected String indentAmount()
-    {
-        return null;
-    }
-
     protected String transform( String input )
-        throws TransformerException, SAXException, ParserConfigurationException
+        throws XmlPullParserException, IOException
     {
         return transform( new StringReader( input ) );
     }
 
-    /**
-     * Use this method only for testing a single filter.
-     *
-     * @param input
-     * @param filter
-     * @return
-     * @throws TransformerException
-     * @throws SAXException
-     * @throws ParserConfigurationException
-     */
-    protected String transform( String input, AbstractSAXFilter filter )
-        throws TransformerException, SAXException, ParserConfigurationException
-    {
-        setParent( filter );
-
-        SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
-        TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
-
-        transformerHandler.getTransformer().setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration() );
-        if ( indentAmount() != null )
-        {
-            transformerHandler.getTransformer().setOutputProperty( OutputKeys.INDENT, "yes" );
-            transformerHandler.getTransformer().setOutputProperty( "{http://xml.apache.org/xslt}indent-amount",
-                                                                   indentAmount() );
-        }
-
-        Transformer transformer = transformerFactory.newTransformer();
-
-        Writer writer = new StringWriter();
-        StreamResult result = new StreamResult( writer );
-        transformerHandler.setResult( result );
-
-        SAXResult transformResult = new SAXResult( transformerHandler );
-        SAXSource transformSource = new SAXSource( filter, new InputSource( new StringReader( input ) ) );
-
-        transformResult.setLexicalHandler( filter );
-        transformer.transform( transformSource, transformResult );
-
-        return writer.toString();
-
-    }
-
     protected String transform( Reader input )
-        throws TransformerException, SAXException, ParserConfigurationException
-    {
-        SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
-        TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
-
-        transformerHandler.getTransformer().setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration() );
-        if ( indentAmount() != null )
-        {
-            transformerHandler.getTransformer().setOutputProperty( OutputKeys.INDENT, "yes" );
-            transformerHandler.getTransformer().setOutputProperty( "{http://xml.apache.org/xslt}indent-amount",
-                                                                   indentAmount() );
-        }
-
-        Transformer transformer = transformerFactory.newTransformer();
-
-        Writer writer = new StringWriter();
-        StreamResult result = new StreamResult( writer );
-        transformerHandler.setResult( result );
-
-        SAXResult transformResult = new SAXResult( transformerHandler );
-
-        AbstractSAXFilter filter = getFilter( l -> transformResult.setLexicalHandler( l ) );
-        setParent( filter );
-
-        filter = new PerCharXMLFilter( filter );
-
-        filter.setLexicalHandler( transformerHandler );
-
-        SAXSource transformSource = new SAXSource( filter, new InputSource( input ) );
+            throws XmlPullParserException, IOException {
 
-        transformer.transform( transformSource, transformResult );
+        MXParser parser = new MXParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(input);
+        XmlPullParser filter = getFilter( parser );
 
+        StringWriter writer = new StringWriter();
+        XmlUtils.writeDocument( filter, writer );
         return writer.toString();
     }
 
-    private void setParent( AbstractSAXFilter filter )
-        throws SAXException, ParserConfigurationException
-    {
-        if ( filter.getParent() == null )
-        {
-            XMLReader r = Factories.newXMLReader();
-
-            AbstractSAXFilter perChar = new PerCharXMLFilter();
-            perChar.setParent( r );
-
-            filter.setParent( perChar );
-            filter.setFeature( "http://xml.org/sax/features/namespaces", true );
-        }
-    }
-
-    /**
-     * From {@link ContentHandler}
-     * <q>Your code should not assume that algorithms using char-at-a-time idioms will be working in characterunits;
-     * in some cases they will split characters. This is relevant wherever XML permits arbitrary characters, such as
-     * attribute values,processing instruction data, and comments as well as in data reported from this method. It's
-     * also generally relevant whenever Java code manipulates internationalized text; the issue isn't unique to XML.</q>
-     *
-     * @author Robert Scholte
-     */
-    class PerCharXMLFilter
-        extends AbstractSAXFilter
-    {
-        public PerCharXMLFilter()
-        {
-            super();
-        }
-
-        public PerCharXMLFilter( AbstractSAXFilter parent )
-        {
-            super( parent );
-        }
-
-        @Override
-        public void characters( char[] ch, int start, int length )
-            throws SAXException
-        {
-            for ( int i = 0; i < length; i++ )
-            {
-                super.characters( ch, start + i, 1 );
-            }
-        }
-
-        @Override
-        public void ignorableWhitespace( char[] ch, int start, int length )
-            throws SAXException
-        {
-            for ( int i = 0; i < length; i++ )
-            {
-                super.ignorableWhitespace( ch, start + i, 1 );
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/CiFriendlyXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/CiFriendlyXMLFilterTest.java
index 80cf84d..bfbbc11 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/CiFriendlyXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/CiFriendlyXMLFilterTest.java
@@ -19,32 +19,18 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
-
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.junit.jupiter.api.Test;
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
-import org.junit.jupiter.api.BeforeEach;
 
-import org.xml.sax.SAXException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class CiFriendlyXMLFilterTest extends AbstractXMLFilterTests
 {
-    private CiFriendlyXMLFilter filter;
+    @Override
+    protected CiFriendlyXMLFilter getFilter(XmlPullParser parser) {
 
-    @BeforeEach
-    public void setUp()
-    {
-        filter = new CiFriendlyXMLFilter( true );
+        CiFriendlyXMLFilter filter = new CiFriendlyXMLFilter( parser, true );
         filter.setChangelist( "CHANGELIST" );
-    }
-
-    @Override
-    protected AbstractSAXFilter getFilter()
-        throws TransformerException, SAXException, ParserConfigurationException
-    {
         return filter;
     }
 
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ConsumerPomXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ConsumerPomXMLFilterTest.java
index 4fe2c19..ee77d12 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ConsumerPomXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ConsumerPomXMLFilterTest.java
@@ -25,30 +25,17 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Optional;
 import java.util.function.BiFunction;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerConfigurationException;
-
-import org.apache.maven.model.transform.sax.AbstractSAXFilter;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.junit.jupiter.api.Test;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
 
 public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
 {
     @Override
-    protected String omitXmlDeclaration()
-    {
-        return "no";
-    }
-
-    @Override
-    protected AbstractSAXFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws SAXException, ParserConfigurationException, TransformerConfigurationException
+    protected XmlPullParser getFilter( XmlPullParser orgParser )
     {
-        final BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory = new BuildToRawPomXMLFilterFactory( lexicalHandlerConsumer, true )
+        final BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory = new BuildToRawPomXMLFilterFactory( true )
         {
             @Override
             protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
@@ -82,10 +69,9 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
 
         };
 
-        RawToConsumerPomXMLFilter filter =
-            new RawToConsumerPomXMLFilterFactory( buildPomXMLFilterFactory ).get( Paths.get( "pom.xml" ) );
-        filter.setFeature( "http://xml.org/sax/features/namespaces", true );
-        return filter;
+        XmlPullParser parser = new RawToConsumerPomXMLFilterFactory( buildPomXMLFilterFactory )
+                        .get( orgParser, Paths.get( "pom.xml" ) );
+        return parser;
     }
 
     @Test
@@ -254,8 +240,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
                         + "<!--post-in-->"
                         + "</modules>"
                         + "<!--after--></project>";
-        String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                        "<project><!--before--><!--after--></project>";
+        String expected = "<project><!--before--><!--after--></project>";
         String actual = transform( input );
         assertThat( actual ).and( expected ).areIdentical();
     }
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ModulesXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ModulesXMLFilterTest.java
index ce4c8b2..0080788 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ModulesXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ModulesXMLFilterTest.java
@@ -19,23 +19,19 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import static org.xmlunit.assertj.XmlAssert.assertThat;
-
-import java.util.function.Consumer;
-
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.junit.jupiter.api.Test;
-import org.xml.sax.ext.LexicalHandler;
+
+import static org.xmlunit.assertj.XmlAssert.assertThat;
 
 public class ModulesXMLFilterTest
     extends AbstractXMLFilterTests
 {
 
     @Override
-    protected ModulesXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
+    protected ModulesXMLFilter getFilter( XmlPullParser parser )
     {
-        ModulesXMLFilter filter = new ModulesXMLFilter();
-        lexicalHandlerConsumer.accept( filter );
-        return filter;
+        return new ModulesXMLFilter( parser );
     }
 
     @Test
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ParentXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ParentXMLFilterTest.java
index ac499fe..5c66e2f 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ParentXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ParentXMLFilterTest.java
@@ -19,32 +19,43 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Optional;
-import java.util.function.Consumer;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
+import java.util.function.Function;
 
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class ParentXMLFilterTest
     extends AbstractXMLFilterTests
 {
+    private Function<XmlPullParser, ParentXMLFilter> filterCreator;
+
+    @BeforeEach
+    void reset() {
+        filterCreator = null;
+    }
+
     @Override
-    protected ParentXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws TransformerException, SAXException, ParserConfigurationException
+    protected ParentXMLFilter getFilter( XmlPullParser parser )
     {
-        ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.of( new RelativeProject( "GROUPID",
-                                                                                           "ARTIFACTID",
-                                                                                           "1.0.0" ) ) );
-        filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
-        lexicalHandlerConsumer.accept( filter );
+        Function<XmlPullParser, ParentXMLFilter> filterCreator =
+            (this.filterCreator != null ? this.filterCreator : this::createFilter);
+        return filterCreator.apply(parser);
+    }
+
+    protected ParentXMLFilter createFilter( XmlPullParser parser ) {
+        return createFilter( parser,
+                x -> Optional.of(new RelativeProject("GROUPID", "ARTIFACTID", "1.0.0")),
+                Paths.get( "pom.xml").toAbsolutePath() );
+    }
 
+    protected ParentXMLFilter createFilter( XmlPullParser parser, Function<Path, Optional<RelativeProject>> pathMapper, Path projectPath ) {
+        ParentXMLFilter filter = new ParentXMLFilter( parser, pathMapper, projectPath );
         return filter;
     }
 
@@ -52,7 +63,7 @@ public class ParentXMLFilterTest
     public void testMinimum()
         throws Exception
     {
-        String input = "<parent/>";
+        String input = "<project><parent /></project>";
         String expected = input;
         String actual = transform( input );
         assertEquals( expected, actual );
@@ -62,11 +73,11 @@ public class ParentXMLFilterTest
     public void testNoRelativePath()
         throws Exception
     {
-        String input = "<parent>"
+        String input = "<project><parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
             + "<version>VERSION</version>"
-            + "</parent>";
+            + "</parent></project>";
         String expected = input;
 
         String actual = transform( input );
@@ -78,15 +89,19 @@ public class ParentXMLFilterTest
     public void testDefaultRelativePath()
         throws Exception
     {
-        String input = "<parent>"
-            + "<groupId>GROUPID</groupId>"
-            + "<artifactId>ARTIFACTID</artifactId>"
-            + "</parent>";
-        String expected = "<parent>"
-                        + "<groupId>GROUPID</groupId>"
-                        + "<artifactId>ARTIFACTID</artifactId>"
-                        + "<version>1.0.0</version>"
-                        + "</parent>";
+        String input = "<project>\n"
+            + "  <parent>\n"
+            + "    <groupId>GROUPID</groupId>\n"
+            + "    <artifactId>ARTIFACTID</artifactId>\n"
+            + "  </parent>\n"
+            + "</project>";
+        String expected = "<project>" + System.lineSeparator()
+                        + "  <parent>" + System.lineSeparator()
+                        + "    <groupId>GROUPID</groupId>" + System.lineSeparator()
+                        + "    <artifactId>ARTIFACTID</artifactId>" + System.lineSeparator()
+                        + "    <version>1.0.0</version>" + System.lineSeparator()
+                        + "  </parent>" + System.lineSeparator()
+                        + "</project>";
 
         String actual = transform( input );
 
@@ -103,16 +118,16 @@ public class ParentXMLFilterTest
     public void testEmptyRelativePathNoVersion()
         throws Exception
     {
-        String input = "<parent>"
+        String input = "<project><parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
             + "<relativePath></relativePath>"
-            + "</parent>";
-        String expected = "<parent>"
+            + "</parent></project>";
+        String expected = "<project><parent>"
                         + "<groupId>GROUPID</groupId>"
                         + "<artifactId>ARTIFACTID</artifactId>"
-                        + "<relativePath/>" // SAX optimization, however "" != null ...
-                        + "</parent>";
+                        + "<relativePath />" // SAX optimization, however "" != null ...
+                        + "</parent></project>";
 
         String actual = transform( input );
 
@@ -123,17 +138,17 @@ public class ParentXMLFilterTest
     public void testNoVersion()
         throws Exception
     {
-        String input = "<parent>"
+        String input = "<project><parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
             + "<relativePath>RELATIVEPATH</relativePath>"
-            + "</parent>";
-        String expected = "<parent>"
+            + "</parent></project>";
+        String expected = "<project><parent>"
                         + "<groupId>GROUPID</groupId>"
                         + "<artifactId>ARTIFACTID</artifactId>"
                         + "<relativePath>RELATIVEPATH</relativePath>"
                         + "<version>1.0.0</version>"
-                        + "</parent>";
+                        + "</parent></project>";
 
         String actual = transform( input );
 
@@ -144,17 +159,16 @@ public class ParentXMLFilterTest
     public void testInvalidRelativePath()
         throws Exception
     {
-        ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.ofNullable( null ) );
-        filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
+        filterCreator = parser -> createFilter(parser, x -> Optional.ofNullable( null ), Paths.get( "pom.xml").toAbsolutePath() );
 
-        String input = "<parent>"
+        String input = "<project><parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
             + "<relativePath>RELATIVEPATH</relativePath>"
-            + "</parent>";
+            + "</parent></project>";
         String expected = input;
 
-        String actual = transform( input, filter );
+        String actual = transform( input );
 
         assertEquals( expected, actual );
     }
@@ -163,18 +177,18 @@ public class ParentXMLFilterTest
     public void testRelativePathAndVersion()
         throws Exception
     {
-        String input = "<parent>"
+        String input = "<project><parent>"
             + "<groupId>GROUPID</groupId>"
             + "<artifactId>ARTIFACTID</artifactId>"
             + "<relativePath>RELATIVEPATH</relativePath>"
             + "<version>1.0.0</version>"
-            + "</parent>";
-        String expected = "<parent>"
+            + "</parent></project>";
+        String expected = "<project><parent>"
                         + "<groupId>GROUPID</groupId>"
                         + "<artifactId>ARTIFACTID</artifactId>"
                         + "<relativePath>RELATIVEPATH</relativePath>"
                         + "<version>1.0.0</version>"
-                        + "</parent>";
+                        + "</parent></project>";
 
         String actual = transform( input );
 
@@ -185,17 +199,20 @@ public class ParentXMLFilterTest
     public void testWithWeirdNamespace()
         throws Exception
     {
-        String input = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
+        String input = "<relativePath:project xmlns:relativePath=\"relativePath\">"
+            + "<relativePath:parent>"
             + "<relativePath:groupId>GROUPID</relativePath:groupId>"
             + "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
             + "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
-            + "</relativePath:parent>";
-        String expected = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
+            + "</relativePath:parent></relativePath:project>";
+        String expected = "<relativePath:project xmlns:relativePath=\"relativePath\">"
+                        + "<relativePath:parent>"
                         + "<relativePath:groupId>GROUPID</relativePath:groupId>"
                         + "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
                         + "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
                         + "<relativePath:version>1.0.0</relativePath:version>"
-                        + "</relativePath:parent>";
+                        + "</relativePath:parent>"
+                        + "</relativePath:project>";
 
         String actual = transform( input );
 
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ReactorDependencyXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ReactorDependencyXMLFilterTest.java
index 69b4783..0304ea6 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/ReactorDependencyXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/ReactorDependencyXMLFilterTest.java
@@ -19,27 +19,29 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import static org.xmlunit.assertj.XmlAssert.assertThat;
-
-import java.util.function.Consumer;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
+import java.util.function.BiFunction;
 
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
+
+import static org.xmlunit.assertj.XmlAssert.assertThat;
 
 public class ReactorDependencyXMLFilterTest
     extends AbstractXMLFilterTests
 {
+    private BiFunction<String, String, String> reactorVersionMapper;
+
+    @BeforeEach
+    protected void reset() {
+        reactorVersionMapper = null;
+    }
+
     @Override
-    protected ReactorDependencyXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
-        throws TransformerException, SAXException, ParserConfigurationException
+    protected ReactorDependencyXMLFilter getFilter(XmlPullParser parser)
     {
-        ReactorDependencyXMLFilter filter = new ReactorDependencyXMLFilter( (g, a) -> "1.0.0" );
-        lexicalHandlerConsumer.accept( filter );
-        return filter;
+        return new ReactorDependencyXMLFilter( parser,
+                reactorVersionMapper != null ? reactorVersionMapper : (g, a) -> "1.0.0" );
     }
 
     @Test
@@ -62,7 +64,7 @@ public class ReactorDependencyXMLFilterTest
     public void testManagedDependency()
         throws Exception
     {
-        ReactorDependencyXMLFilter filter = new ReactorDependencyXMLFilter( (g, a) -> null );
+        reactorVersionMapper = (g, a) -> null;
 
         String input = "<dependency>"
             + "<groupId>GROUPID</groupId>"
@@ -70,7 +72,7 @@ public class ReactorDependencyXMLFilterTest
             + "</dependency>";
         String expected = input;
 
-        String actual = transform( input, filter );
+        String actual = transform( input );
 
         assertThat( actual ).isEqualTo( expected );
     }
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/RelativePathXMLFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/RelativePathXMLFilterTest.java
index bc1c345..bfe3582 100644
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/RelativePathXMLFilterTest.java
+++ b/maven-model-transform/src/test/java/org/apache/maven/model/transform/RelativePathXMLFilterTest.java
@@ -19,17 +19,18 @@ package org.apache.maven.model.transform;
  * under the License.
  */
 
-import static org.xmlunit.assertj.XmlAssert.assertThat;
-
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 import org.junit.jupiter.api.Test;
 
+import static org.xmlunit.assertj.XmlAssert.assertThat;
+
 public class RelativePathXMLFilterTest
     extends AbstractXMLFilterTests
 {
     @Override
-    protected RelativePathXMLFilter getFilter()
+    protected RelativePathXMLFilter getFilter(XmlPullParser parser)
     {
-        return new RelativePathXMLFilter();
+        return new RelativePathXMLFilter(parser);
     }
 
     @Test
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/ChainedFilterTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/ChainedFilterTest.java
deleted file mode 100644
index 028b9e5..0000000
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/ChainedFilterTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.xmlunit.assertj.XmlAssert.assertThat;
-
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.io.Writer;
-
-import javax.xml.transform.Transformer;
-import javax.xml.transform.sax.SAXResult;
-import javax.xml.transform.sax.SAXSource;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-
-import org.junit.jupiter.api.Test;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-/**
- * A small example of a pipeline of 2 XML Filters, to understand how to get the expected result
- *
- * @author Robert Scholte
- * @since 4.0.0
- */
-public class ChainedFilterTest
-{
-
-    @Test
-    public void test()
-        throws Exception
-    {
-        String input = "<project><!-- aBc --><name>dEf</name></project>";
-
-        SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
-        TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
-
-        Writer writer = new StringWriter();
-        StreamResult result = new StreamResult( writer );
-        transformerHandler.setResult( result );
-
-        SAXResult transformResult = new SAXResult( transformerHandler );
-
-        // Watch the order of filters! In reverse order the values would be 'AweSome'
-        AbstractSAXFilter filter = new Awesome();
-
-        // AbstractSAXFilter doesn't have a constructor with XMLReader, otherwise the LexicalHandler pipeline will be broken
-        filter.setParent( Factories.newXMLReader() );
-
-        // LexicalHandler of transformerResult must be the first filter
-        transformResult.setLexicalHandler( filter );
-
-        filter = new ChangeCase( filter );
-        // LexicalHandler on last filter must be the transformerHandler
-        filter.setLexicalHandler( transformerHandler );
-
-        SAXSource transformSource = new SAXSource( filter, new InputSource( new StringReader( input ) ) );
-
-        Transformer transformer = transformerFactory.newTransformer();
-        transformer.transform( transformSource, transformResult );
-
-        String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-            + "<project><!--AWESOME--><name>awesome</name></project>";
-        assertThat( writer.toString() ).and( expected ).areIdentical();
-    }
-
-    static class ChangeCase
-        extends AbstractSAXFilter
-    {
-
-        public ChangeCase()
-        {
-            super();
-        }
-
-        public ChangeCase( AbstractSAXFilter parent )
-        {
-            super( parent );
-        }
-
-        @Override
-        public void comment( char[] ch, int start, int length )
-            throws SAXException
-        {
-            String s = new String( ch, start, length ).toUpperCase();
-            super.comment( s.toCharArray(), 0, s.length() );
-        }
-
-        @Override
-        public void characters( char[] ch, int start, int length )
-            throws SAXException
-        {
-            String s = new String( ch, start, length ).toLowerCase();
-            super.characters( s.toCharArray(), 0, s.length() );
-        }
-    }
-
-    static class Awesome
-        extends AbstractSAXFilter
-    {
-
-        public Awesome()
-        {
-            super();
-        }
-
-        public Awesome( AbstractSAXFilter parent )
-        {
-            super( parent );
-        }
-
-        @Override
-        public void comment( char[] ch, int start, int length )
-            throws SAXException
-        {
-            String s = "AweSome";
-            super.comment( s.toCharArray(), 0, s.length() );
-        }
-
-        @Override
-        public void characters( char[] ch, int start, int length )
-            throws SAXException
-        {
-            String s = "AweSome";
-            super.characters( s.toCharArray(), 0, s.length() );
-        }
-    }
-
-}
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/CommentRenormalizerTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/CommentRenormalizerTest.java
deleted file mode 100644
index c663741..0000000
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/CommentRenormalizerTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import org.xml.sax.ext.LexicalHandler;
-
-public class CommentRenormalizerTest
-{
-    private LexicalHandler lexicalHandler = mock( LexicalHandler.class );
-
-    @ParameterizedTest
-    @ValueSource( strings = { "\n", "\r\n", "\r" } )
-    public void singleLine( String lineSeparator )
-        throws Exception
-    {
-        CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
-
-        char[] ch = "single line".toCharArray();
-
-        commentRenormalizer.comment( ch, 0, ch.length );
-
-        verify( lexicalHandler ).comment( ch, 0, ch.length );
-    }
-
-    @ParameterizedTest
-    @ValueSource( strings = { "\n", "\r\n", "\r" } )
-    public void multiLine( String lineSeparator )
-        throws Exception
-    {
-        CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
-
-        String text = "I%sam%sthe%sbest%s";
-
-        char[] chIn = String.format( text, "\n", "\n", "\n", "\n" ).toCharArray();
-        char[] chOut = String.format( text, lineSeparator, lineSeparator, lineSeparator, lineSeparator ).toCharArray();
-
-        commentRenormalizer.comment( chIn, 0, chIn.length );
-
-        verify( lexicalHandler ).comment( chOut, 0, chOut.length );
-    }
-}
diff --git a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/SAXEventUtilsTest.java b/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/SAXEventUtilsTest.java
deleted file mode 100644
index 8ec4bec..0000000
--- a/maven-model-transform/src/test/java/org/apache/maven/model/transform/sax/SAXEventUtilsTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.apache.maven.model.transform.sax;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static org.hamcrest.CoreMatchers.is;
-
-import org.junit.jupiter.api.Test;
-
-public class SAXEventUtilsTest
-{
-    @Test
-    public void replaceWithNamespace()
-    {
-        assertThat( SAXEventUtils.renameQName( "org:bar", "com" ), is( "org:com" ) );
-    }
-
-    @Test
-    public void replaceWithoutNamespace()
-    {
-        assertThat( SAXEventUtils.renameQName( "bar", "com" ), is( "com" ) );
-    }
-}