All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlcleaner.XWikiDOMSerializer Maven / Gradle / Ivy

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.htmlcleaner;

import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;

/**
 * Generate a W3C Document from a SF's HTML Cleaner TagNode.
 *
 * Some code has been copy-pasted from SF's HTML Cleaner code (which is under a BDS license, see
 * http://htmlcleaner.sourceforge.net/license.php). Our goal is to remove this class completely if we can get SF's HTML
 * Cleaner to support the usage of a dedicated builder.
 *
 * Here's the reason why we want to be able to give a dedicated builder:
 * Note that creating the DocumentBuilder is not super fast but it's specifically more about the DocumentBuilderFactory
 * creation mainly because it's blocking all the threads which are doing stuff implying loading a class from the
 * classloader making it an important lock contention. I modified its behavior (and other similar tasks) after noticing
 * that there was often a bunch of threads waiting for this kind of lock.
 *
 * Note: Even though in a public package this code is not meant to be a public API. We've had to put in under the {@code
 * org.htmlcleaner} package because we use the following package protected API: TagNode#getNamespaceURIOnPath(String).
 *
 * @version $Id: 293e8383f2060a5d948f8e9ea96a4d9fd0b44b18 $
 * @since 1.8.2
 */
public class XWikiDOMSerializer extends DomSerializer
{
    private static final String HTML_QUALIFIED_NAME = "html";

    /**
     * @param props the HTML Cleaner properties set by the user to control the HTML cleaning.
     */
    public XWikiDOMSerializer(CleanerProperties props)
    {
        // We don't want the XML to be escaped in the produced Document.
        super(props, false);
    }

    /**
     * This method is an exact copy of {@link DomSerializer#createDocument(TagNode)} except that the {@link DocumentBuilder}
     * is given in parameter.
     * @param builder the {@link DocumentBuilder} instance to use, DocumentBuilder is not guaranteed to
     * be thread safe so at most the safe instance should be used only in the same thread
     * @param rootNode the HTML Cleaner root node to serialize
     * @return the W3C Document object
     */
    // XWikiDomSerializer copied from DomSerializer
    @SuppressWarnings({"checkstyle:CyclomaticComplexity", "checkstyle:NPathComplexity"})
    private Document createDocument(DocumentBuilder builder, TagNode rootNode)
    {
        DOMImplementation impl = builder.getDOMImplementation();

        Document document;

        //
        // Where a DOCTYPE is supplied in the input, ensure that this is in the output DOM. See issue #27
        //
        // Note that we may want to fix incorrect DOCTYPEs in future; there are some fairly
        // common patterns for errors with the older HTML4 doctypes.
        //
        if (rootNode.getDocType() != null) {
            String qualifiedName = rootNode.getDocType().getPart1();
            String publicId = rootNode.getDocType().getPublicId();
            String systemId = rootNode.getDocType().getSystemId();

            //
            // If there is no qualified name, set it to html. See bug #153.
            //
            if (qualifiedName == null) {
                qualifiedName = HTML_QUALIFIED_NAME;
            }

            DocumentType documentType = impl.createDocumentType(qualifiedName, publicId, systemId);

            //
            // While the qualified name is "HTML" for some DocTypes, we want the actual document root name to be "html".
            // See bug #116
            //
            if (qualifiedName.equals("HTML")) {
                qualifiedName = HTML_QUALIFIED_NAME;
            }
            document = impl.createDocument(rootNode.getNamespaceURIOnPath(""), qualifiedName, documentType);
        } else {
            document = builder.newDocument();
            Element rootElement = document.createElement(rootNode.getName());
            document.appendChild(rootElement);
        }

        //
        // Turn off error checking if we're allowing invalid attribute names, or if we've chosen to turn it off
        //
        if (props.isAllowInvalidAttributeNames() || !strictErrorChecking) {
            document.setStrictErrorChecking(false);
        }


        //
        // Copy across root node attributes - see issue 127. Thanks to rasifiel for the patch
        //
        Map attributes =  rootNode.getAttributes();
        Iterator> entryIterator = attributes.entrySet().iterator();
        while (entryIterator.hasNext()) {
            Map.Entry entry = entryIterator.next();
            String attrName = entry.getKey();
            String attrValue = entry.getValue();

            //
            // Fix any invalid attribute names
            //
            if (!props.isAllowInvalidAttributeNames()) {
                attrName = Utils.sanitizeXmlIdentifier(attrName, props.getInvalidXmlAttributeNamePrefix());
            }

            if (attrName != null && (Utils.isValidXmlIdentifier(attrName) || props.isAllowInvalidAttributeNames())) {

                if (escapeXml) {
                    attrValue = Utils.escapeXml(attrValue, props, true);
                }

                document.getDocumentElement().setAttribute(attrName, attrValue);

                //
                // Flag the attribute as an ID attribute if appropriate. Thanks to Chris173
                //
                if (attrName.equalsIgnoreCase("id")) {
                    document.getDocumentElement().setIdAttribute(attrName, true);
                }
            }

        }
        return document;
    }

    /**
     * Create the DOM given a rootNode and a document builder.
     * This method is a replica of {@link DomSerializer#createDOM(TagNode)} excepts that it requires to give a
     * DocumentBuilder.
     * @param documentBuilder the {@link DocumentBuilder} instance to use, DocumentBuilder is not guaranteed to
     * be thread safe so at most the safe instance should be used only in the same thread
     * @param rootNode the HTML Cleaner root node to serialize
     * @return the W3C Document object
     * @throws ParserConfigurationException if there's an error during serialization
     */
    public Document createDOM(DocumentBuilder documentBuilder, TagNode rootNode) throws ParserConfigurationException
    {
        Document document = createDocument(documentBuilder, rootNode);

        createSubnodes(document, document.getDocumentElement(), rootNode.getAllChildren());

        return document;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy