You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2022/09/21 12:31:20 UTC

[sling-org-apache-sling-xss] branch master updated: SLING-7231 Move to owasp sanitizer library (#28)

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

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-xss.git


The following commit(s) were added to refs/heads/master by this push:
     new 7a9ed4c  SLING-7231 Move to owasp sanitizer library (#28)
7a9ed4c is described below

commit 7a9ed4c18cfacaa3a270ba0bc286b7d5e67cb00e
Author: Tatyana <36...@users.noreply.github.com>
AuthorDate: Wed Sep 21 14:31:16 2022 +0200

    SLING-7231 Move to owasp sanitizer library (#28)
    
    Co-authored-by: Julian Sedding <js...@apache.org>
    Co-authored-by: Robert Munteanu <ro...@apache.org>
---
 bnd.bnd                                            |  28 +-
 pom.xml                                            |  64 ++--
 src/main/java/javax/xml/parsers/FactoryFinder.java | 178 -----------
 .../java/javax/xml/transform/FactoryFinder.java    | 232 --------------
 .../org/apache/sling/xss/impl/AntiSamyActions.java |  30 ++
 .../sling/xss/impl/AntiSamyPolicyAdapter.java      | 287 +++++++++++++++++
 ...AttributeTranslatingTransformerFactoryImpl.java |  55 ----
 .../org/apache/sling/xss/impl/FallbackATag.java    |  35 +--
 .../apache/sling/xss/impl/FallbackSlingPolicy.java |  36 +--
 .../org/apache/sling/xss/impl/HtmlSanitizer.java   |  85 +++++
 .../sling/xss/impl/HtmlToHtmlContentContext.java   |  39 +--
 .../sling/xss/impl/InvalidConfigException.java     |  31 ++
 .../org/apache/sling/xss/impl/PolicyHandler.java   |  45 +--
 .../org/apache/sling/xss/impl/SanitizedResult.java |  35 +++
 .../java/org/apache/sling/xss/impl/XSSAPIImpl.java |  18 +-
 .../org/apache/sling/xss/impl/XSSFilterImpl.java   |  84 ++---
 .../sling/xss/impl/status/XSSStatusService.java    |   3 +-
 .../sling/xss/impl/style/BatikCssCleaner.java      |  82 +++++
 .../apache/sling/xss/impl/style/CssValidator.java  |  52 ++++
 .../sling/xss/impl/style/StyleTagProcessor.java    |  79 +++++
 .../xss/impl/style/ValidatingDocumentHandler.java  | 342 +++++++++++++++++++++
 .../sling/xss/impl/xml/AllowedEmptyTags.java       |  44 +++
 .../apache/sling/xss/impl/xml/AntiSamyPolicy.java  | 141 +++++++++
 .../apache/sling/xss/impl/xml/AntiSamyRules.java   | 127 ++++++++
 .../sling/xss/impl/xml/AntiSamyXmlParser.java      |  56 ++++
 .../org/apache/sling/xss/impl/xml/Attribute.java   | 139 +++++++++
 .../org/apache/sling/xss/impl/xml/Category.java    |  30 ++
 .../org/apache/sling/xss/impl/xml/Directive.java   |  37 +++
 .../sling/xss/impl/xml/DynamicTagAttributes.java   |  36 +++
 .../sling/xss/impl/xml/GlobalTagAttributes.java    |  36 +++
 .../sling/xss/impl/xml/IncludeExcludeMatcher.java  |  41 +++
 .../org/apache/sling/xss/impl/xml/Literal.java     |  43 +++
 .../org/apache/sling/xss/impl/xml/MapBuilder.java  | 246 +++++++++++++++
 .../org/apache/sling/xss/impl/xml/Property.java    | 125 ++++++++
 .../java/org/apache/sling/xss/impl/xml/Regexp.java |  64 ++++
 .../org/apache/sling/xss/impl/xml/Shorthand.java   |  30 ++
 .../java/org/apache/sling/xss/impl/xml/Tag.java    |  85 +++++
 .../org/apache/sling/xss/impl/xml/TagRules.java    |  30 ++
 .../apache/sling/xss/impl/xml/TagsToEncode.java    |  36 +++
 .../html/DynamicAttributesSanitizerPolicy.java     | 151 +++++++++
 .../javax.xml.transform.TransformerFactory         |  18 --
 .../apache/sling/xss/impl/AntiSamyPolicyTest.java  | 144 ++++-----
 .../org/apache/sling/xss/impl/XSSAPIImplTest.java  |  11 +-
 .../apache/sling/xss/impl/XSSFilterImplTest.java   |  28 ++
 .../org/apache/sling/xss/impl/xml/PolicyTest.java  |  70 +++++
 45 files changed, 2854 insertions(+), 754 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index 45edbcb..e0e4581 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,5 +1,10 @@
 Import-Package: !bsh, \
                 !nu.xom, \
+                !com.google.errorprone.annotations, \
+                !com.google.errorprone.annotations.concurrent, \
+                !javax.annotation, \
+                !org.checkerframework.checker.nullness.qual, \
+                !sun.misc, \
                 !org.apache.bcel.*, \
                 !org.apache.log4j.spi, \
                 !org.apache.log4j.xml, \
@@ -22,23 +27,20 @@ Import-Package: !bsh, \
                 !javax.mail.internet, \
                 !javax.servlet.jsp, \
                 !javax.servlet.jsp.tagext, \
-                !javax.xml.*, \
                 !sun.io, \
                 *
 Private-Package: org.apache.sling.xss.impl, \
                  org.apache.batik.*, \
+                 com.google.common.base, \
+                 com.google.common.collect, \
+                 com.google.common.io, \
+                 com.google.common.base.internal, \
+                 com.google.common.graph, \
+                 com.google.common.hash, \
+                 com.google.common.math, \
+                 com.google.common.primitives, \
                  org.w3c.css.sac, \
-                 org.apache.xalan.*, \
-                 org.apache.xerces.*, \
-                 org.apache.xml.*, \
-                 org.apache.xpath.*, \
-                 org.apache.xml.serialize, \
-                 org.apache.xml.serializer.*, \
-                 org.apache.commons.beanutils.*;-split-package:=merge-first, \
+                 org.apache.commons.beanutils.*, \
                  org.apache.commons.configuration.*, \
                  org.apache.commons.logging.impl, \
-                 org.cyberneko.html.*, \
-                 org.owasp.*, \
-                 java_cup.runtime, \
-                 javax.xml.parsers;-split-package:=merge-first, \
-                 javax.xml.transform;-split-package:=merge-first
+                 org.owasp.*;-split-package:=merge-first
diff --git a/pom.xml b/pom.xml
index f3e966e..cd8ad72 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>48</version>
+        <version>49</version>
         <relativePath />
     </parent>
 
@@ -44,7 +44,7 @@
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-xss.git</url>
       <tag>HEAD</tag>
   </scm>
-  
+
     <properties>
         <project.build.outputTimestamp>1653046679</project.build.outputTimestamp>
     </properties>
@@ -78,6 +78,8 @@
                             <ignore>javax.servlet.jsp.*</ignore>
                             <ignore>org.apache.avalon.*</ignore> 
                             <ignore>org.apache.log.*</ignore> 
+                            <ignore>org.owasp.validator.html.*</ignore> 
+                            <ignore>org.w3c.dom.svg.*</ignore> 
                         </ignores>
                     </configuration>
                 </plugin>
@@ -102,10 +104,6 @@
                         <configuration>
                             <outputDirectory>${project.build.directory}/dependencies-classes</outputDirectory>
                             <artifactItems>
-                                <artifactItem>
-                                    <groupId>org.owasp.antisamy</groupId>
-                                    <artifactId>antisamy</artifactId>
-                                </artifactItem>
                                 <artifactItem>
                                     <groupId>org.owasp.esapi</groupId>
                                     <artifactId>esapi</artifactId>
@@ -114,14 +112,6 @@
                                     <groupId>org.owasp.encoder</groupId>
                                     <artifactId>encoder</artifactId>
                                 </artifactItem>
-                                <artifactItem>
-                                    <groupId>xalan</groupId>
-                                    <artifactId>xalan</artifactId>
-                                </artifactItem>
-                                <artifactItem>
-                                    <groupId>xml-apis</groupId>
-                                    <artifactId>xml-apis</artifactId>
-                                </artifactItem>
                             </artifactItems>
                         </configuration>
                     </execution>
@@ -188,21 +178,21 @@
     <!-- ======================================================================= -->
     <dependencies>
         <dependency>
-            <groupId>org.owasp.antisamy</groupId>
-            <artifactId>antisamy</artifactId>
-            <version>1.6.4</version>
+            <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
+            <artifactId>owasp-java-html-sanitizer</artifactId>
+            <version>20220608.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>xml-apis</groupId>
-            <artifactId>xml-apis</artifactId>
-            <version>1.4.01</version>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+            <version>2.13.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>xalan</groupId>
-            <artifactId>xalan</artifactId>
-            <version>2.7.2</version>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>2.13.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -219,8 +209,30 @@
                     <groupId>commons-collections</groupId>
                     <artifactId>commons-collections</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.owasp.antisamy</groupId>
+                    <artifactId>antisamy</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.apache.xmlgraphics</groupId>
+            <artifactId>batik-css</artifactId>
+            <version>1.14</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>xml-apis</groupId>
+                    <artifactId>xml-apis-ext</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.w3c.css</groupId>
+            <artifactId>sac</artifactId>
+            <version>1.3</version>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.owasp.encoder</groupId>
@@ -358,6 +370,12 @@
             <version>1.0.1</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.25</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/src/main/java/javax/xml/parsers/FactoryFinder.java b/src/main/java/javax/xml/parsers/FactoryFinder.java
deleted file mode 100644
index 1ff4f95..0000000
--- a/src/main/java/javax/xml/parsers/FactoryFinder.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// $Id: FactoryFinder.java 670431 2008-06-23 01:40:03Z mrglavas $
-
-package javax.xml.parsers;
-
-/**
- * <p>This class is duplicated for each JAXP subpackage so keep it in
- * sync.  It is package private.
- *
- * <p>This code is designed to implement the JAXP 1.1 spec pluggability
- * feature and is designed to run on JDK version 1.1 and later including
- * JVMs that perform early linking like the Microsoft JVM in IE 5.  Note
- * however that it must be compiled on a JDK version 1.2 or later system
- * since it calls Thread#getContextClassLoader().  The code also runs both
- * as part of an unbundled jar file and when bundled as part of the JDK.
- *
- * <p><strong>NOTE</strong>:
- * <p>This class is included in order to make sure that all {@code javax.xml.parsers} factories are the ones embedded by the bundle
- * and not the ones provided by the underlying JVM or platform.
- *
- * <p>For more details check the following issues:
- * <ol>
- *      <li>https://issues.apache.org/jira/browse/SLING-8321</li>
- *      <li>https://issues.apache.org/jira/browse/SLING-8328</li>
- * </ol>
- */
-final class FactoryFinder {
-    
-    /**
-     * <p>Debug flag to trace loading process.</p>
-     */
-    private static boolean debug = false;
-
-    // Define system property "jaxp.debug" to get output
-    static {
-        // Use try/catch block to support applets, which throws
-        // SecurityException out of this code.
-        try {
-            String val = System.getProperty("jaxp.debug");
-            // Allow simply setting the prop to turn on debug
-            debug = val != null && (! "false".equals(val));
-        } catch (SecurityException se) {
-            debug = false;
-        }
-    }
-    
-    private FactoryFinder() {}
-
-    private static void dPrint(String msg) {
-        if (debug) {
-            System.err.println("JAXP: " + msg);
-        }
-    }
-    
-    /**
-     * Create an instance of a class using the specified ClassLoader and
-     * optionally fall back to the current ClassLoader if not found.
-     *
-     * @param className Name of the concrete class corresponding to the
-     * service provider
-     *
-     * @param cl ClassLoader to use to load the class, null means to use
-     * the bootstrap ClassLoader
-     *
-     * @param doFallback true if the current ClassLoader should be tried as
-     * a fallback if the class is not found using cl
-     */
-    static Object newInstance(String className, ClassLoader cl,
-                                      boolean doFallback)
-        throws ConfigurationError
-    {
-        // assert(className != null);
-
-        try {
-            Class providerClass;
-            if (cl == null) {
-                // If classloader is null Use the bootstrap ClassLoader.  
-                // Thus Class.forName(String) will use the current
-                // ClassLoader which will be the bootstrap ClassLoader.
-                providerClass = Class.forName(className);
-            } else {
-                try {
-                    providerClass = cl.loadClass(className);
-                } catch (ClassNotFoundException x) {
-                    if (doFallback) {
-                        // Fall back to current classloader
-                        cl = FactoryFinder.class.getClassLoader();
-                        if (cl != null) {
-                            providerClass = cl.loadClass(className);
-                        }
-                        else {
-                            providerClass = Class.forName(className);
-                        }
-                    } else {
-                        throw x;
-                    }
-                }
-            }
-                        
-            Object instance = providerClass.newInstance();
-            if (debug) dPrint("created new instance of " + providerClass +
-                   " using ClassLoader: " + cl);
-            return instance;
-        } catch (ClassNotFoundException x) {
-            throw new ConfigurationError(
-                "Provider " + className + " not found", x);
-        } catch (Exception x) {
-            throw new ConfigurationError(
-                "Provider " + className + " could not be instantiated: " + x,
-                x);
-        }
-    }
-    
-    /**
-     * Finds the implementation Class object in the specified order.  Main
-     * entry point.
-     * @return Class object of factory, never null
-     *
-     * @param factoryId             Name of the factory to find, same as
-     *                              a property name
-     * @param fallbackClassName     Implementation class name, if nothing else
-     *                              is found.  Use null to mean no fallback.
-     *
-     * Package private so this code can be shared.
-     */
-    static Object find(String factoryId, String fallbackClassName)
-        throws ConfigurationError
-    {        
-
-        // Figure out which ClassLoader to use for loading the provider
-        // class.  If there is a Context ClassLoader then use it.
-        
-        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-        
-        if (classLoader == null) {
-            // if we have no Context ClassLoader
-            // so use the current ClassLoader
-            classLoader = FactoryFinder.class.getClassLoader();
-        }
-
-        if (debug) dPrint("loaded from fallback value: " + fallbackClassName);
-        return newInstance(fallbackClassName, classLoader, true);
-    }
-
-    static class ConfigurationError extends Error {
-        private Exception exception;
-
-        /**
-         * Construct a new instance with the specified detail string and
-         * exception.
-         */
-        ConfigurationError(String msg, Exception x) {
-            super(msg);
-            this.exception = x;
-        }
-
-        Exception getException() {
-            return exception;
-        }
-    }
-
-}
diff --git a/src/main/java/javax/xml/transform/FactoryFinder.java b/src/main/java/javax/xml/transform/FactoryFinder.java
deleted file mode 100644
index 7fee7be..0000000
--- a/src/main/java/javax/xml/transform/FactoryFinder.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// $Id: FactoryFinder.java 670431 2008-06-23 01:40:03Z mrglavas $
-
-package javax.xml.transform;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Optional;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * <p>This class is duplicated for each JAXP subpackage so keep it in
- * sync.  It is package private.
- *
- * <p>This code is designed to implement the JAXP 1.1 spec pluggability
- * feature and is designed to run on JDK version 1.1 and later including
- * JVMs that perform early linking like the Microsoft JVM in IE 5.  Note
- * however that it must be compiled on a JDK version 1.2 or later system
- * since it calls Thread#getContextClassLoader().  The code also runs both
- * as part of an unbundled jar file and when bundled as part of the JDK.
- * 
- * <p>This class does not support the full transformer factory lookup mechanism.
- * It only supports lookups via the <tt>ServiceLoader</tt> mechanism. By virtue of the
- * libraries embedded in the Sling XSS bundle, the fallback <tt>TransformerFactory</tt>
- * implementation is the one provided by Xalan.</p>
- *
- * <p><strong>NOTE</strong>:
- * <p>This class is included in order to make sure that all {@code javax.xml.transform} factories are the ones embedded by the bundle
- * and not the ones provided by the underlying JVM or platform.
- *
- * <p>For more details check the following issues:
- * <ol>
- *      <li>https://issues.apache.org/jira/browse/SLING-8321</li>
- *      <li>https://issues.apache.org/jira/browse/SLING-8328</li>
- *      <li>https://issues.apache.org/jira/browse/SLING-10953</li>
- * </ol>
- */
-final class FactoryFinder {
-    
-    /**
-     * <p>Debug flag to trace loading process.</p>
-     */
-    private static boolean debug = false;
-
-    // Define system property "jaxp.debug" to get output
-    static {
-        // Use try/catch block to support applets, which throws
-        // SecurityException out of this code.
-        try {
-            String val = System.getProperty("jaxp.debug");
-            // Allow simply setting the prop to turn on debug
-            debug = val != null && (! "false".equals(val));
-        } catch (SecurityException se) {
-            debug = false;
-        }
-    }
-    
-    private FactoryFinder() {}
-
-    private static void dPrint(String msg) {
-        if (debug) {
-            System.err.println("JAXP: " + msg);
-        }
-    }
-    
-    /**
-     * Create an instance of a class using the specified ClassLoader and
-     * optionally fall back to the current ClassLoader if not found.
-     *
-     * @param className Name of the concrete class corresponding to the
-     * service provider
-     *
-     * @param cl ClassLoader to use to load the class, null means to use
-     * the bootstrap ClassLoader
-     *
-     * @param doFallback true if the current ClassLoader should be tried as
-     * a fallback if the class is not found using cl
-     */
-    static Object newInstance(String className, ClassLoader cl,
-                                      boolean doFallback)
-        throws ConfigurationError
-    {
-        // assert(className != null);
-
-        try {
-            Class providerClass;
-            if (cl == null) {
-                // If classloader is null Use the bootstrap ClassLoader.  
-                // Thus Class.forName(String) will use the current
-                // ClassLoader which will be the bootstrap ClassLoader.
-                providerClass = Class.forName(className);
-            } else {
-                try {
-                    providerClass = cl.loadClass(className);
-                } catch (ClassNotFoundException x) {
-                    if (doFallback) {
-                        // Fall back to current classloader
-                        cl = FactoryFinder.class.getClassLoader();
-                        if (cl != null) {
-                            providerClass = cl.loadClass(className);
-                        }
-                        else {
-                            providerClass = Class.forName(className);
-                        }
-                    } else {
-                        throw x;
-                    }
-                }
-            }
-                        
-            Object instance = providerClass.newInstance();
-            if (debug) dPrint("created new instance of " + providerClass +
-                   " using ClassLoader: " + cl);
-            return instance;
-        } catch (ClassNotFoundException x) {
-            throw new ConfigurationError(
-                "Provider " + className + " not found", x);
-        } catch (Exception x) {
-            throw new ConfigurationError(
-                "Provider " + className + " could not be instantiated: " + x,
-                x);
-        }
-    }
-    
-    /**
-     * Finds the implementation Class object in the specified order.  Main
-     * entry point.
-     * @return Class object of factory, never null
-     *
-     * @param factoryId             Name of the factory to find, same as
-     *                              a property name
-     * @param fallbackClassName     Implementation class name, if nothing else
-     *                              is found.  Use null to mean no fallback.
-     *
-     * Package private so this code can be shared.
-     */
-    static Object find(String factoryId, String fallbackClassName)
-        throws ConfigurationError
-    {        
-
-        // Figure out which ClassLoader to use for loading the provider
-        // class.  If there is a Context ClassLoader then use it.
-        
-        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-        
-        if (classLoader == null) {
-            // if we have no Context ClassLoader
-            // so use the current ClassLoader
-            classLoader = FactoryFinder.class.getClassLoader();
-        }
-
-        Object instance = loadFactoryFromServiceFile(factoryId, classLoader);
-        if ( instance != null )
-            return instance;
-
-        if (debug) dPrint("loaded from fallback value: " + fallbackClassName);
-        return newInstance(fallbackClassName, classLoader, true);
-    }
-
-    private static Object loadFactoryFromServiceFile(String factoryId, ClassLoader classLoader) {
-        try {
-            Enumeration<URL> serviceFiles = classLoader.getResources("/META-INF/services/" + factoryId);
-            while ( serviceFiles.hasMoreElements() ) {
-                URL url = serviceFiles.nextElement();
-                if ( debug ) {
-                    dPrint("Inspecting service file " + url );
-                }
-                try ( InputStream is = url.openStream() ) {
-                    List<String> lines = IOUtils.readLines(is, StandardCharsets.UTF_8);
-                    Optional<String> service = lines.stream()
-                        .filter( s -> ! s.trim().startsWith("#") )
-                        .filter( s -> ! s.trim().isEmpty())
-                        .findFirst();
-
-                    if ( service.isPresent() ) {
-                        try {
-                            return newInstance(service.get(), classLoader, true);
-                        } catch (ConfigurationError e) {
-                            // continue
-                        }
-                    }
-                }
-            }
-        } catch (IOException e) {
-            if (debug) {
-                dPrint("Failed loading service files " + e.getMessage());
-                e.printStackTrace(System.err);
-            }
-        }
-
-        return null;
-    }
-
-    static class ConfigurationError extends Error {
-        private Exception exception;
-
-        /**
-         * Construct a new instance with the specified detail string and
-         * exception.
-         */
-        ConfigurationError(String msg, Exception x) {
-            super(msg);
-            this.exception = x;
-        }
-
-        Exception getException() {
-            return exception;
-        }
-    }
-
-}
diff --git a/src/main/java/org/apache/sling/xss/impl/AntiSamyActions.java b/src/main/java/org/apache/sling/xss/impl/AntiSamyActions.java
new file mode 100644
index 0000000..68839c8
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/AntiSamyActions.java
@@ -0,0 +1,30 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl;
+
+public class AntiSamyActions {
+  private AntiSamyActions() {
+    throw new IllegalStateException("Utility class");
+  }
+  public static final String REMOVE_ATTRIBUTE_ON_INVALID = "removeAttribute";
+  static final String TRUNCATE = "truncate";
+  static final String FILTER = "filter";
+  static final String REMOVE = "remove";
+  static final String VALIDATE = "validate";
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/AntiSamyPolicyAdapter.java b/src/main/java/org/apache/sling/xss/impl/AntiSamyPolicyAdapter.java
new file mode 100644
index 0000000..bacb4f9
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/AntiSamyPolicyAdapter.java
@@ -0,0 +1,287 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.sling.xss.impl.style.CssValidator;
+import org.apache.sling.xss.impl.xml.Attribute;
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy;
+import org.apache.sling.xss.impl.xml.Tag;
+import org.jetbrains.annotations.Nullable;
+import org.owasp.html.AttributePolicy;
+import org.owasp.html.HtmlPolicyBuilder;
+import org.owasp.html.PolicyFactory;
+
+import com.google.common.base.Predicate;
+
+public class AntiSamyPolicyAdapter {
+    private static final String ALLOW_DYNAMIC_ATTRIBUTES = "allowDynamicAttributes";
+    private static final String REMOVE_TAG_ON_INVALID_ACTION = "removeTag";
+
+    private final List<String> onInvalidRemoveTagList = new ArrayList<>();
+    private final Map<String, AttributePolicy> dynamicAttributesPolicyMap = new HashMap<>();
+
+    private PolicyFactory policyFactory;
+    private CssValidator cssValidator;
+
+    public AntiSamyPolicyAdapter(AntiSamyPolicy policy) {
+        removeAttributeGuards();
+        HtmlPolicyBuilder policyBuilder = new HtmlPolicyBuilder();
+
+        cssValidator = new CssValidator(policy.getCssPolicy());
+
+        // ------------ this is for the global attributes -------------
+        Map<String, Attribute> globalAttributes = policy.getGlobalAttributes();
+
+        for (Attribute attribute : globalAttributes.values()) {
+            if (attribute.getOnInvalid().equals(REMOVE_TAG_ON_INVALID_ACTION)) {
+                onInvalidRemoveTagList.add(attribute.getName());
+            }
+
+            if (CssValidator.STYLE_ATTRIBUTE_NAME.equals(attribute.getName())) {
+                // we match style tags separately
+                policyBuilder.allowAttributes(attribute.getName()).matching(cssValidator.newCssAttributePolicy())
+                        .globally();
+            } else {
+                List<String> allowedValuesFromAttribute = attribute.getLiterals();
+                for (String allowedValue : allowedValuesFromAttribute) {
+                    policyBuilder.allowAttributes(attribute.getName()).matching(true, allowedValue).globally();
+                }
+
+                List<Pattern> regexsFromAttribute = attribute.getPatternList();
+                if (!regexsFromAttribute.isEmpty()) {
+                    policyBuilder.allowAttributes(attribute.getName()).matching(matchesToPatterns(regexsFromAttribute))
+                            .globally();
+                } else {
+                    policyBuilder.allowAttributes(attribute.getName()).globally();
+                }
+            }
+        }
+
+        // ------------ this is for the allowed empty tags -------------
+        List<String> allowedEmptyTags = policy.getAllowedEmptyTags();
+        for (String allowedEmptyTag : allowedEmptyTags) {
+            policyBuilder.allowWithoutAttributes(allowedEmptyTag);
+        }
+
+        // ------------ this is for the tag rules -------------
+        Map<String, Tag> tagMap = policy.getTagRules();
+        for (Map.Entry<String, Tag> tag : tagMap.entrySet()) {
+
+            String tagAction = tag.getValue().getAction();
+            switch (tagAction) {
+                // Tag.action
+                case AntiSamyActions.TRUNCATE:
+                    policyBuilder.allowElements(tag.getValue().getName());
+                    break;
+
+                // filter: remove tags, but keep content,
+                case AntiSamyActions.FILTER:
+                    break;
+
+                // remove: remove tag and contents
+                case AntiSamyActions.REMOVE:
+                    policyBuilder.disallowElements(tag.getValue().getName());
+                    break;
+
+                case AntiSamyActions.VALIDATE:
+                case "":
+                    policyBuilder.allowElements(tag.getValue().getName());
+                    boolean styleSeen = false;
+                    // get the allowed Attributes for the tag
+                    Map<String, Attribute> allowedAttributes = tag.getValue().getAttributeMap();
+                    // if there are allowed Attributes, map over them
+                    for (Attribute attribute : allowedAttributes.values()) {
+
+                        if (attribute.getOnInvalid().equals(REMOVE_TAG_ON_INVALID_ACTION)) {
+                            onInvalidRemoveTagList.add(attribute.getName());
+                        }
+
+                        styleSeen = CssValidator.STYLE_ATTRIBUTE_NAME.equals(attribute.getName());
+
+                        List<String> literalList = attribute.getLiterals();
+                        List<Pattern> patternList = attribute.getPatternList();
+
+                        if (!literalList.isEmpty() && !patternList.isEmpty()) {
+                            // if both, the patterns and the literals are not empty, the value should be checked with them with an OR and not with an AND.
+                            policyBuilder.allowAttributes(attribute.getName())
+                                .matching(matchesPatternsOrLiterals(patternList, true, literalList))
+                                .onElements(tag.getValue().getName());
+                        }
+                        else if (!literalList.isEmpty()) {
+                            policyBuilder.allowAttributes(attribute.getName())
+                                .matching(true, literalList.toArray(new String[0]))
+                                .onElements(tag.getValue().getName());
+                            policyBuilder.allowAttributes(attribute.getName()).onElements(tag.getValue().getName());
+                        }
+                        else if (!patternList.isEmpty()) {
+                            policyBuilder.allowAttributes(attribute.getName())
+                                    .matching(matchesToPatterns(patternList))
+                                    .onElements(tag.getValue().getName());
+                        }
+                    }
+
+                    if (!styleSeen) {
+                        policyBuilder.allowAttributes(CssValidator.STYLE_ATTRIBUTE_NAME)
+                                .matching(cssValidator.newCssAttributePolicy()).onElements(tag.getValue().getName());
+                    }
+                    break;
+
+                default:
+                    throw new IllegalArgumentException("No tag action found.");
+            }
+        }
+
+        // disallow style tag on specific elements
+        policyBuilder.disallowAttributes(CssValidator.STYLE_ATTRIBUTE_NAME)
+                .onElements(cssValidator.getDisallowedTagNames().toArray(new String[0]));
+
+        // ---------- dynamic attributes ------------
+        Map<String, Attribute> dynamicAttributes = new HashMap<>();
+
+        // checks if the dynamic attributes are allowed
+        if (policy.getDirectives().get(ALLOW_DYNAMIC_ATTRIBUTES).equals("true")) {
+            dynamicAttributes.putAll(policy.getDynamicAttributes());
+            for (Attribute attribute : dynamicAttributes.values()) {
+                if (attribute.getOnInvalid().equals(REMOVE_TAG_ON_INVALID_ACTION)) {
+                    onInvalidRemoveTagList.add(attribute.getName());
+                }
+
+                List<Pattern> regexsFromAttribute = attribute.getPatternList();
+                for (Pattern regex : regexsFromAttribute) {
+                    dynamicAttributesPolicyMap.put(attribute.getName(), newDynamicAttributePolicy(regex));
+                }
+
+                List<String> allowedValuesFromAttribute = attribute.getLiterals();
+                if (!allowedValuesFromAttribute.isEmpty()) {
+                    dynamicAttributesPolicyMap.put(attribute.getName(),
+                            newDynamicAttributePolicy(true, allowedValuesFromAttribute.toArray(new String[0])));
+                }
+
+            }
+        }
+
+        policyFactory = policyBuilder.allowTextIn(CssValidator.STYLE_TAG_NAME).toFactory();
+
+    }
+
+    public PolicyFactory getHtmlCleanerPolicyFactory() {
+        return policyFactory;
+    }
+
+    public Map<String, AttributePolicy> getDynamicAttributesPolicyMap() {
+        return dynamicAttributesPolicyMap;
+    }
+
+    public List<String> getOnInvalidRemoveTagList() {
+        return onInvalidRemoveTagList;
+    }
+
+    public CssValidator getCssValidator() {
+        return cssValidator;
+    }
+
+    private static Predicate<String> matchesToPatterns(List<Pattern> patternList) {
+        return new Predicate<String>() {
+            @Override
+            public boolean apply(String s) {
+                for (Pattern pattern : patternList) {
+                    if (pattern.matcher(s).matches()) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+    }
+
+    private static Predicate<String> matchesPatternsOrLiterals(List<Pattern> patternList, boolean ignoreCase, List<String> literalList) {
+        return new Predicate<String>() {
+            public boolean apply(String s) {
+                // check if the string matches to the pattern
+                for (Pattern pattern : patternList) {
+                    if (pattern.matcher(s).matches()) {
+                        return true;
+                    }
+                }
+                // if the pattern does not match it goes through the literals
+                for (String string : literalList) {
+                    s = ignoreCase
+                        ? s.toLowerCase()
+                        : s;
+                    if (string.equals(s)) {
+                        return true;
+                    }
+                }
+                // if it neither matches the patterns nor the literals it returns false
+                return false;
+            }
+        };
+    }
+
+    public AttributePolicy newDynamicAttributePolicy(final Pattern pattern) {
+        return new AttributePolicy() {
+            @Override
+            public @Nullable String apply(String elementName, String attributeName, String value) {
+                return pattern.matcher(value).matches() ? value : null;
+            }
+        };
+    }
+
+    public AttributePolicy newDynamicAttributePolicy(boolean ignoreCase, String... allowedValues) {
+        final List<String> allowed = Arrays.asList(allowedValues);
+        return new AttributePolicy() {
+            @Override
+            public @Nullable String apply(String elementName, String attributeName, String uncanonValue) {
+                String value = ignoreCase ? uncanonValue.toLowerCase() : uncanonValue;
+                return allowed.contains(value) ? value : null;
+            }
+        };
+    }
+
+    // java html sanitizer has some default Attribute Guards, which we don't want.
+    // So we are removing them here
+    private void removeAttributeGuards() {
+        try {
+            Field guards = HtmlPolicyBuilder.class.getDeclaredField("ATTRIBUTE_GUARDS");
+            letMeIn(guards);
+            guards.set(null, new HashMap<>());
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private void letMeIn(Field field) throws ReflectiveOperationException {
+        if (!field.isAccessible())
+            field.setAccessible(true);
+        if ((field.getModifiers() & Modifier.FINAL) != 0) {
+            Field modifiersField = Field.class.getDeclaredField("modifiers");
+            modifiersField.setAccessible(true);
+            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/AttributeTranslatingTransformerFactoryImpl.java b/src/main/java/org/apache/sling/xss/impl/AttributeTranslatingTransformerFactoryImpl.java
deleted file mode 100644
index d8c04aa..0000000
--- a/src/main/java/org/apache/sling/xss/impl/AttributeTranslatingTransformerFactoryImpl.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ~ Licensed to the Apache Software Foundation (ASF) under one
- ~ or more contributor license agreements.  See the NOTICE file
- ~ distributed with this work for additional information
- ~ regarding copyright ownership.  The ASF licenses this file
- ~ to you under the Apache License, Version 2.0 (the
- ~ "License"); you may not use this file except in compliance
- ~ with the License.  You may obtain a copy of the License at
- ~
- ~   http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing,
- ~ software distributed under the License is distributed on an
- ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- ~ KIND, either express or implied.  See the License for the
- ~ specific language governing permissions and limitations
- ~ under the License.
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-package org.apache.sling.xss.impl;
-
-import static javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD;
-import static javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET;
-import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
-
-import javax.xml.XMLConstants;
-import javax.xml.transform.TransformerConfigurationException;
-
-import org.apache.xalan.processor.TransformerFactoryImpl;
-
-/**
- * Translates configuration calls to specific unsupported attributes to the {@link XMLConstants#FEATURE_SECURE_PROCESSING} feature
- *
- * <p>This is done in order to support AntiSamy 1.6.4 which requires the usage of a JAXP 1.5 compliant
- * transformer factory, which is Xalan is not. This implementation is minimal and not expected to be used
- * outside of this bundle.</p>
- *
- * @see <a href="https://github.com/nahsra/antisamy/issues/103">AntiSamy issue 103</a>
- */
-public class AttributeTranslatingTransformerFactoryImpl extends TransformerFactoryImpl {
-
-    @Override
-    public void setAttribute(String name, Object value) throws IllegalArgumentException {
-        if ( "".equals(value) &&  (
-                ACCESS_EXTERNAL_DTD.equals(name) ||
-                ACCESS_EXTERNAL_STYLESHEET.equals(name) ) ) {
-            try {
-                setFeature(FEATURE_SECURE_PROCESSING, true);
-                return;
-            } catch (TransformerConfigurationException e) {
-                throw new IllegalArgumentException("Failed translating attribute " + name + " to feature " + FEATURE_SECURE_PROCESSING ,e);
-            }
-        }
-        super.setAttribute(name, value);
-    }
-}
diff --git a/src/main/java/org/apache/sling/xss/impl/FallbackATag.java b/src/main/java/org/apache/sling/xss/impl/FallbackATag.java
index a94de65..c2e5266 100644
--- a/src/main/java/org/apache/sling/xss/impl/FallbackATag.java
+++ b/src/main/java/org/apache/sling/xss/impl/FallbackATag.java
@@ -18,29 +18,19 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.xss.impl;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
-import org.owasp.validator.html.model.Attribute;
-import org.owasp.validator.html.model.Tag;
+import org.apache.sling.xss.impl.xml.Attribute;
+import org.apache.sling.xss.impl.xml.Tag;
 
 public class FallbackATag extends Tag {
 
-    static final Attribute FALLBACK_HREF_ATTRIBUTE = new Attribute(
-            "href",
-            Arrays.asList(
-                    XSSFilterImpl.ON_SITE_SIMPLIFIED,
-                    XSSFilterImpl.OFF_SITE_SIMPLIFIED
-            ),
-            Collections.emptyList(),
-            "removeAttribute", ""
-    );
-
     private final Tag wrapped;
 
     public FallbackATag(Tag wrapped) {
-        super("a", new HashMap<>(), "validate");
+        super("a", AntiSamyActions.VALIDATE, new ArrayList<>());
         this.wrapped = wrapped;
     }
 
@@ -60,8 +50,8 @@ public class FallbackATag extends Tag {
     }
 
     @Override
-    public String getRegularExpression() {
-        return wrapped.getRegularExpression();
+    public List<Attribute> getAttributeList() {
+        return wrapped.getAttributeList();
     }
 
     @Override
@@ -69,10 +59,17 @@ public class FallbackATag extends Tag {
         return wrapped.getName();
     }
 
+    @Override
+    public Map<String, Attribute> getAttributeMap() {
+        Map<String, Attribute> map = wrapped.getAttributeMap();
+        map.put("href", XSSFilterImpl.FALLBACK_HREF_ATTRIBUTE);
+        return map;
+    }
+
     @Override
     public Attribute getAttributeByName(String name) {
         if ("href".equalsIgnoreCase(name)) {
-            return FALLBACK_HREF_ATTRIBUTE;
+            return XSSFilterImpl.FALLBACK_HREF_ATTRIBUTE;
         }
         return wrapped.getAttributeByName(name);
     }
diff --git a/src/main/java/org/apache/sling/xss/impl/FallbackSlingPolicy.java b/src/main/java/org/apache/sling/xss/impl/FallbackSlingPolicy.java
index 5e8145e..16ac7f4 100644
--- a/src/main/java/org/apache/sling/xss/impl/FallbackSlingPolicy.java
+++ b/src/main/java/org/apache/sling/xss/impl/FallbackSlingPolicy.java
@@ -18,38 +18,24 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.xss.impl;
 
+import java.io.IOException;
 import java.io.InputStream;
 
-import org.owasp.validator.html.InternalPolicy;
-import org.owasp.validator.html.PolicyException;
-import org.owasp.validator.html.model.Tag;
-import org.xml.sax.InputSource;
+import javax.xml.stream.XMLStreamException;
 
-public class FallbackSlingPolicy extends InternalPolicy {
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy;
+import org.apache.sling.xss.impl.xml.Tag;
 
-    private FallbackATag fallbackATag;
-    private final Object aTagLock = new Object();
+public class FallbackSlingPolicy extends AntiSamyPolicy {
 
-    public FallbackSlingPolicy(InputStream inputStream) throws PolicyException {
-       super(getSimpleParseContext(getTopLevelElement(new InputSource(inputStream), () -> new InputSource(inputStream))));
+    public FallbackSlingPolicy(InputStream inputStream) throws InvalidConfigException, XMLStreamException, IOException {
 
-    }
+        super(inputStream);
 
-    @Override
-    public Tag getTagByLowercaseName(String tagName) {
-        if ("a".equalsIgnoreCase(tagName)) {
-            synchronized (aTagLock) {
-                if (fallbackATag == null) {
-                    Tag wrapped = super.getTagByLowercaseName(tagName);
-                    if (wrapped != null) {
-                        fallbackATag = new FallbackATag(wrapped);
-                    }
-                }
-            }
-            if (fallbackATag != null) {
-                return fallbackATag;
-            }
+        Tag original = tagRules.get("a");
+        if (original != null) {
+            Tag wrapped = new FallbackATag(original);
+            tagRules.put("a", wrapped);
         }
-        return super.getTagByLowercaseName(tagName);
     }
 }
diff --git a/src/main/java/org/apache/sling/xss/impl/HtmlSanitizer.java b/src/main/java/org/apache/sling/xss/impl/HtmlSanitizer.java
new file mode 100644
index 0000000..777068a
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/HtmlSanitizer.java
@@ -0,0 +1,85 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy;
+import org.owasp.html.DynamicAttributesSanitizerPolicy;
+import org.owasp.html.Handler;
+import org.owasp.html.HtmlStreamEventReceiver;
+import org.owasp.html.HtmlStreamRenderer;
+import org.owasp.html.PolicyFactory;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class HtmlSanitizer {
+
+    private AntiSamyPolicyAdapter customPolicy;
+    private ImmutableMap policies;
+    private ImmutableSet<String> textContainers;
+
+    public HtmlSanitizer(AntiSamyPolicy policy) {
+        this.customPolicy = new AntiSamyPolicyAdapter(policy);
+        policies = reflectionGetPolicies(customPolicy.getHtmlCleanerPolicyFactory());
+        textContainers = reflectionGetTextContainers(customPolicy.getHtmlCleanerPolicyFactory());
+    }
+
+    public SanitizedResult scan(String taintedHTML) {
+        StringBuilder sb = new StringBuilder(taintedHTML.length());
+        HtmlStreamEventReceiver out = HtmlStreamRenderer.create(sb, Handler.DO_NOTHING);
+        DynamicAttributesSanitizerPolicy dynamicPolicy = new DynamicAttributesSanitizerPolicy(out, policies,
+                textContainers, customPolicy.getDynamicAttributesPolicyMap(), customPolicy.getOnInvalidRemoveTagList());
+
+        org.owasp.html.HtmlSanitizer.sanitize(taintedHTML, dynamicPolicy,
+                customPolicy.getCssValidator().newStyleTagProcessor());
+        return new SanitizedResult(sb.toString(), dynamicPolicy.getNumberOfErrors());
+    }
+
+    private ImmutableSet<String> reflectionGetTextContainers(PolicyFactory policyFactory) {
+        Class<?> c = policyFactory.getClass();
+        try {
+            Field field = c.getDeclaredField("textContainers");
+            field.setAccessible(true);
+            return (ImmutableSet<String>) field.get(policyFactory);
+        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private ImmutableMap reflectionGetPolicies(PolicyFactory policyFactory) {
+        Class<?> c = policyFactory.getClass();
+        try {
+            Field field = c.getDeclaredField("policies");
+            field.setAccessible(true);
+            return (ImmutableMap) field.get(policyFactory);
+        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public SanitizedResult scan(String taintedHTML, AntiSamyPolicy policy) {
+        Objects.requireNonNull(taintedHTML, "Null html input");
+        Objects.requireNonNull(policy, "Null policy loaded");
+
+        return new HtmlSanitizer(policy).scan(taintedHTML);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/HtmlToHtmlContentContext.java b/src/main/java/org/apache/sling/xss/impl/HtmlToHtmlContentContext.java
index d387033..8229f1d 100644
--- a/src/main/java/org/apache/sling/xss/impl/HtmlToHtmlContentContext.java
+++ b/src/main/java/org/apache/sling/xss/impl/HtmlToHtmlContentContext.java
@@ -16,12 +16,8 @@
  ******************************************************************************/
 package org.apache.sling.xss.impl;
 
-import java.util.List;
 
 import org.apache.commons.lang3.StringUtils;
-import org.owasp.validator.html.CleanResults;
-import org.owasp.validator.html.PolicyException;
-import org.owasp.validator.html.ScanException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,14 +41,10 @@ public class HtmlToHtmlContentContext implements XSSFilterRule {
     @Override
     public boolean check(final PolicyHandler policyHandler, final String str) {
         if (StringUtils.isNotEmpty(str)) {
-            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
             try {
-                Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
-                return policyHandler.getAntiSamy().scan(str).getNumberOfErrors() == 0;
+                return getCleanResults(policyHandler, str).getNumberOfErrors() == 0;
             } catch (final Exception se) {
                 logError(se, str);
-            } finally {
-                Thread.currentThread().setContextClassLoader(tccl);
             }
         }
         return false;
@@ -62,21 +54,16 @@ public class HtmlToHtmlContentContext implements XSSFilterRule {
      * @see XSSFilterRule#filter(PolicyHandler, java.lang.String)
      */
     @Override
-    public String filter(final PolicyHandler policyHandler, final String str) {
-        if (StringUtils.isNotEmpty(str)) {
+    public String filter(final PolicyHandler policyHandler, final String unsafeString) {
+        if (StringUtils.isNotEmpty(unsafeString)) {
             try {
-                final CleanResults  results = getCleanResults(policyHandler, str);
+                final String results = getCleanResults(policyHandler, unsafeString).getSanitizedString();
                 if (results != null) {
-                    final String cleaned = results.getCleanHTML();
-                    final List<String> errors = results.getErrorMessages();
-                    for (final String error : errors) {
-                        log.info("AntiSamy warning: {}", error);
-                    }
-                    log.debug("Protected (HTML -> HTML):\n{}", cleaned);
-                    return cleaned;
+                    log.debug("Protected (HTML -> HTML):\n{}", results);
+                    return results;
                 }
             } catch (Exception e) {
-                logError(e, str);
+                logError(e, unsafeString);
             }
         }
         return StringUtils.EMPTY;
@@ -90,18 +77,14 @@ public class HtmlToHtmlContentContext implements XSSFilterRule {
         return true;
     }
 
-    private CleanResults getCleanResults(PolicyHandler handler, String input) throws ScanException, PolicyException {
-        CleanResults results;
-        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+    private SanitizedResult getCleanResults(PolicyHandler handler, String input) {
+        SanitizedResult results;
         try {
-            Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
-            results = handler.getAntiSamy().scan(input);
+            results = handler.getHtmlSanitizer().scan(input);
         } catch (StackOverflowError e) {
             log.debug("Will perform a second attempt at filtering the following input due to a StackOverflowError:\n{}", input);
-            results = handler.getFallbackAntiSamy().scan(input);
+            results = handler.getFallbackHtmlSanitizer().scan(input);
             log.debug("Second attempt was successful.");
-        } finally {
-            Thread.currentThread().setContextClassLoader(tccl);
         }
         return results;
     }
diff --git a/src/main/java/org/apache/sling/xss/impl/InvalidConfigException.java b/src/main/java/org/apache/sling/xss/impl/InvalidConfigException.java
new file mode 100644
index 0000000..0501001
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/InvalidConfigException.java
@@ -0,0 +1,31 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl;
+
+public class InvalidConfigException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public InvalidConfigException(Exception e) {
+        super(e);
+    }
+
+    public InvalidConfigException(String s) {
+        super(s);
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/PolicyHandler.java b/src/main/java/org/apache/sling/xss/impl/PolicyHandler.java
index 5568983..9b4c257 100644
--- a/src/main/java/org/apache/sling/xss/impl/PolicyHandler.java
+++ b/src/main/java/org/apache/sling/xss/impl/PolicyHandler.java
@@ -21,61 +21,44 @@ import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 
 import org.apache.commons.io.IOUtils;
-import org.owasp.validator.html.AntiSamy;
-import org.owasp.validator.html.Policy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy;
 
 /**
  * Class that provides the capability of securing input provided as plain text for HTML output.
  */
 public class PolicyHandler {
 
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final Policy policy;
-    private Policy fallbackPolicy;
-    private AntiSamy antiSamy;
-    private AntiSamy fallbackAntiSamy;
+    private final AntiSamyPolicy policy;
+    private AntiSamyPolicy fallbackPolicy;
+    private HtmlSanitizer htmlSanitizer;
+    private HtmlSanitizer fallbackHtmlSanitizer;
 
     /**
      * Creates a {@code PolicyHandler} from an {@link InputStream}.
      *
-     * @param policyStream the InputStream from which to read this handler's {@link Policy}
+     * @param policyStream the InputStream from which to read this handler's {@link AntiSamyPolicy}
      */
     public PolicyHandler(InputStream policyStream) throws Exception {
-        // fix for classloader issue with IBM JVM: see bug #31946
-        // (currently: http://bugs.day.com/bugzilla/show_bug.cgi?id=31946)
-        Thread currentThread = Thread.currentThread();
-        ClassLoader cl = currentThread.getContextClassLoader();
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
-            currentThread.setContextClassLoader(this.getClass().getClassLoader());
             IOUtils.copy(policyStream, baos);
             ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-            currentThread.setContextClassLoader(this.getClass().getClassLoader());
-            this.policy = Policy.getInstance(bais);
-            if ( "true".equals(this.policy.getDirective(Policy.EMBED_STYLESHEETS)) ) {
-                logger.warn("The AntiSamy directive {} is set to true. This directive is deprecated and will not be supported in future Sling XSS bundle releases", Policy.EMBED_STYLESHEETS);
-            }
+            this.policy = new AntiSamyPolicy(bais);
             bais.reset();
+            this.htmlSanitizer = new HtmlSanitizer(this.policy);
             this.fallbackPolicy = new FallbackSlingPolicy(bais);
-            this.antiSamy = new AntiSamy(this.policy);
-            this.fallbackAntiSamy = new AntiSamy(this.fallbackPolicy);
-        } finally {
-            currentThread.setContextClassLoader(cl);
+            this.fallbackHtmlSanitizer = new HtmlSanitizer(this.fallbackPolicy);
         }
     }
 
-    public Policy getPolicy() {
+    public AntiSamyPolicy getPolicy() {
         return this.policy;
     }
 
-    public AntiSamy getAntiSamy() {
-        return this.antiSamy;
+    public HtmlSanitizer getHtmlSanitizer() {
+        return this.htmlSanitizer;
     }
 
-    public AntiSamy getFallbackAntiSamy() {
-        return fallbackAntiSamy;
+    public HtmlSanitizer getFallbackHtmlSanitizer() {
+        return fallbackHtmlSanitizer;
     }
 }
diff --git a/src/main/java/org/apache/sling/xss/impl/SanitizedResult.java b/src/main/java/org/apache/sling/xss/impl/SanitizedResult.java
new file mode 100644
index 0000000..561fee4
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/SanitizedResult.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or
+ * more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the
+ * Apache License, Version 2.0 (the "License"); you may not use
+ * this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+ * applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ ******************************************************************************/
+package org.apache.sling.xss.impl;
+
+public class SanitizedResult {
+    private String sanitizedString;
+    private int numberOfErrors;
+
+    public SanitizedResult(String sanitizedString, int numberOfErrors) {
+        this.sanitizedString = sanitizedString;
+        this.numberOfErrors = numberOfErrors;
+    }
+
+    public String getSanitizedString() {
+        return sanitizedString;
+    }
+
+    public int getNumberOfErrors() {
+        return numberOfErrors;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java b/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
index 68ed5c9..f5d5e88 100644
--- a/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
+++ b/src/main/java/org/apache/sling/xss/impl/XSSAPIImpl.java
@@ -67,9 +67,6 @@ public class XSSAPIImpl implements XSSAPI {
 
     @Activate
     protected void activate() {
-        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
-        try {
-            Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
             factory = SAXParserFactory.newInstance();
             factory.setValidating(false);
             factory.setNamespaceAware(true);
@@ -83,9 +80,6 @@ public class XSSAPIImpl implements XSSAPI {
             Map<String, Object> config = new HashMap<>();
             config.put("org.apache.johnzon.supports-comments", true);
             jsonReaderFactory = Json.createReaderFactory(config);
-        } finally {
-            Thread.currentThread().setContextClassLoader(tccl);
-        }
     }
 
     @Deactivate
@@ -186,12 +180,12 @@ public class XSSAPIImpl implements XSSAPI {
             // Percent-encode characters that are not allowed in unquoted
             // HTML attributes: ", ', >, <, ` and space. We don't encode =
             // since this would break links with query parameters.
-            String encodedUrl = url.replaceAll("\"", "%22")
-                    .replaceAll("'", "%27")
-                    .replaceAll(">", "%3E")
-                    .replaceAll("<", "%3C")
-                    .replaceAll("`", "%60")
-                    .replaceAll(" ", "%20");
+            String encodedUrl = url.replace("\"", "%22")
+                    .replace("'", "%27")
+                    .replace(">", "%3E")
+                    .replace("<", "%3C")
+                    .replace("`", "%60")
+                    .replace(" ", "%20");
             try {
                 if (xssFilter.isValidHref(encodedUrl)) {
                     return encodedUrl;
diff --git a/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java b/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
index cadcd75..ade0fe8 100644
--- a/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
+++ b/src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
@@ -42,6 +42,9 @@ import org.apache.sling.serviceusermapping.ServiceUserMapped;
 import org.apache.sling.xss.ProtectionContext;
 import org.apache.sling.xss.XSSFilter;
 import org.apache.sling.xss.impl.status.XSSStatusService;
+import org.apache.sling.xss.impl.xml.Attribute;
+import org.apache.sling.xss.impl.xml.Regexp;
+import org.apache.sling.xss.impl.xml.Tag;
 import org.jetbrains.annotations.NotNull;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentContext;
@@ -53,8 +56,6 @@ import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
-import org.owasp.validator.html.model.Attribute;
-import org.owasp.validator.html.model.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -136,7 +137,13 @@ public class XSSFilterImpl implements XSSFilter {
     static final Pattern OFF_SITE_SIMPLIFIED = Pattern.compile("(\\s)*((ht|f)tp(s?)://|mailto:)" +
             "[\\p{L}\\p{N}]+[\\p{L}\\p{N}\\p{Zs}\\.\\#@\\$%\\+&amp;;:\\-_~,\\?=/!\\*\\(\\)]*(\\s)*");
 
-    private static final Pattern[] BACKUP_PATTERNS = new Pattern[] {ON_SITE_SIMPLIFIED, OFF_SITE_SIMPLIFIED};
+    static final Attribute FALLBACK_HREF_ATTRIBUTE = new Attribute(
+            "href",
+            Arrays.asList(
+                    new Regexp("on-site-simplified", ON_SITE_SIMPLIFIED.toString()),
+                    new Regexp("off-site-simplified", OFF_SITE_SIMPLIFIED.toString())),
+            Collections.emptyList(),
+            AntiSamyActions.REMOVE_ATTRIBUTE_ON_INVALID, null);
 
     /*
       NumericEntityEscaper is deprecated starting with version 3.6 of commons-lang3, however the indicated replacement comes from
@@ -148,12 +155,10 @@ public class XSSFilterImpl implements XSSFilter {
     static final Attribute DEFAULT_HREF_ATTRIBUTE = new Attribute(
             "href",
             Arrays.asList(
-                    Pattern.compile(RELATIVE_REF),
-                    Pattern.compile(URI)
-            ),
-            Collections.emptyList(),
-            "removeAttribute", ""
-    );
+                    new Regexp("relative-ref", RELATIVE_REF),
+                    new Regexp("uri", URI)),
+            null,
+            AntiSamyActions.REMOVE_ATTRIBUTE_ON_INVALID, null);
 
     static final String DEFAULT_POLICY_PATH = "sling/xss/config.xml";
     static final String EMBEDDED_POLICY_PATH = "SLING-INF/content/config.xml";
@@ -229,18 +234,17 @@ public class XSSFilterImpl implements XSSFilter {
 
     private boolean runHrefValidation(@NotNull String url) {
         // Same logic as in org.owasp.validator.html.scan.MagicSAXFilter.startElement()
-        boolean isValid = hrefAttribute.containsAllowedValue(url.toLowerCase());
+        String urlLowerCase = url.toLowerCase();
+        boolean isValid = hrefAttribute.containsAllowedValue(urlLowerCase);
         if (!isValid) {
             try {
-                isValid = hrefAttribute.matchesAllowedExpression(url.toLowerCase());
+                isValid = hrefAttribute.matchesAllowedExpression(urlLowerCase);
             } catch (StackOverflowError e) {
                 logger.debug("Detected a StackOverflowError when validating url {} with configured regexes. Trying fallback.", url);
                 try {
-                    for (Pattern p : BACKUP_PATTERNS) {
-                        isValid = p.matcher(url.toLowerCase()).matches();
-                        if (isValid) {
-                            break;
-                        }
+                    isValid = FALLBACK_HREF_ATTRIBUTE.containsAllowedValue(urlLowerCase);
+                    if (!isValid) {
+                        isValid = FALLBACK_HREF_ATTRIBUTE.matchesAllowedExpression(urlLowerCase);
                     }
                 } catch (StackOverflowError inner) {
                     logger.debug("Detected a StackOverflowError when validating url {} with fallback regexes", url);
@@ -279,30 +283,26 @@ public class XSSFilterImpl implements XSSFilter {
         }
     }
 
-    private synchronized void updatePolicy() {
+    synchronized void updatePolicy() {
         this.policyHandler = null;
         try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
             Resource policyResource = xssResourceResolver.getResource(policyPath);
             if (policyResource != null) {
-                activePolicy = new AntiSamyPolicy(false, policyResource.getPath());
-                try (InputStream policyStream = activePolicy.read()) {
-                    setPolicyHandler(new PolicyHandler(policyStream));
-                    logger.info("Installed policy from {}.", activePolicy.path);
-                } catch (Exception e) {
-                    activePolicy = null;
-                    Throwable[] suppressed = e.getSuppressed();
-                    if (suppressed.length > 0) {
-                        for (Throwable t : suppressed) {
-                            logger.error("Unable to load policy from " + policyResource.getPath(), t);
-                        }
-                    }
-                    logger.error("Unable to load policy from " + policyResource.getPath(), e);
-                }
+                setActivePolicy(policyResource);
             }
         } catch (final LoginException e) {
             logger.error("Unable to load the default policy file.", e);
         }
         if (policyHandler == null) {
+            // the content was not installed but the service is active; let's use the embedded file for the default handler
+            setActiveEmbededPolicy();
+        }
+        if (policyHandler == null || activePolicy == null) {
+            throw new IllegalStateException("Cannot load a policy handler.");
+        }
+    }
+
+    private void setActiveEmbededPolicy() {
             // the content was not installed but the service is active; let's use the embedded file for the default handler
             logger.info("Could not find a policy file at the configured location {}. Attempting to use the default resource embedded in" +
                     " the bundle.", policyPath);
@@ -320,9 +320,23 @@ public class XSSFilterImpl implements XSSFilter {
                 }
                 logger.error("Unable to load policy from embedded policy file.", e);
             }
-        }
-        if (policyHandler == null || activePolicy == null) {
-            throw new IllegalStateException("Cannot load a policy handler.");
+        
+    }
+
+    private void setActivePolicy(Resource policyResource) {
+        activePolicy = new AntiSamyPolicy(false, policyResource.getPath());
+        try (InputStream policyStream = activePolicy.read()) {
+            setPolicyHandler(new PolicyHandler(policyStream));
+            logger.info("Installed policy from {}.", activePolicy.path);
+        } catch (Exception e) {
+            activePolicy = null;
+            Throwable[] suppressed = e.getSuppressed();
+            if (suppressed.length > 0) {
+                for (Throwable t : suppressed) {
+                    logger.error("Unable to load policy from " + policyResource.getPath(), t);
+                }
+            }
+            logger.error("Unable to load policy from " + policyResource.getPath(), e);
         }
     }
 
@@ -341,7 +355,7 @@ public class XSSFilterImpl implements XSSFilter {
     }
 
     private void setPolicyHandler(PolicyHandler policyHandler) {
-        Tag linkTag = policyHandler.getPolicy().getTagByLowercaseName("a");
+        Tag linkTag = policyHandler.getPolicy().getTagRules().get("a");
         Attribute hrefAttribute = (linkTag != null) ? linkTag.getAttributeByName("href") : null;
         if (hrefAttribute == null) {
             // Fallback to default configuration
diff --git a/src/main/java/org/apache/sling/xss/impl/status/XSSStatusService.java b/src/main/java/org/apache/sling/xss/impl/status/XSSStatusService.java
index d0e28f9..c136276 100644
--- a/src/main/java/org/apache/sling/xss/impl/status/XSSStatusService.java
+++ b/src/main/java/org/apache/sling/xss/impl/status/XSSStatusService.java
@@ -20,6 +20,7 @@ package org.apache.sling.xss.impl.status;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -53,7 +54,7 @@ public class XSSStatusService {
 
     public static final int MAX_INVALID_URLS_RECORDED = 1000;
 
-    private Map<String, AtomicInteger> invalidUrls;
+    private Map<String, AtomicInteger> invalidUrls = new HashMap<>();
 
     public void reportInvalidUrl(@NotNull String url) {
         if (invalidUrls.containsKey(url)) {
diff --git a/src/main/java/org/apache/sling/xss/impl/style/BatikCssCleaner.java b/src/main/java/org/apache/sling/xss/impl/style/BatikCssCleaner.java
new file mode 100644
index 0000000..2623723
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/style/BatikCssCleaner.java
@@ -0,0 +1,82 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.style;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.apache.batik.css.parser.Parser;
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy.CssPolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.InputSource;
+
+public class BatikCssCleaner {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private final CssPolicy cssPolicy;
+
+    private static final String CDATA_PRE = "<![CDATA[";
+    private static final String CDATA_POST = "]]>";
+
+    public BatikCssCleaner(CssPolicy cssPolicy) {
+        this.cssPolicy = cssPolicy;
+    }
+
+    /**
+     * Parses a CSS stylesheet and returns it in a safe form
+     *
+     * @param untrustedCss a complete CSS stylesheet
+     * @return the cleaned CSS stylesheet text
+     */
+    public String cleanStylesheet(String untrustedCss) {
+        try {
+            if ( untrustedCss.startsWith(CDATA_PRE) && untrustedCss.endsWith(CDATA_POST) )
+                untrustedCss = untrustedCss.substring(CDATA_PRE.length(), untrustedCss.length() - CDATA_POST.length());
+            Parser parser = new Parser();
+            ValidatingDocumentHandler handler = new ValidatingDocumentHandler(cssPolicy, false);
+            parser.setDocumentHandler(handler);
+            parser.parseStyleSheet(new InputSource(new StringReader(untrustedCss)));
+            return handler.getValidCss();
+        } catch (CSSException | IOException e) {
+            logger.warn("Unexpected error while cleaning stylesheet", e);
+            return "";
+        }
+    }
+
+    /**
+     * Parses a CSS style declaration (i.e. the text of a <tt>style</tt> attribute) and returns it in a safe form
+     *
+     * @param untrustedCss a css style declaration
+     * @return the cleaned CSS style declaration
+     */
+    public String cleanStyleDeclaration(String untrustedCss) {
+        try {
+            Parser parser = new Parser();
+            ValidatingDocumentHandler handler = new ValidatingDocumentHandler(cssPolicy, true);
+            parser.setDocumentHandler(handler);
+            parser.parseStyleDeclaration(new InputSource(new StringReader(untrustedCss)));
+            return handler.getValidCss();
+        } catch (CSSException | IOException e) {
+            logger.warn("Unexpected error while cleaning style declaration", e);
+            return "";
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/style/CssValidator.java b/src/main/java/org/apache/sling/xss/impl/style/CssValidator.java
new file mode 100644
index 0000000..f3db08d
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/style/CssValidator.java
@@ -0,0 +1,52 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.style;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy.CssPolicy;
+import org.owasp.html.AttributePolicy;
+import org.owasp.html.HtmlStreamEventProcessor;
+
+public class CssValidator {
+
+    public static final String STYLE_TAG_NAME = "style";
+    public static final String STYLE_ATTRIBUTE_NAME = STYLE_TAG_NAME;
+
+    private final BatikCssCleaner cssParser;
+    private final List<String> disallowedTagNames = new ArrayList<>();
+
+    public CssValidator(CssPolicy cssPolicy) {
+        cssParser = new BatikCssCleaner(cssPolicy);
+    }
+
+    public HtmlStreamEventProcessor newStyleTagProcessor() {
+        return new StyleTagProcessor(cssParser);
+    }
+
+    public AttributePolicy newCssAttributePolicy() {
+        return (String elementName, String attributeName, String value) -> cssParser.cleanStyleDeclaration(value);
+    }
+
+    public List<String> getDisallowedTagNames() {
+        return Collections.unmodifiableList(disallowedTagNames);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/style/StyleTagProcessor.java b/src/main/java/org/apache/sling/xss/impl/style/StyleTagProcessor.java
new file mode 100644
index 0000000..01c3e78
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/style/StyleTagProcessor.java
@@ -0,0 +1,79 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.style;
+
+import java.util.List;
+
+import org.owasp.html.HtmlStreamEventProcessor;
+import org.owasp.html.HtmlStreamEventReceiver;
+
+class StyleTagProcessor implements HtmlStreamEventProcessor {
+
+    private final BatikCssCleaner cssCleaner;
+
+    StyleTagProcessor(BatikCssCleaner cssCleaner) {
+        this.cssCleaner = cssCleaner;
+    }
+
+    @Override
+    public HtmlStreamEventReceiver wrap(HtmlStreamEventReceiver sink) {
+        return new StyleTagReceiver(sink);
+    }
+
+    class StyleTagReceiver implements HtmlStreamEventReceiver {
+
+        private final HtmlStreamEventReceiver wrapped;
+        private boolean inStyleTag;
+
+        StyleTagReceiver(HtmlStreamEventReceiver wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        @Override
+        public void openDocument() {
+            wrapped.openDocument();
+        }
+
+        @Override
+        public void closeDocument() {
+            wrapped.closeDocument();
+        }
+
+        @Override
+        public void openTag(String elementName, List<String> attrs) {
+            wrapped.openTag(elementName, attrs);
+            inStyleTag = CssValidator.STYLE_TAG_NAME.equals(elementName);
+        }
+
+        @Override
+        public void closeTag(String elementName) {
+            wrapped.closeTag(elementName);
+            inStyleTag = false;
+        }
+
+        @Override
+        public void text(String taintedCss) {
+            if (inStyleTag ) {
+                wrapped.text(cssCleaner.cleanStylesheet(taintedCss));
+            } else {
+                wrapped.text(taintedCss);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/style/ValidatingDocumentHandler.java b/src/main/java/org/apache/sling/xss/impl/style/ValidatingDocumentHandler.java
new file mode 100644
index 0000000..ec506b8
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/style/ValidatingDocumentHandler.java
@@ -0,0 +1,342 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.style;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy.CssPolicy;
+import org.apache.sling.xss.impl.xml.Property;
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.NegativeCondition;
+import org.w3c.css.sac.SACMediaList;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SelectorList;
+import org.w3c.css.sac.SiblingSelector;
+
+public class ValidatingDocumentHandler implements DocumentHandler {
+
+    private final CssPolicy cssPolicy;
+    private final StringBuilder cleanCss = new StringBuilder();
+    private final boolean isInLine;
+
+    private boolean isInSelector;
+
+    public ValidatingDocumentHandler(CssPolicy cssPolicy, boolean isInLine) {
+        this.cssPolicy = cssPolicy;
+        this.isInLine = isInLine;
+    }
+
+    @Override
+    public void startSelector(SelectorList selectors) throws CSSException {
+
+        List<String> validSelectors = validateSelectors(selectors);
+        if ( validSelectors.isEmpty() )
+            return;
+
+        StringJoiner joiner = new StringJoiner(", ", "", " {\n");
+        validSelectors.forEach( joiner::add );
+        cleanCss.append(joiner.toString());
+        isInSelector = true;
+    }
+
+    @Override
+    public void endSelector(SelectorList selectors) throws CSSException {
+        if ( !isInSelector )
+            return;
+
+        cleanCss.append("}\n");
+        isInSelector = false;
+    }
+
+    @Override
+    public void property(String name, LexicalUnit value, boolean important) throws CSSException {
+        if (!isInSelector && !isInLine) {
+            return;
+        }
+
+        List<String> validPropertyValues = validatePropertyValues(name, value);
+        if ( validPropertyValues.isEmpty() )
+            return;
+
+        cleanCss.append(validPropertyValues.stream()
+            .map( s -> important  ? s + " !important" : s)
+            .collect(Collectors.joining(" ", "\t" + name + ": ", ";\n")));
+    }
+
+    private List<String> validateSelectors(SelectorList selectors) {
+        List<String> selectorNames = new ArrayList<>();
+        for ( int i = 0 ; i < selectors.getLength(); i++ ) {
+            Selector selector = selectors.item(i);
+            if ( isValidSelector(selector) )
+                selectorNames.add(selector.toString());
+        }
+        return selectorNames;
+    }
+
+    private boolean isValidSelector(Selector selector) {
+        switch ( selector.getSelectorType() ) {
+        case Selector.SAC_ANY_NODE_SELECTOR:
+        case Selector.SAC_ELEMENT_NODE_SELECTOR:
+        case Selector.SAC_PSEUDO_ELEMENT_SELECTOR:
+        case Selector.SAC_ROOT_NODE_SELECTOR:
+        case Selector.SAC_NEGATIVE_SELECTOR:
+            return cssPolicy.isValidElementName(selector.toString().toLowerCase(Locale.ENGLISH));
+        case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+            SiblingSelector sibling = (SiblingSelector) selector;
+            return isValidSelector(sibling.getSiblingSelector()) && isValidSelector(sibling.getSelector());
+        case Selector.SAC_CONDITIONAL_SELECTOR:
+            ConditionalSelector conditional = (ConditionalSelector) selector;
+            return isValidSelector(conditional.getSimpleSelector()) && isValidCondition(conditional.getCondition());
+        case Selector.SAC_CHILD_SELECTOR:
+        case Selector.SAC_DESCENDANT_SELECTOR:
+            DescendantSelector descendant = (DescendantSelector) selector;
+            return isValidSelector(descendant.getAncestorSelector()) && isValidSelector(descendant.getSimpleSelector());
+            default:
+                return false;
+        }
+    }
+
+    private boolean isValidCondition(Condition condition) {
+
+        switch (condition.getConditionType()) {
+        case Condition.SAC_CLASS_CONDITION:
+            return cssPolicy.isValidClassName(condition.toString().toLowerCase(Locale.ENGLISH));
+        case Condition.SAC_ID_CONDITION:
+            return cssPolicy.isValidId(condition.toString().toLowerCase(Locale.ENGLISH));
+        case Condition.SAC_AND_CONDITION:
+        case Condition.SAC_OR_CONDITION:
+            CombinatorCondition comb = (CombinatorCondition) condition;
+            return isValidCondition(comb.getFirstCondition()) && isValidCondition(comb.getSecondCondition());
+        case Condition.SAC_NEGATIVE_CONDITION:
+            return isValidCondition(((NegativeCondition) condition).getCondition());
+        case Condition.SAC_PSEUDO_CLASS_CONDITION:
+            return cssPolicy.isValidPseudoElementName(condition.toString().toLowerCase(Locale.ENGLISH));
+        case Condition.SAC_ATTRIBUTE_CONDITION:
+        case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION:
+        case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+            return false;
+        case Condition.SAC_ONLY_CHILD_CONDITION:
+        case Condition.SAC_ONLY_TYPE_CONDITION:
+            // constant values, unconditionally true
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private List<String> validatePropertyValues(String name, LexicalUnit value) {
+        List<String> validPropertyValues = new ArrayList<>();
+        while ( value != null ) {
+            String stringValue = lexicalValueToString(value);
+            value = value.getNextLexicalUnit();
+            boolean isValid = validateProperty(name, stringValue);
+            if ( !isValid )
+                continue;
+            validPropertyValues.add(stringValue);
+        }
+        return validPropertyValues;
+    }
+
+    public String getValidCss() {
+        return cleanCss.toString();
+    }
+
+    private boolean validateProperty(String name, String lexicalValueToString) {
+        if ( lexicalValueToString == null )
+            return false;
+
+        Property property = cssPolicy.getCssRules().get(name);
+        if ( property == null )
+            return false;
+
+        if ( property.getLiterals().contains(lexicalValueToString) )
+            return true;
+
+        if ( property.getRegexps().stream()
+            .anyMatch( p -> p.matcher(lexicalValueToString).matches() ) )
+            return true;
+
+        if ( property.getShorthands().stream()
+            .anyMatch( s -> validateProperty(s, lexicalValueToString)) )
+            return true;
+
+        return false;
+    }
+
+    private String lexicalValueToString(LexicalUnit lu) {
+        switch (lu.getLexicalUnitType()) {
+        case LexicalUnit.SAC_PERCENTAGE:
+        case LexicalUnit.SAC_DIMENSION:
+        case LexicalUnit.SAC_EM:
+        case LexicalUnit.SAC_EX:
+        case LexicalUnit.SAC_PIXEL:
+        case LexicalUnit.SAC_INCH:
+        case LexicalUnit.SAC_CENTIMETER:
+        case LexicalUnit.SAC_MILLIMETER:
+        case LexicalUnit.SAC_POINT:
+        case LexicalUnit.SAC_PICA:
+        case LexicalUnit.SAC_DEGREE:
+        case LexicalUnit.SAC_GRADIAN:
+        case LexicalUnit.SAC_RADIAN:
+        case LexicalUnit.SAC_MILLISECOND:
+        case LexicalUnit.SAC_SECOND:
+        case LexicalUnit.SAC_HERTZ:
+        case LexicalUnit.SAC_KILOHERTZ:
+            // various measurements
+            return lu.getFloatValue() + lu.getDimensionUnitText();
+        case LexicalUnit.SAC_INTEGER:
+            // number
+            return String.valueOf(lu.getIntegerValue());
+        case LexicalUnit.SAC_REAL:
+            // number
+            return String.valueOf(lu.getFloatValue());
+        case LexicalUnit.SAC_STRING_VALUE:
+        case LexicalUnit.SAC_IDENT:
+            // identifier, potentially needs quoting
+            String stringValue = lu.getStringValue();
+            if (stringValue.indexOf(" ") != -1)
+                stringValue = "\"" + stringValue + "\"";
+            return stringValue;
+        case LexicalUnit.SAC_URI:
+            // this is a URL
+            return "url(" + lu.getStringValue() + ")";
+        case LexicalUnit.SAC_RGBCOLOR:
+            // this is a rgb encoded color; technically we don't need to encode
+            // it precisely like this but it makes it simpler to keep the tests
+            // based on the AntiSamy implementation
+            return toRgbString(lu);
+        case LexicalUnit.SAC_INHERIT:
+            // constant
+            return "inherit";
+        case LexicalUnit.SAC_OPERATOR_COMMA:
+            return ",";
+        case LexicalUnit.SAC_ATTR:
+        case LexicalUnit.SAC_COUNTER_FUNCTION:
+        case LexicalUnit.SAC_COUNTERS_FUNCTION:
+        case LexicalUnit.SAC_FUNCTION:
+        case LexicalUnit.SAC_RECT_FUNCTION:
+        case LexicalUnit.SAC_SUB_EXPRESSION:
+        case LexicalUnit.SAC_UNICODERANGE:
+        default:
+            // unsupported
+            return null;
+        }
+    }
+
+    private String toRgbString(LexicalUnit lu) {
+        // 16 default capacity actually fits nicely
+        //
+        // rgb(255,255,255)
+        // ....|....|....|.
+        StringBuilder sb = new StringBuilder();
+        LexicalUnit param = lu.getParameters();
+        sb.append("rgb(");
+        sb.append(param.getIntegerValue()); // R value
+        sb.append(',');
+        param = param.getNextLexicalUnit(); // comma
+        param = param.getNextLexicalUnit(); // G value
+        sb.append(param.getIntegerValue());
+        sb.append(',');
+        param = param.getNextLexicalUnit(); // comma
+        param = param.getNextLexicalUnit(); // B value
+        sb.append(param.getIntegerValue());
+        sb.append(')');
+
+        return sb.toString();
+    }
+
+    @Override
+    public void importStyle(String uri, SACMediaList media, String defaultNamespaceURI) throws CSSException {
+        // embedded stylesheets are not supported
+    }
+
+    @Override
+    public void startDocument(InputSource source) throws CSSException {
+        // nothing to do
+    }
+
+    @Override
+    public void endDocument(InputSource source) throws CSSException {
+        // nothing to do
+    }
+
+    @Override
+    public void comment(String text) throws CSSException {
+        // we intentionally ignore comments, they don't need to be in the output
+    }
+
+    @Override
+    public void ignorableAtRule(String atRule) throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void namespaceDeclaration(String prefix, String uri) throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void startMedia(SACMediaList media) throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void endMedia(SACMediaList media) throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void startPage(String name, String pseudo_page) throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void endPage(String name, String pseudo_page) throws CSSException {
+        // nothing to do
+    }
+
+    @Override
+    public void startFontFace() throws CSSException {
+        // nothing to do
+
+    }
+
+    @Override
+    public void endFontFace() throws CSSException {
+        // nothing to do
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/AllowedEmptyTags.java b/src/main/java/org/apache/sling/xss/impl/xml/AllowedEmptyTags.java
new file mode 100644
index 0000000..15e9441
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/AllowedEmptyTags.java
@@ -0,0 +1,44 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class AllowedEmptyTags {
+
+    @JacksonXmlElementWrapper(localName = "literal-list")
+    @JacksonXmlProperty(localName = "literal")
+    private List<Literal> allowedEmptyTagsList = Collections.emptyList();
+
+    public List<Literal> getLiteralList() {
+        return allowedEmptyTagsList;
+    }
+
+    public List<String> getLiterals() {
+        // reads out the literals and creates a list out of it
+        return allowedEmptyTagsList.stream().map(Literal::getValue)
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyPolicy.java b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyPolicy.java
new file mode 100644
index 0000000..da1c91a
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyPolicy.java
@@ -0,0 +1,141 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.xml.stream.XMLStreamException;
+
+import org.apache.sling.xss.impl.InvalidConfigException;
+
+public class AntiSamyPolicy {
+
+    protected final Map<String, Pattern> commonRegularExpressions = new HashMap<>();
+    protected final Map<String, Attribute> commonAttributes = new HashMap<>();
+    protected final Map<String, Tag> tagRules = new HashMap<>();
+    protected final Map<String, Property> cssRules = new HashMap<>();
+    protected final Map<String, String> directives = new HashMap<>();
+    protected final Map<String, Attribute> globalAttributes = new HashMap<>();
+    protected final Map<String, Attribute> dynamicAttributes = new HashMap<>();
+    protected final List<String> requireClosingTags = new ArrayList<>();
+    protected List<String> allowedEmptyTags = new ArrayList<>();
+
+    public AntiSamyPolicy(InputStream input) throws InvalidConfigException, XMLStreamException, IOException {
+        AntiSamyXmlParser xmlParser = new AntiSamyXmlParser();
+        MapBuilder mapBuilder = new MapBuilder();
+        AntiSamyRules root = xmlParser.createRules(input);
+        mapBuilder.createRulesMap(this, root);
+    }
+
+    public Map<String, String> getDirectives() {
+        return directives;
+    }
+
+    public List<String> getRequireClosingTags() {
+        return requireClosingTags;
+    }
+
+    public Map<String, Pattern> getCommonRegularExpressions() {
+        return commonRegularExpressions;
+    }
+
+    public Map<String, Attribute> getGlobalAttributes() {
+        return globalAttributes;
+    }
+
+    public Map<String, Attribute> getCommonAttributes() {
+        return commonAttributes;
+    }
+
+    public Map<String, Property> getCssRules() {
+        return cssRules;
+    }
+
+    public List<String> getAllowedEmptyTags() {
+        return allowedEmptyTags;
+    }
+
+    public Map<String, Tag> getTagRules() {
+        return tagRules;
+    }
+
+    public Map<String, Attribute> getDynamicAttributes() {
+        return dynamicAttributes;
+    }
+
+    public CssPolicy getCssPolicy() {
+        return new CssPolicy(cssRules,
+                commonRegularExpressions);
+    }
+
+    public static class CssPolicy {
+
+        private final Map<String, Property> cssRules;
+        private final IncludeExcludeMatcher elementMatcher;
+        private final IncludeExcludeMatcher classMatcher;
+        private final IncludeExcludeMatcher idMatcher;
+        private final IncludeExcludeMatcher pseudoElementMatcher;
+        private final IncludeExcludeMatcher attributeMatcher;
+
+        public CssPolicy(Map<String, Property> cssrules, Map<String, Pattern> commonRegExps) {
+            this.cssRules = Collections.unmodifiableMap(cssrules);
+            this.elementMatcher = new IncludeExcludeMatcher(commonRegExps.get("cssElementSelector"),
+                    commonRegExps.get("cssElementExclusion"));
+            this.classMatcher = new IncludeExcludeMatcher(commonRegExps.get("cssClassSelector"),
+                    commonRegExps.get("cssClassExclusion"));
+            this.idMatcher = new IncludeExcludeMatcher(commonRegExps.get("cssIDSelector"),
+                    commonRegExps.get("cssIDExclusion"));
+            this.pseudoElementMatcher = new IncludeExcludeMatcher(commonRegExps.get("cssPseudoElementSelector"),
+                    commonRegExps.get("cssPseudoElementExclusion"));
+            this.attributeMatcher = new IncludeExcludeMatcher(commonRegExps.get("cssAttributeSelector"),
+                    commonRegExps.get("cssAttributeExclusion"));
+        }
+
+        public Map<String, Property> getCssRules() {
+            return cssRules;
+        }
+
+        public boolean isValidElementName(String name) {
+            return elementMatcher.matches(name);
+        }
+
+        public boolean isValidClassName(String name) {
+            return classMatcher.matches(name);
+        }
+
+        public boolean isValidId(String name) {
+            return idMatcher.matches(name);
+        }
+
+        public boolean isValidPseudoElementName(String name) {
+            return pseudoElementMatcher.matches(name);
+        }
+
+        public boolean isValidAttributeSelector(String name) {
+            return attributeMatcher.matches(name);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyRules.java b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyRules.java
new file mode 100644
index 0000000..3e830a7
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyRules.java
@@ -0,0 +1,127 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+@JacksonXmlRootElement(localName = "anti-samy-rules")
+public class AntiSamyRules {
+    @JsonIgnore
+    @JacksonXmlProperty(localName = "xmlns:xsi", isAttribute = true)
+    private String xmlnsXsi;
+
+    @JsonIgnore
+    @JacksonXmlProperty(localName = "noNamespaceSchemaLocation", isAttribute = true)
+    private String noNamespaceSchemaLocation;
+
+    @JacksonXmlElementWrapper(localName = "directives")
+    @JacksonXmlProperty(localName = "directive")
+    private List<Directive> directiveList = Collections.emptyList();
+
+    @JacksonXmlElementWrapper(localName = "common-regexps")
+    @JacksonXmlProperty(localName = "regexp")
+    private List<Regexp> regexpList = Collections.emptyList();
+
+    @JacksonXmlElementWrapper(localName = "common-attributes")
+    @JacksonXmlProperty(localName = "attribute")
+    private List<Attribute> commonAttributeList = Collections.emptyList();
+
+    @JacksonXmlProperty(localName = "global-tag-attributes")
+    private GlobalTagAttributes globalTagAttributes;
+
+    @JacksonXmlProperty(localName = "dynamic-tag-attributes")
+    private DynamicTagAttributes dynamicTagAttribute;
+
+    @JacksonXmlElementWrapper(localName = "tag-rules")
+    @JacksonXmlProperty(localName = "tag")
+    private List<Tag> tagRulesList = Collections.emptyList();
+
+    @JacksonXmlProperty(localName = "tags-to-encode")
+    private TagsToEncode tagsToEncode;
+
+    @JacksonXmlElementWrapper(localName = "css-rules")
+    @JacksonXmlProperty(localName = "property")
+    private List<Property> propertyList = Collections.emptyList();
+
+    @JacksonXmlProperty(localName = "allowed-empty-tags")
+    private AllowedEmptyTags allowedEmptyTags;
+
+    public AllowedEmptyTags getAllowedEmptyTags() {
+        return allowedEmptyTags;
+    }
+
+    public DynamicTagAttributes getDynamicTagAttribute() {
+        return dynamicTagAttribute;
+    }
+
+    public GlobalTagAttributes getGlobalTagAttributes() {
+        return globalTagAttributes;
+    }
+
+    public String getNoNamespaceSchemaLocation() {
+        return noNamespaceSchemaLocation;
+    }
+
+    public TagsToEncode getTagsToEncode() {
+        return tagsToEncode;
+    }
+
+    public String getXmlnsXsi() {
+        return xmlnsXsi;
+    }
+
+    public List<Attribute> getCommonAttributeList() {
+        return commonAttributeList;
+    }
+
+    public List<Directive> getDirectiveList() {
+        return directiveList;
+    }
+
+    public List<Regexp> getRegexpList() {
+        return regexpList;
+    }
+
+    public List<Property> getPropertyList() {
+        return propertyList;
+    }
+
+    public List<Tag> getTagRulesList() {
+        return tagRulesList;
+    }
+
+    public Map<String, String> getDirectivesByName() {
+        return directiveList.stream()
+                .collect(Collectors.toMap(Directive::getName, Directive::getValue));
+    }
+
+    public Map<String, Pattern> getCommonPatternByName() {
+        return regexpList.stream()
+                .collect(Collectors.toMap(Regexp::getName, Regexp::getPattern));
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyXmlParser.java b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyXmlParser.java
new file mode 100644
index 0000000..e871121
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/AntiSamyXmlParser.java
@@ -0,0 +1,56 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+
+public class AntiSamyXmlParser {
+
+    private static final String DIRECTIVE_EMBED_STYLE_SHEETS = "embedStyleSheets";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    public AntiSamyRules createRules(InputStream input) throws XMLStreamException, IOException {
+
+        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+        // disable external entities declarations
+        xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
+
+        XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(input);
+        XmlMapper mapper = new XmlMapper(xmlInputFactory, XMLOutputFactory.newInstance());
+        AntiSamyRules rules = mapper.readValue(xmlStreamReader, AntiSamyRules.class);
+        if ("true".equals(rules.getDirectivesByName().get(DIRECTIVE_EMBED_STYLE_SHEETS))) {
+            logger.warn("Unsupported configuration directive {} is set to true and will be ignored",
+                    DIRECTIVE_EMBED_STYLE_SHEETS);
+        }
+        xmlStreamReader.close();
+        return rules;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Attribute.java b/src/main/java/org/apache/sling/xss/impl/xml/Attribute.java
new file mode 100644
index 0000000..c1a14eb
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Attribute.java
@@ -0,0 +1,139 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.sling.xss.impl.AntiSamyActions;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.jetbrains.annotations.NotNull;
+
+public class Attribute {
+
+    @NotNull
+    private final String name;
+
+    private final String description;
+    private final String onInvalid;
+    private final List<Regexp> regexpList;
+    private final List<Literal> literalList;
+
+    @JsonCreator
+    public Attribute(@JacksonXmlProperty(localName = "name", isAttribute = true) @NotNull String name,
+            @JacksonXmlProperty(localName = "regexp-list") List<Regexp> allowedRegexps,
+            @JacksonXmlProperty(localName = "literal-list") List<Literal> literalList,
+            @JacksonXmlProperty(localName = "onInvalid", isAttribute = true) String onInvalid,
+            @JacksonXmlProperty(localName = "description", isAttribute = true) String description) {
+        this.name = name;
+        this.description = Optional.ofNullable(description).orElse("");
+        this.onInvalid = onInvalid != null && onInvalid.length() > 0 ? onInvalid : AntiSamyActions.REMOVE_ATTRIBUTE_ON_INVALID;
+        this.regexpList = Optional.ofNullable(allowedRegexps)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+        this.literalList = Optional.ofNullable(literalList)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+    }
+
+    @Override
+    public String toString() {
+        return "Attribute - name: " + name + ", description " + description + ", onInvalid " + onInvalid
+                + ", allowedRegexlist: "
+                + regexpList.size() + ", literals " + literalList;
+    }
+
+    @NotNull
+    public String getOnInvalid() {
+        return onInvalid;
+    }
+
+    @NotNull
+    public String getDescription() {
+        return description;
+    }
+
+    @NotNull
+    public String getName() {
+        return name;
+    }
+
+    @NotNull
+    public List<String> getLiterals() {
+        return getLiteralList().stream()
+                .map(Literal::getValue)
+                .map(String::toLowerCase)
+                .collect(Collectors.toList());
+    }
+
+    @NotNull
+    public List<Literal> getLiteralList() {
+        return literalList;
+    }
+
+    @NotNull
+    public List<Pattern> getPatternList() {
+        return getRegexpList().stream()
+                .map(Regexp::getPattern)
+                .collect(Collectors.toList());
+    }
+
+    @NotNull
+    public List<Regexp> getRegexpList() {
+        return regexpList;
+    }
+
+    public boolean containsAllowedValue(String valueInLowerCase) {
+        return getLiteralList().stream()
+                .map(Literal::getValue)
+                .anyMatch(valueInLowerCase::equals);
+    }
+
+    public boolean matchesAllowedExpression(String value) {
+        return getPatternList().stream()
+                .anyMatch(pattern -> pattern.matcher(value).matches());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+
+        if (obj instanceof Attribute) {
+            Attribute attribute = (Attribute) obj;
+            return name == attribute.name || attribute.name.equals(name)
+                    && description == attribute.description || attribute.description.equals(description)
+                            && onInvalid == attribute.onInvalid
+                    || attribute.onInvalid.equals(onInvalid)
+                            && regexpList == attribute.regexpList
+                    || attribute.regexpList.equals(regexpList)
+                            && literalList == attribute.literalList;
+
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode() + description.hashCode() + onInvalid.hashCode();
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Category.java b/src/main/java/org/apache/sling/xss/impl/xml/Category.java
new file mode 100644
index 0000000..68f55f0
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Category.java
@@ -0,0 +1,30 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Category {
+    @JacksonXmlProperty(isAttribute = true)
+    private String value;
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Directive.java b/src/main/java/org/apache/sling/xss/impl/xml/Directive.java
new file mode 100644
index 0000000..a1aafd4
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Directive.java
@@ -0,0 +1,37 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Directive {
+    @JacksonXmlProperty(isAttribute = true)
+    private String name;
+    @JacksonXmlProperty(isAttribute = true)
+    private String value;
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/DynamicTagAttributes.java b/src/main/java/org/apache/sling/xss/impl/xml/DynamicTagAttributes.java
new file mode 100644
index 0000000..33e46ec
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/DynamicTagAttributes.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class DynamicTagAttributes {
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "attribute")
+    private List<Attribute> dynamicTagAttributeList = Collections.emptyList();
+
+    public List<Attribute> getDynamicTagAttributeList() {
+        return dynamicTagAttributeList;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/GlobalTagAttributes.java b/src/main/java/org/apache/sling/xss/impl/xml/GlobalTagAttributes.java
new file mode 100644
index 0000000..5aeac49
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/GlobalTagAttributes.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class GlobalTagAttributes {
+
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "attribute")
+    private List<Attribute> globalTagAttributesList = Collections.emptyList();
+
+    public List<Attribute> getGlobalTagAttributeList() {
+        return globalTagAttributesList;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/IncludeExcludeMatcher.java b/src/main/java/org/apache/sling/xss/impl/xml/IncludeExcludeMatcher.java
new file mode 100644
index 0000000..da25d5e
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/IncludeExcludeMatcher.java
@@ -0,0 +1,41 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.regex.Pattern;
+
+class IncludeExcludeMatcher {
+    private final Pattern include;
+    private final Pattern exclude;
+
+    public IncludeExcludeMatcher(Pattern include, Pattern exclude) {
+        this.include = include;
+        this.exclude = exclude;
+    }
+
+    public boolean matches(String input) {
+        if ( !include.matcher(input).matches() )
+            return false;
+
+        if ( exclude == null )
+            return true;
+
+        return ! exclude.matcher(input).matches();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Literal.java b/src/main/java/org/apache/sling/xss/impl/xml/Literal.java
new file mode 100644
index 0000000..277569b
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Literal.java
@@ -0,0 +1,43 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Literal {
+    @JacksonXmlProperty(isAttribute = true, localName = "value")
+    private String value;
+
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Literal) {
+            return ((Literal) obj).value == value || ((Literal) obj).value.equals(value);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return value.hashCode();
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/MapBuilder.java b/src/main/java/org/apache/sling/xss/impl/xml/MapBuilder.java
new file mode 100644
index 0000000..734c363
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/MapBuilder.java
@@ -0,0 +1,246 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.sling.xss.impl.InvalidConfigException;
+
+class MapBuilder {
+
+    AntiSamyPolicy policy;
+    // Antisamy hardcodes the allowed-empty-tags default:
+    // https://github.com/nahsra/antisamy/blob/main/src/main/java/org/owasp/validator/html/scan/Constants.java#L37
+    private static final List<String> ALLOWED_EMPTY_TAGS = Arrays.asList(
+            "br",
+            "hr",
+            "a",
+            "img",
+            "link",
+            "iframe",
+            "script",
+            "object",
+            "applet",
+            "frame",
+            "base",
+            "param",
+            "meta",
+            "input",
+            "textarea",
+            "embed",
+            "basefont",
+            "col");
+
+    public void createRulesMap(AntiSamyPolicy policy, AntiSamyRules topLevelElement) throws InvalidConfigException {
+        this.policy = policy;
+
+        parseCommonRegExps(topLevelElement.getRegexpList());
+        parseDirectives(topLevelElement.getDirectiveList());
+        parseAllowedEmptyTags(topLevelElement.getAllowedEmptyTags());
+        parseCommonAttributes(topLevelElement.getCommonAttributeList());
+        parseGlobalAttributes(topLevelElement.getGlobalTagAttributes().getGlobalTagAttributeList());
+        parseDynamicAttributes(topLevelElement.getDynamicTagAttribute().getDynamicTagAttributeList());
+        parseTagRules(topLevelElement.getTagRulesList());
+
+        parseCSSRules(topLevelElement.getPropertyList());
+    }
+
+    /**
+     * Go through the <common-regexps> section of the policy file.
+     *
+     * @param root                      Top level of <common-regexps>
+     */
+    private void parseCommonRegExps(List<Regexp> root) {
+        for (Regexp regex : root) {
+            String name = regex.getName();
+            Pattern regexp = Pattern.compile(regex.getValue(),
+                    Pattern.DOTALL);
+            policy.commonRegularExpressions.put(name, regexp);
+        }
+    }
+
+    /**
+     * Go through <directives> section of the policy file.
+     *
+     * @param root       Top level of <directives>
+     */
+    private void parseDirectives(List<Directive> root) {
+        for (Directive directive : root) {
+            String name = directive.getName();
+            String value = directive.getValue();
+            policy.directives.put(name, value);
+        }
+    }
+
+    private void parseCommonAttributes(List<Attribute> root) {
+        for (Attribute attribute : root) {
+            List<Regexp> allowedRegexps = getAllowedRegexps(attribute.getRegexpList());
+            Attribute newAttribute = new Attribute(attribute.getName(), allowedRegexps, attribute.getLiteralList(),
+                    attribute.getOnInvalid(), attribute.getDescription());
+            policy.commonAttributes.put(attribute.getName().toLowerCase(), newAttribute);
+        }
+    }
+
+    /**
+    * Go through <allowed-empty-tags> section of the policy file.
+    *
+    * @param allowedEmptyTagsListNode Top level of <allowed-empty-tags>
+    * @param allowedEmptyTags The tags that can be empty
+    */
+    private void parseAllowedEmptyTags(AllowedEmptyTags allowedEmptyTagsList) {
+        if (allowedEmptyTagsList != null) {
+            policy.allowedEmptyTags = allowedEmptyTagsList.getLiterals();
+        } else
+            policy.allowedEmptyTags.addAll(ALLOWED_EMPTY_TAGS);
+    }
+
+    /**
+    * Go through <global-tag-attributes> section of the policy file.
+    *
+    * @param root Top level of <global-tag-attributes>
+    * @param globalAttributes1 A HashMap of global Attributes that need
+    validation
+    * for every tag.
+    * @param commonAttributes The common attributes
+    * @throws InvalidConfigException
+    */
+    private void parseGlobalAttributes(List<Attribute> root) throws InvalidConfigException {
+        for (Attribute ele : root) {
+            String name = ele.getName();
+            Attribute toAdd = policy.commonAttributes.get(name.toLowerCase());
+
+            if (toAdd != null)
+                policy.globalAttributes.put(name.toLowerCase(), toAdd);
+            else
+                throw new InvalidConfigException("Global attribute '" + name
+                        + "' was not defined in <common-attributes>");
+        }
+    }
+
+    /**
+    * Go through <dynamic-tag-attributes> section of the policy file.
+    *
+    * @param root Top level of <dynamic-tag-attributes>
+    * @param dynamicAttributes A HashMap of dynamic Attributes that need
+    validation
+    * for every tag.
+    * @param commonAttributes The common attributes
+    * @throws InvalidConfigException
+    */
+
+    private void parseDynamicAttributes(List<Attribute> root) throws InvalidConfigException {
+        for (Attribute ele : root) {
+            String name = ele.getName();
+            Attribute toAdd = policy.getCommonAttributes().get(name.toLowerCase());
+
+            if (toAdd != null) {
+                String attrName = name.toLowerCase().substring(0, name.length() - 1);
+                policy.getDynamicAttributes().put(attrName, toAdd);
+            } else
+                throw new InvalidConfigException("Dynamic attribute '" + name
+                        + "' was not defined in <common-attributes>");
+        }
+    }
+
+    private void parseTagRules(List<Tag> root) throws InvalidConfigException {
+        if (root == null)
+            return;
+
+        for (Tag tagNode : root) {
+            String name = tagNode.getName();
+            String action = tagNode.getAction();
+
+            List<Attribute> attributeList = tagNode.getAttributeList();
+            List<Attribute> tagAttributes = getTagAttributes(attributeList, name);
+            Tag tag = new Tag(name, action, tagAttributes);
+
+            policy.tagRules.put(name.toLowerCase(), tag);
+        }
+    }
+
+    private List<Attribute> getTagAttributes(List<Attribute> attributeList, String tagName)
+            throws InvalidConfigException {
+        List<Attribute> tagAttributes = new ArrayList<>();
+
+        for (Attribute attribute : attributeList) {
+            Attribute newAttribute;
+            String attributeName = attribute.getName().toLowerCase();
+            List<Regexp> regexps = attribute.getRegexpList();
+            List<Literal> literals = attribute.getLiteralList();
+            String onInvalid = attribute.getOnInvalid();
+            String description = attribute.getDescription();
+
+            // attribute has no children
+            if (regexps.isEmpty() && literals.isEmpty()) {
+                Attribute commonAttribute = policy.commonAttributes.get(attributeName);
+                if (commonAttribute != null) {
+                    // creates a new Attribute with the fetched Attribute's information if not
+                    // available
+                    newAttribute = new Attribute(attributeName,
+                            !regexps.isEmpty() ? regexps : commonAttribute.getRegexpList(),
+                            !literals.isEmpty() ? literals : commonAttribute.getLiteralList(),
+                            !onInvalid.isEmpty() ? onInvalid : commonAttribute.getOnInvalid(),
+                            !description.isEmpty() ? description : commonAttribute.getDescription());
+                } else {
+                    throw new InvalidConfigException("Attribute '" + attributeName +
+                            "' was referenced as a common attribute in definition of '" + tagName +
+                            "', but does not exist in <common-attributes>");
+                }
+            } else {
+                List<Regexp> commonAllowedRegexps = getAllowedRegexps(regexps);
+                List<Literal> allowedValues = attribute.getLiteralList();
+                newAttribute = new Attribute(attributeName, commonAllowedRegexps, allowedValues, onInvalid,
+                        description);
+            }
+            // Add fully built attribute.
+            tagAttributes.add(newAttribute);
+        }
+        return tagAttributes;
+    }
+
+    private void parseCSSRules(List<Property> root) {
+
+        for (Property property : root) {
+            List<Regexp> allowedRegexp3 = getAllowedRegexps(property.getRegexpList());
+            Property propertyWithPatterns = new Property(property.getName(), allowedRegexp3, property.getLiteralList(),
+                    property.getShorthandList(), property.getDescription(), property.getOnInvalid(),
+                    property.getDefaultValue());
+            policy.getCssRules().put(property.getName().toLowerCase(), propertyWithPatterns);
+        }
+    }
+
+    private List<Regexp> getAllowedRegexps(List<Regexp> nameAndRegexpsList) {
+        List<Regexp> allowedRegExp = new ArrayList<>();
+        for (Regexp regExpNode : nameAndRegexpsList) {
+            String regExpName = regExpNode.getName();
+            String value = regExpNode.getValue();
+
+            if (regExpName != null && regExpName.length() > 0) {
+                allowedRegExp
+                        .add(new Regexp(regExpName, policy.getCommonRegularExpressions().get(regExpName).toString()));
+            } else if (value != null) {
+                allowedRegExp.add(new Regexp(regExpName, value));
+            }
+        }
+        return allowedRegExp;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Property.java b/src/main/java/org/apache/sling/xss/impl/xml/Property.java
new file mode 100644
index 0000000..94accb4
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Property.java
@@ -0,0 +1,125 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.sling.xss.impl.AntiSamyActions;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Property {
+    private String name;
+    private String description;
+    private String defaultValue;
+
+    @JacksonXmlElementWrapper(localName = "regexp-list")
+    private List<Regexp> regexpList;
+
+    @JacksonXmlElementWrapper(localName = "literal-list")
+    private List<Literal> literalList;
+
+    @JacksonXmlElementWrapper(localName = "category-list")
+    private List<Category> categoryList;
+
+    @JacksonXmlElementWrapper(localName = "shorthand-list")
+    private List<Shorthand> shorthandList;
+
+    private String onInvalid;
+
+    @JsonCreator
+    public Property(@JacksonXmlProperty(localName = "name", isAttribute = true) String name,
+            @JacksonXmlProperty(localName = "regexp") List<Regexp> allowedRegexps,
+            @JacksonXmlProperty(localName = "literal") List<Literal> allowedValue,
+            @JacksonXmlProperty(localName = "shorthand") List<Shorthand> shortHandRefs,
+            @JacksonXmlProperty(localName = "description", isAttribute = true) String description,
+            @JacksonXmlProperty(localName = "onInvalid", isAttribute = true) String onInvalidStr,
+            @JacksonXmlProperty(isAttribute = true, localName = "default") String defaultValue) {
+
+        this.name = name;
+        this.description = Optional.ofNullable(description).orElse("");
+        this.onInvalid = onInvalid != null && onInvalid.length() > 0 ? onInvalid : AntiSamyActions.REMOVE_ATTRIBUTE_ON_INVALID;
+        this.regexpList = Optional.ofNullable(allowedRegexps)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+        this.literalList = Optional.ofNullable(literalList)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+        this.shorthandList = Optional.ofNullable(shortHandRefs)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+        this.defaultValue = defaultValue;
+    }
+
+    public List<Category> getCategoryList() {
+        return categoryList;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public List<Literal> getLiteralList() {
+        return literalList;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<Regexp> getRegexpList() {
+        return regexpList;
+    }
+
+    public List<Shorthand> getShorthandList() {
+        return shorthandList;
+    }
+
+    public List<String> getShorthands() {
+        // reads out the shorthands and creates a list out of it
+        return shorthandList.stream().map(Shorthand::getName)
+                .collect(Collectors.toList());
+    }
+
+    public List<String> getLiterals() {
+        // reads out the literals and creates a list out of it
+        return literalList.stream().map(Literal::getValue)
+                .collect(Collectors.toList());
+    }
+
+    public String getOnInvalid() {
+        return onInvalid;
+    }
+
+    public List<Pattern> getRegexps() {
+        // reads out the patterns and creates a list out of it
+        return regexpList.stream().map(Regexp::getPattern)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Regexp.java b/src/main/java/org/apache/sling/xss/impl/xml/Regexp.java
new file mode 100644
index 0000000..2658aa4
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Regexp.java
@@ -0,0 +1,64 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Regexp {
+    private String name;
+    private String value;
+
+    @JsonCreator
+    public Regexp(@JacksonXmlProperty(localName = "name", isAttribute = true) String name,
+            @JacksonXmlProperty(localName = "value", isAttribute = true) String regexp) {
+
+        this.name = name;
+        this.value = regexp;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public Pattern getPattern() {
+        return Pattern.compile(value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Regexp) {
+            return ((Regexp) obj).name == name || ((Regexp) obj).name.equals(name)
+                    && ((Regexp) obj).value == value
+                    || ((Regexp) obj).value.equals(value);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode() + value.hashCode();
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Shorthand.java b/src/main/java/org/apache/sling/xss/impl/xml/Shorthand.java
new file mode 100644
index 0000000..bcbf731
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Shorthand.java
@@ -0,0 +1,30 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Shorthand {
+    @JacksonXmlProperty(isAttribute = true)
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/Tag.java b/src/main/java/org/apache/sling/xss/impl/xml/Tag.java
new file mode 100644
index 0000000..a3e015f
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/Tag.java
@@ -0,0 +1,85 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Tag {
+    private String name;
+    private String action;
+    private List<Attribute> attributeList;
+
+    @JsonCreator
+    public Tag(
+            @JacksonXmlProperty(isAttribute = true, localName = "name") String name,
+            @JacksonXmlProperty(isAttribute = true, localName = "action") String action,
+            @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "attribute") List<Attribute> attributeList) {
+        this.name = name.toLowerCase();
+        this.attributeList = Optional.ofNullable(attributeList)
+                .map(Collections::unmodifiableList)
+                .orElseGet(Collections::emptyList);
+        this.action = action.toLowerCase();
+
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    /**
+     * Indicates if the action for this tag matches the supplied action
+     *
+     * @param action The action to match against
+     * @return True if it matches
+     */
+    public boolean isAction(String action) {
+        return action.equals(this.action);
+    }
+
+    public Tag mutateAction(String action) {
+        return new Tag(this.name, action, this.attributeList);
+    }
+
+    public List<Attribute> getAttributeList() {
+        return attributeList;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Map<String, Attribute> getAttributeMap() {
+        return attributeList.stream()
+                .collect(Collectors.toMap(Attribute::getName, Function.identity()));
+    }
+
+    public Attribute getAttributeByName(String name) {
+        Map<String, Attribute> attributeMap = getAttributeMap();
+        return attributeMap.get(name);
+    }
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/TagRules.java b/src/main/java/org/apache/sling/xss/impl/xml/TagRules.java
new file mode 100644
index 0000000..dec49e9
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/TagRules.java
@@ -0,0 +1,30 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class TagRules {
+    @JacksonXmlProperty(localName = "tag")
+    private List<Tag> tagRulesList = Collections.emptyList();
+
+}
diff --git a/src/main/java/org/apache/sling/xss/impl/xml/TagsToEncode.java b/src/main/java/org/apache/sling/xss/impl/xml/TagsToEncode.java
new file mode 100644
index 0000000..8223a74
--- /dev/null
+++ b/src/main/java/org/apache/sling/xss/impl/xml/TagsToEncode.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.xss.impl.xml;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class TagsToEncode {
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "tag")
+    private List<String> tagToEncode = Collections.emptyList();
+
+    public List<String> getTagToEncode() {
+        return tagToEncode;
+    }
+
+}
diff --git a/src/main/java/org/owasp/html/DynamicAttributesSanitizerPolicy.java b/src/main/java/org/owasp/html/DynamicAttributesSanitizerPolicy.java
new file mode 100644
index 0000000..cd8b460
--- /dev/null
+++ b/src/main/java/org/owasp/html/DynamicAttributesSanitizerPolicy.java
@@ -0,0 +1,151 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.owasp.html;
+
+import java.lang.reflect.InvocationTargetException;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Extends the default policy to support dynamic attributes.
+ * 
+ * Since we want to support the antisamy config file, we have to make dynamic
+ * tag attributes possible.
+ * It is placed in the 'org.owasp.html' package because there are package
+ * private fields.
+ */
+public class DynamicAttributesSanitizerPolicy extends ElementAndAttributePolicyBasedSanitizerPolicy {
+
+  private Map<String, ElementAndAttributePolicies> elementAndAttrPolicies;
+  private Map<String, AttributePolicy> dynamicAttributesPolicyMap;
+  private List<String> onInvalidRemoveTagList;
+  private int numberOfErrors;
+
+  public DynamicAttributesSanitizerPolicy(HtmlStreamEventReceiver out,
+      ImmutableMap<String, ElementAndAttributePolicies> elAndAttrPolicies,
+      ImmutableSet<String> allowedTextContainers,
+      Map<String, AttributePolicy> dynamicAttributesPolicyMap, List<String> onInvalidRemoveTagList) {
+    super(out, elAndAttrPolicies, allowedTextContainers);
+    this.elementAndAttrPolicies = elAndAttrPolicies;
+    this.dynamicAttributesPolicyMap = dynamicAttributesPolicyMap;
+    this.onInvalidRemoveTagList = onInvalidRemoveTagList;
+  }
+
+  @Override
+  public void openTag(String elementName, List<String> attrs) {
+    // StylingPolicy repeats some of this code because it is more complicated
+    // to refactor it into multiple method bodies, so if you change this,
+    // check the override of it in that class.
+    if (elementName != null && attrs != null && elementAndAttrPolicies != null) {
+      ElementAndAttributePolicies policies = elementAndAttrPolicies.get(elementName);
+
+      String adjustedElementName = applyPolicies2(elementName, attrs, policies);
+      if (adjustedElementName != null
+          && !(attrs.isEmpty() && policies.htmlTagSkipType.skipAvailability())) {
+        writeOpenTag(policies, adjustedElementName, attrs);
+        return;
+      }
+      deferOpenTag(elementName);
+    }
+  }
+
+  final @Nullable String applyPolicies2(
+      String elementName, List<String> attrs,
+      ElementAndAttributePolicies policies) {
+    String adjustedElementName;
+    Boolean removeTag = false;
+    if (policies != null) {
+      for (ListIterator<String> attrsIt = attrs.listIterator(); attrsIt.hasNext();) {
+        String name = attrsIt.next();
+
+        AttributePolicy attrPolicy = null;
+        // check if the attribute name starts with an dynamic tag, to handle it specially
+        for (Entry<String, AttributePolicy> dynamicAttributeEntry : dynamicAttributesPolicyMap.entrySet()) {
+          if (name.startsWith(dynamicAttributeEntry.getKey())) {
+            attrPolicy = dynamicAttributeEntry.getValue();
+            break;
+          }
+        }
+        // if it is not an dynamic attr it gets it's normal policy
+        if (attrPolicy == null) {
+          attrPolicy = policies.attrPolicies.get(name);
+        }
+
+        // if there is no policy for this attribute, it gets removed
+        if (attrPolicy == null) {
+          numberOfErrors++;
+          attrsIt.remove();
+          attrsIt.next();
+          attrsIt.remove();
+        } else {
+          String value = attrsIt.next();
+          String adjustedValue = attrPolicy.apply(elementName, name, value);
+          if (adjustedValue == null) {
+            numberOfErrors++;
+            if (onInvalidRemoveTagList.contains(name)) {
+              removeTag = true;
+            }
+            attrsIt.remove();
+            attrsIt.previous();
+            attrsIt.remove();
+          } else {
+            attrsIt.set(adjustedValue);
+          }
+        }
+      }
+
+      try {
+        Method removeDuplicateAttributesMethod = ElementAndAttributePolicyBasedSanitizerPolicy.class
+            .getDeclaredMethod("removeDuplicateAttributes", List.class);
+        removeDuplicateAttributesMethod.setAccessible(true);
+        try {
+          removeDuplicateAttributesMethod.invoke(null, attrs);
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+          throw new RuntimeException(e);
+        }
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+
+      // checks if the onInvalid action of the invalid Tag is remove, and then removes
+      // it
+      adjustedElementName = Boolean.TRUE.equals(removeTag) ? null : policies.elPolicy.apply(elementName, attrs);
+      if (adjustedElementName != null) {
+        adjustedElementName = HtmlLexer.canonicalElementName(adjustedElementName);
+      }
+    } else {
+      numberOfErrors++;
+      adjustedElementName = null;
+    }
+    return adjustedElementName;
+  }
+
+  public int getNumberOfErrors() {
+    return numberOfErrors;
+  }
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory b/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory
deleted file mode 100644
index 0788ee2..0000000
--- a/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory
+++ /dev/null
@@ -1,18 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-# 
-#   http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-org.apache.sling.xss.impl.AttributeTranslatingTransformerFactoryImpl
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java b/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
index 65a4fd7..7b4d2d3 100644
--- a/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
+++ b/src/test/java/org/apache/sling/xss/impl/AntiSamyPolicyTest.java
@@ -21,15 +21,17 @@ package org.apache.sling.xss.impl;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import javax.xml.stream.XMLStreamException;
+
+import java.io.IOException;
 import java.util.regex.Pattern;
+
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy;
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
-import org.owasp.validator.html.AntiSamy;
-import org.owasp.validator.html.Policy;
-import org.owasp.validator.html.PolicyException;
 
 
 /**
@@ -40,32 +42,29 @@ import org.owasp.validator.html.PolicyException;
 public class AntiSamyPolicyTest {
 
     public static final String POLICY_FILE = "SLING-INF/content/config.xml";
-    private static AntiSamy antiSamy;
+    private static HtmlSanitizer antiSamy;
 
     @BeforeAll
-    public static void setup() throws PolicyException {
-        antiSamy = new AntiSamy(Policy.getInstance(AntiSamyPolicyTest.class.getClassLoader().getResourceAsStream(POLICY_FILE)));
+    public static void setup() throws InvalidConfigException, XMLStreamException, IOException {
+        antiSamy = new HtmlSanitizer(new AntiSamyPolicy(AntiSamyPolicyTest.class.getClassLoader().getResourceAsStream(POLICY_FILE)));
     }
 
     @ParameterizedTest
     @MethodSource("dataForScriptFiltering")
     public void testScriptFiltering(TestInput testInput) throws Exception {
-         testInput.skipComparingInputWithOutput = false;
-         testInput.runCheck(Mode.SAX_AND_DOM);
+         testInput.runCheck();
     }
 
     @ParameterizedTest
     @MethodSource("dataForEventHandlerAttributes")
     public void testEventHandlerAttributes(TestInput testInput) throws Exception {
-         testInput.skipComparingInputWithOutput = false;
-         testInput.runCheck(Mode.SAX_AND_DOM);
+       testInput.runCheck();
     }
 
     @ParameterizedTest
     @MethodSource("dataForImageFiltering")
     public void testImageFiltering(TestInput testInput) throws Exception {
-         testInput.skipComparingInputWithOutput = false;
-         testInput.runCheck(Mode.SAX_AND_DOM);
+         testInput.runCheck();
     }
 
     @ParameterizedTest
@@ -81,20 +80,20 @@ public class AntiSamyPolicyTest {
     @ParameterizedTest
     @MethodSource("dataForURIFiltering")
     public void testURIFiltering(TestInput testInput) throws Exception {
-         testInput.runCheck(Mode.SAX_AND_DOM);
+         testInput.runCheck();
     }
 
     @ParameterizedTest
     @MethodSource("dataForCSSFiltering")
     public void testCSSFiltering(TestInput testInput) throws Exception {
-         testInput.runCheck(Mode.SAX_AND_DOM);
+         testInput.runCheck();
     }
 
     @ParameterizedTest
     @MethodSource("dataForDataAttributes")
     public void testDataAttributes(TestInput testInput) throws Exception {
          testInput.skipComparingInputWithOutput = false;
-         testInput.runCheck(Mode.SAX);
+         testInput.runCheck();
     }
 
     /**
@@ -103,14 +102,12 @@ public class AntiSamyPolicyTest {
     @ParameterizedTest
     @MethodSource("dataForIssueSLING8771")
     public void testIssueSLING8771(TestInput testInput) throws Exception {
-         testInput.runCheck(Mode.SAX_AND_DOM);
+         testInput.runCheck();
         }
 
     private void testOutputIsEmpty(String input) throws Exception {
-        String cleanDOMModeHTML = antiSamy.scan(input, AntiSamy.DOM).getCleanHTML();
-        String cleanSAXModeHTML = antiSamy.scan(input, AntiSamy.SAX).getCleanHTML();
-        assertTrue(StringUtils.isEmpty(cleanDOMModeHTML), "Expected empty DOM filtered output for '" + input + "'.");
-        assertTrue(StringUtils.isEmpty(cleanSAXModeHTML), "Expected empty SAX filtered output for '" + input + "'.");
+         String cleanHTML = antiSamy.scan(input).getSanitizedString();
+         assertTrue(StringUtils.isEmpty(cleanHTML), "Expected empty filtered output for '" + input + "'.");
     }
 
     static TestInput[] dataForScriptFiltering() {
@@ -269,67 +266,48 @@ public class AntiSamyPolicyTest {
              this.skipComparingInputWithOutput = skipComparingInputWithOutput;
          }
  
-         void runCheck(Mode mode) throws Exception {
-             String cleanDOMModeHTML = antiSamy.scan(input, AntiSamy.DOM).getCleanHTML();
-             String cleanSAXModeHTML = antiSamy.scan(input, AntiSamy.SAX).getCleanHTML();
-             if (!skipComparingInputWithOutput) {
-                 if(pattern != null){
-                     assertTrue(pattern.matcher(input.toLowerCase()).find(), String.format("Test is not properly configured: input '%s' doesn't seem to contain '%s' (case-insensitive match).",
-                             input,expectedPartialOutput));
-                 }
-                 assertTrue(input.toLowerCase().contains(expectedPartialOutput.toLowerCase()), String.format("Test is not properly configured: input '%s' doesn't seem to contain '%s' (case-insensitive match).",
-                         input,expectedPartialOutput));
-             }
-             if (containsExpectedPartialOutput) {
-                 if (mode == Mode.DOM || mode == Mode.SAX_AND_DOM) {
-                     if(pattern != null){
-                         assertTrue(
-                                 pattern.matcher(antiSamy.scan(input, AntiSamy.DOM).getCleanHTML()).find(), String.format("Expected that DOM filtered output '%s' for input '%s' would contain '%s'.", cleanDOMModeHTML, input,
-                                         expectedPartialOutput));
-                     } else {
-                         assertTrue(
-                                 antiSamy.scan(input, AntiSamy.DOM).getCleanHTML().contains(expectedPartialOutput), String.format("Expected that DOM filtered output '%s' for input '%s' would contain '%s'.", cleanDOMModeHTML, input,
-                                         expectedPartialOutput));
-                     }
-                 }
-                 if (mode == Mode.SAX || mode == Mode.SAX_AND_DOM) {
-                     if(pattern != null){
-                         assertTrue(pattern.matcher(antiSamy.scan(input, AntiSamy.SAX).getCleanHTML()).find(), String.format("Expected that SAX filtered output '%s' for input '%s' would contain '%s'.", cleanSAXModeHTML,
-                                 input, expectedPartialOutput));
-                     } else {
-                         assertTrue(antiSamy.scan(input, AntiSamy.SAX).getCleanHTML().contains(expectedPartialOutput), String.format("Expected that SAX filtered output '%s' for input '%s' would contain '%s'.", cleanSAXModeHTML,
-                                 input, expectedPartialOutput));
-                     }
-                 }
-             } else {
-                 if (mode == Mode.DOM || mode == Mode.SAX_AND_DOM) {
-                     if(pattern != null){
-                         assertFalse(pattern.matcher(antiSamy.scan(input, AntiSamy.DOM).getCleanHTML()).find(),
-                                 String.format("Expected that DOM filtered output '%s' for input '%s', would NOT contain '%s'.", cleanDOMModeHTML,
-                                         input, expectedPartialOutput));
-                     } else {
-                         assertFalse(antiSamy.scan(input, AntiSamy.DOM).getCleanHTML().contains(expectedPartialOutput),
-                                 String.format("Expected that DOM filtered output '%s' for input '%s', would NOT contain '%s'.", cleanDOMModeHTML,
-                                         input, expectedPartialOutput));
-                     }
-                 }
-                 if (mode == Mode.SAX || mode == Mode.SAX_AND_DOM) {
-                     if(pattern != null){
-                         assertFalse(pattern.matcher(antiSamy.scan(input, AntiSamy.SAX).getCleanHTML()).find(), String.format("Expected that SAX filtered output '%s' for input '%s' would NOT contain '%s'.", cleanSAXModeHTML,
-                                 input, expectedPartialOutput));
-                     }
-                     else {
-                         assertFalse(antiSamy.scan(input, AntiSamy.SAX).getCleanHTML().contains(expectedPartialOutput), String.format("Expected that SAX filtered output '%s' for input '%s' would NOT contain '%s'.", cleanSAXModeHTML,
-                                 input, expectedPartialOutput));
-                     }
-                 }
-             }
-         }
- 
+         void runCheck() throws Exception {
+               String cleanHTML = antiSamy.scan(input).getSanitizedString();
+               if (!skipComparingInputWithOutput) {
+                       if (pattern != null) {
+                               assertTrue(pattern.matcher(input.toLowerCase()).find(), String.format(
+                                               "Test is not properly configured: input '%s' doesn't seem to partialy matcht to following pattern:'%s' (case-insensitive match).",
+                                               input, expectedPartialOutput.toString()));
+                       } else {
+                               assertTrue(input.toLowerCase().contains(expectedPartialOutput.toLowerCase()), String.format(
+                                               "Test is not properly configured: input '%s' doesn't seem to contain '%s' (case-insensitive match).",
+                                               input, expectedPartialOutput));
+                       }
+               }
+               if (containsExpectedPartialOutput) {
+                       if (pattern != null) {
+                               assertTrue(
+                                               pattern.matcher(antiSamy.scan(input).getSanitizedString()).find(),
+                                               String.format("Expected that filtered output '%s' for input '%s' would partialy match to following pattern: '%s'.",
+                                                               cleanHTML,
+                                                               input,
+                                                               expectedPartialOutput));
+                       } else {
+                               assertTrue(
+                                               antiSamy.scan(input).getSanitizedString().contains(expectedPartialOutput),
+                                               String.format("Expected that filtered output '%s' for input '%s' would contain '%s'.",
+                                                               cleanHTML,
+                                                               input,
+                                                               expectedPartialOutput));
+                       }
+               } else {
+                       if (pattern != null) {
+                               assertFalse(pattern.matcher(antiSamy.scan(input).getSanitizedString()).find(),
+                                               String.format("Expected that filtered output '%s' for input '%s', would NOT partialy match to following pattern:: '%s'.",
+                                                               cleanHTML,
+                                                               input, expectedPartialOutput));
+                       } else {
+                               assertFalse(antiSamy.scan(input).getSanitizedString().contains(expectedPartialOutput),
+                                               String.format("Expected that filtered output '%s' for input '%s', would NOT contain '%s'.",
+                                                               cleanHTML,
+                                                               input, expectedPartialOutput));
+                       }
+               }
+       }
     }
-
-    private enum Mode {
-        SAX, DOM, SAX_AND_DOM;
-    }
-}
-
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java b/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
index f93e41c..b3a2576 100644
--- a/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
+++ b/src/test/java/org/apache/sling/xss/impl/XSSAPIImplTest.java
@@ -34,6 +34,7 @@ import org.apache.sling.testing.mock.sling.junit5.SlingContext;
 import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension;
 import org.apache.sling.xss.XSSAPI;
 import org.apache.sling.xss.impl.status.XSSStatusService;
+import org.apache.sling.xss.impl.xml.Attribute;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -41,7 +42,6 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 import org.osgi.framework.ServiceReference;
-import org.owasp.validator.html.model.Attribute;
 import org.powermock.reflect.Whitebox;
 
 @ExtendWith(SlingContextExtension.class)
@@ -335,6 +335,15 @@ public class XSSAPIImplTest {
     static String[][] dataForFilterHtml() {
         return new String[][] {
                 //         Source                            Expected Result
+
+                // checking if both, the literal and the regex check are executed
+                { "<link media=\"screen\">hello</link>", "<link media=\"screen\" />hello" },
+                { "<link media=\"checkRege10\">hello</link>", "<link media=\"checkRege10\" />hello" },
+
+                { "<div align=\"center\">valid Test</div>", "<div align=\"center\">valid Test</div>" },
+                { "<style media=\"screen\">h1 {color:red;}</style>", "<style media=\"screen\">h1 {\n\tcolor: red;\n}\n</style>" },
+                { "<object id=\"center\" type=\"application/x-shockwave-flash\">valid Test</object>", "<object id=\"center\" type=\"application/x-shockwave-flash\">valid Test</object>" },
+
                 {null, ""},
                 {"", ""},
                 {"simple", "simple"},
diff --git a/src/test/java/org/apache/sling/xss/impl/XSSFilterImplTest.java b/src/test/java/org/apache/sling/xss/impl/XSSFilterImplTest.java
index ce44f8e..444501d 100644
--- a/src/test/java/org/apache/sling/xss/impl/XSSFilterImplTest.java
+++ b/src/test/java/org/apache/sling/xss/impl/XSSFilterImplTest.java
@@ -61,6 +61,21 @@ public class XSSFilterImplTest {
         return testData;
     }
 
+    static List<Object[]> dataForCheckMethod() {
+        List<Object[]> testData = new ArrayList<>();
+        testData.add(new Object[] { "<link media=\"screen\">hello</link>", true });
+        testData.add(new Object[] { "<link media=\"testingRege10\">hello</link>", true });
+        testData.add(new Object[] { "<style media=\"screen\">h1 {color:red;}</style>", true });
+        testData.add(new Object[] { "<link type=\"text/css\">valid Test</link>", true });
+        testData.add(new Object[] { "<body bgcolor=\"black\">valid Test</body>", true });
+        testData.add(new Object[] { "<div background=\"green\">invalid Test</div>", false });
+        testData.add(new Object[] { "<table border=\"3\">valid Test</table>", true });
+        testData.add(new Object[] { "<table border=\"green\">invalid Test</table>", false });
+        testData.add(new Object[] { "<script>invalid Test</script>", false });
+        testData.add(new Object[] { "", false });
+        return testData;
+    }
+
     public SlingContext context = new SlingContext();
 
     private XSSFilter xssFilter;
@@ -101,6 +116,19 @@ public class XSSFilterImplTest {
         assertEquals(XSSFilterImpl.EMBEDDED_POLICY_PATH, antiSamyPolicy.getPath(), "This is not the policy we're looking for.");
     }
 
+    @ParameterizedTest
+    @MethodSource("dataForCheckMethod")
+    public void testCheckMethod(String input, boolean isValid) {
+        context.registerInjectActivateService(new XSSFilterImpl());
+        xssFilter = context.getService(XSSFilter.class);
+        System.out.println(input);
+        if (isValid) {
+            assertTrue(xssFilter.check(XSSFilter.DEFAULT_CONTEXT, input), "Expected valid input value for: " + input);
+        } else {
+            assertFalse(xssFilter.check(XSSFilter.DEFAULT_CONTEXT, input), "Expected invalid input value for: " + input);
+        }
+    }
+
     @ParameterizedTest
     @MethodSource("dataForValidHref")
     public void isValidHref(String input, boolean isValid) {
diff --git a/src/test/java/org/apache/sling/xss/impl/xml/PolicyTest.java b/src/test/java/org/apache/sling/xss/impl/xml/PolicyTest.java
new file mode 100644
index 0000000..a512fa1
--- /dev/null
+++ b/src/test/java/org/apache/sling/xss/impl/xml/PolicyTest.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or
+ * more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the
+ * Apache License, Version 2.0 (the "License"); you may not use
+ * this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+ * applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ ******************************************************************************/
+package org.apache.sling.xss.impl.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.sling.xss.impl.InvalidConfigException;
+import org.apache.sling.xss.impl.xml.AntiSamyPolicy.CssPolicy;
+import org.junit.jupiter.api.Test;
+
+class PolicyTest {
+
+    @Test
+    void loadDefaultPolicy() throws Exception, InvalidConfigException {
+        try(InputStream input = AntiSamyPolicy.class.getClassLoader().getResourceAsStream("SLING-INF/content/config.xml")) {
+            AntiSamyPolicy policy = new AntiSamyPolicy(input);
+            Map<String, Pattern> regexp = policy.getCommonRegularExpressions();
+            List<String> empty = policy.getAllowedEmptyTags();
+            List<String> closingTag = policy.getRequireClosingTags();
+            Map<String, Attribute> global = policy.getGlobalAttributes();
+            Map<String, Attribute> dynamic = policy.getDynamicAttributes();
+            Map<String, Attribute> commonAttr = policy.getCommonAttributes();
+            Map<String, Tag> tagRules = policy.getTagRules();
+            Map<String, Property> cssRules = policy.getCssRules();
+            Map<String, String> directives = policy.getDirectives();
+
+            assertNotNull(policy);
+            Tag imgTag = policy.getTagRules().get("img");
+            assertNotNull(imgTag, "img tag rules");
+            assertEquals(9, imgTag.getAttributeList().size(), "number of known img tag attributes");
+            assertEquals(41, regexp.size(), "number of known common regexs");
+            assertEquals(19, empty.size(), "number of known allowed emty tags");
+            assertEquals(5, global.size(), "number of known global attributes");
+            assertEquals(1, dynamic.size(), "number of known dynamic attributes");
+            assertEquals(0, closingTag.size(), "number of known closing Tags");
+            assertEquals(46, commonAttr.size(), "number of known common attributes");
+            assertEquals(73, tagRules.size(), "number of known tag rules");
+            assertEquals(118, cssRules.size(), "number of known css rules");
+            assertEquals(12, directives.size(), "number of known directives");
+
+            CssPolicy cssPolicy = policy.getCssPolicy();
+
+            assertEquals(118, cssPolicy.getCssRules().size(), "cssPolicy.cssRules.size");
+            assertTrue(cssPolicy.isValidElementName("base-link"));
+            assertFalse(cssPolicy.isValidElementName("base|link"));
+        }
+    }
+}