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

org.thymeleaf.util.DOMUtils Maven / Gradle / Ivy

The newest version!
/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2013, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   Licensed 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.thymeleaf.util;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.thymeleaf.Arguments;
import org.thymeleaf.Configuration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.TemplateRepository;
import org.thymeleaf.context.Context;
import org.thymeleaf.dom.Attribute;
import org.thymeleaf.dom.Document;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.NestableAttributeHolderNode;
import org.thymeleaf.dom.NestableNode;
import org.thymeleaf.dom.Node;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.exceptions.TemplateOutputException;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.resourceresolver.ClassLoaderResourceResolver;
import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
import org.thymeleaf.templateparser.ITemplateParser;
import org.thymeleaf.templateparser.html.LegacyHtml5TemplateParser;
import org.thymeleaf.templateparser.xmlsax.XhtmlAndHtml5NonValidatingSAXTemplateParser;
import org.thymeleaf.templateparser.xmlsax.XmlNonValidatingSAXTemplateParser;
import org.thymeleaf.templateresolver.AlwaysValidTemplateResolutionValidity;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolution;
import org.thymeleaf.templatewriter.AbstractGeneralTemplateWriter;
import org.thymeleaf.templatewriter.XhtmlHtml5TemplateWriter;
import org.thymeleaf.templatewriter.XmlTemplateWriter;

/**
 * 
 * @author Daniel Fernández
 * 
 * @since 1.1.2
 *
 */
public final class DOMUtils {

    
    private static final char[] AMP = "&".toCharArray();
    private static final char[] LT = "<".toCharArray();
    private static final char[] GT = ">".toCharArray();
    private static final char[] QUOT = """.toCharArray();
    // ' will not be used as it is considered an XML entity, but not
    // an HTML one. This causes problems in Internet Explorer <= 8.
    private static final char[] APOS = "'".toCharArray();
    
    
    
    
    
    public static List extractFragmentByElementAndAttributeValue(
            final List rootNodes, final String elementName, final String attributeName, final String attributeValue) {

        Validate.notNull(rootNodes, "Root node list cannot be null");
        // Element name, attribute name and attribute value CAN be null 
        // (in that case, all elements will be searched)

        final String normalizedElementName = Element.normalizeElementName(elementName);
        final String normalizedAttributeName = Attribute.normalizeAttributeName(attributeName);
        
        final List fragmentNodes = new ArrayList(5);
        for (final Node rootNode : rootNodes) {
            final List extraction = 
                    extractFragmentFromNode(rootNode, normalizedElementName, normalizedAttributeName, attributeValue);
            if (extraction != null) {
                fragmentNodes.addAll(extraction);
            }
        }
        return fragmentNodes;
        
    }

    
    
    private static List extractFragmentFromNode(
            final Node node, final String normalizedElementName, final String normalizedAttributeName, final String attributeValue) {
        
        if (node instanceof NestableNode) {

            final NestableNode nestableNode = (NestableNode) node;
            
            /*
             * First check the element itself
             */
            if (nestableNode instanceof Element) {
                final Element element = (Element) nestableNode;
                if (normalizedElementName == null || normalizedElementName.equals(element.getNormalizedName())) {
                    if (normalizedAttributeName != null) {
                        if (element.hasNormalizedAttribute(normalizedAttributeName)) {
                            final String elementAttrValue = element.getAttributeValue(normalizedAttributeName);
                            if (elementAttrValue != null && elementAttrValue.trim().equals(attributeValue)) {
                                return Collections.singletonList((Node)nestableNode);
                            }
                        }
                    } else {
                        return Collections.singletonList((Node)nestableNode);
                    }
               }
            } else if (nestableNode instanceof NestableAttributeHolderNode) {
                final NestableAttributeHolderNode attributeHolderNode = (NestableAttributeHolderNode) nestableNode;
                // If not null, an element without name can never be selectable
                if (normalizedElementName == null) {
                    if (normalizedAttributeName != null) {
                        if (attributeHolderNode.hasNormalizedAttribute(normalizedAttributeName)) {
                            final String elementAttrValue = attributeHolderNode.getAttributeValue(normalizedAttributeName);
                            if (elementAttrValue != null && elementAttrValue.trim().equals(attributeValue)) {
                                return Collections.singletonList((Node)nestableNode);
                            }
                        }
                    } else {
                        return Collections.singletonList((Node)nestableNode);
                    }
                }
            }
            
            /*
             * If element does not match itself, try children
             */
            final List extraction = new ArrayList(5);
            final List children = nestableNode.getChildren();
            for (final Node child : children) {
                final List childResult =
                        extractFragmentFromNode(
                                child, normalizedElementName, normalizedAttributeName, attributeValue);
                if (childResult != null) {
                    extraction.addAll(childResult);
                }
            }
            return extraction;
            
        }
        
        return null;
        
    }


    /**
     * @deprecated This method has been deprecated in 2.1.3 and is no longer an adequate way to obtain escaped
     *             HTML code. The whole of the escaping mechanism defined on top of these DOMUtils escape/unescape
     *             methods had too many drawbacks and issues, so it was completely replaced by the Unbescape library.
     *             Will be removed in 3.0.
     */
    @Deprecated
    public static char[] escapeXml(final char[] ch, final boolean escapeQuotes) throws IOException {
        
        if (ch == null) {
            return null;
        }

        for (final char c : ch) {
            if (c == '&' || c == '<' || c == '>' || (escapeQuotes && (c == '\'' || c == '\"'))) {
                final CharArrayWriter writer = new CharArrayWriter();
                writeXmlEscaped(ch, writer, escapeQuotes);
                return writer.toCharArray();
            }
        }
        
        return ch;
        
    }


    /**
     * @deprecated This method has been deprecated in 2.1.3 and is no longer an adequate way to obtain escaped
     *             HTML code. The whole of the escaping mechanism defined on top of these DOMUtils escape/unescape
     *             methods had too many drawbacks and issues, so it was completely replaced by the Unbescape library.
     *             Will be removed in 3.0.
     */
    @Deprecated
    public static String escapeXml(final String str, final boolean escapeQuotes) throws IOException {
        
        if (str == null) {
            return null;
        }
        
        final int strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            final char c = str.charAt(i);
            if (c == '&' || c == '<' || c == '>' || (escapeQuotes && (c == '\'' || c == '\"'))) {
                final StringWriter writer = new StringWriter();
                writeXmlEscaped(str, writer, escapeQuotes);
                return writer.toString();
            }
        }
        
        return str;
        
    }



    /**
     * @deprecated This method has been deprecated in 2.1.3 and is no longer an adequate way to obtain escaped
     *             HTML code. The whole of the escaping mechanism defined on top of these DOMUtils escape/unescape
     *             methods had too many drawbacks and issues, so it was completely replaced by the Unbescape library.
     *             Will be removed in 3.0.
     */
    @Deprecated
    public static void writeXmlEscaped(final String buffer, final Writer writer, final boolean escapeQuotes) throws IOException {
        if (buffer == null) {
            return;
        }
        writeXmlEscaped(buffer.toCharArray(), writer, escapeQuotes);
    }


    /**
     * @deprecated This method has been deprecated in 2.1.3 and is no longer an adequate way to obtain escaped
     *             HTML code. The whole of the escaping mechanism defined on top of these DOMUtils escape/unescape
     *             methods had too many drawbacks and issues, so it was completely replaced by the Unbescape library.
     *             Will be removed in 3.0.
     */
    @Deprecated
    public static void writeXmlEscaped(final char[] buffer, final Writer writer, final boolean escapeQuotes) throws IOException {
        
        if (buffer == null || buffer.length == 0) {
            return;
        }
        
        int off = 0;
        int len = 0;
        int i = 0;
        
        while (i < buffer.length) {
            final char c = buffer[i];
            if (c == '&') {
                if (len > 0) {
                    writer.write(buffer, off, len);
                }
                if (isEntityStart(buffer, i)) {
                    // This avoids escaping &'s that are in fact starting 
                    // already escaped entities.
                    writer.write('&');
                } else {
                    writer.write(AMP);
                }
                off = i + 1;
                len = 0;
            } else if (c == '<') {
                if (len > 0) {
                    writer.write(buffer, off, len);
                }
                writer.write(LT);
                off = i + 1;
                len = 0;
            } else if (c == '>') {
                if (len > 0) {
                    writer.write(buffer, off, len);
                }
                writer.write(GT);
                off = i + 1;
                len = 0;
            } else if (escapeQuotes && c == '\"') {
                if (len > 0) {
                    writer.write(buffer, off, len);
                }
                writer.write(QUOT);
                off = i + 1;
                len = 0;
            } else if (escapeQuotes && c == '\'') {
                if (len > 0) {
                    writer.write(buffer, off, len);
                }
                writer.write(APOS);
                off = i + 1;
                len = 0;
            } else {
                len++;
            }
            i++;
        }
        if (len > 0) {
            writer.write(buffer, off, len);
        }
    }
    

    
    private static boolean isEntityStart(final char[] buffer, final int position) {
        int i = position + 1;
        while (i < buffer.length) {
            final char c = buffer[i];
            if (!( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == ';') )) {
                return false;
            }
            if (c == ';') {
                return true;
            }
            i++;
        }
        return false;
    }



    /**
     * @deprecated This method has been deprecated in 2.1.3 and is no longer an adequate way to obtain unescaped
     *             HTML code. The whole of the escaping mechanism defined on top of these DOMUtils escape/unescape
     *             methods had too many drawbacks and issues, so it was completely replaced by the Unbescape library.
     *             Will be removed in 3.0.
     */
    @Deprecated
    public static String unescapeXml(final String str, final boolean unescapeQuotes) {
        
        if (str == null) {
            return null;
        }
        
        final int strLen = str.length();
        
        // Try to fail fast if no unescaping is needed
        boolean existsAmpersand = false;
        for (int i = 0; i < strLen; i++) {
            if (str.charAt(i) == '&') {
                existsAmpersand = true;
                break;
            }
        }
        if (!existsAmpersand) {
            return str;
        }
        
        int off = 0;
        int len = 0;
        int entityStart = -1;
        int entityLen = 0;
        
        int i = 0;
        
        final StringBuilder builder = new StringBuilder();
        while (i < strLen) {
            
            final char c = str.charAt(i);
            
            if (c == '&') {
                if (entityLen > 0) {
                    builder.append(str.substring(entityStart, (entityStart + entityLen)));
                } else if (len > 0) {
                    builder.append(str.substring(off, (off + len)));
                }
                entityStart = i;
                entityLen = 1;
                off = -1;
                len = 0;
            } else if (c == ';' && entityStart != -1) {
                builder.append(unescapeXmlEntity(str, entityStart, entityLen + 1, unescapeQuotes));
                entityStart = -1;
                entityLen = 0;
                off = i + 1;
                len = 0;
            } else {
                if (entityStart != -1) {
                    entityLen++;
                } else {
                    len++;
                }
            }
            i++;
        }
        if (entityLen > 0) {
            builder.append(str, entityStart, (entityStart + entityLen));
        } else if (len > 0) {
            builder.append(str.substring(off, (off + len)));
        }
        
        return builder.toString();
        
    }

    
    
    private static String unescapeXmlEntity(
            final String str, final int off, final int len, final boolean unescapeQuotes) {
        
        if (len < 4) {
            return str.substring(off, (off + len));
        }
        
        final char c1 = str.charAt(off + 1);
        final char c2 = str.charAt(off + 2);
        if (len == 4 && c1 == 'l' && c2 == 't') {
            return "<";
        }
        if (len == 4 && c1 == 'g' && c2 == 't') {
            return ">";
        }
        if (len == 5 && c1 == 'a' && c2 == 'm' && str.charAt(off + 3) == 'p') {
            return "&";
        }
        if (len == 6 && unescapeQuotes && c1 == 'q' && c2 == 'u' && str.charAt(off + 3) == 'o' && str.charAt(off + 4) == 't') {
            return "\"";
        }
        if (len == 6 && unescapeQuotes && c1 == 'a' && c2 == 'p' && str.charAt(off + 3) == 'o' && str.charAt(off + 4) == 's') {
            return "\'";
        }
        if (len == 5 && c1 == '#') {
            final char c3 = str.charAt(off + 3);
            if (c2 == '6' && c3 == '0') {
                return "<";
            } else if (c2 == '6' && c3 == '2') {
                return ">";
            } else if (c2 == '3' && c3 == '8') {
                return "&";
            } else if (unescapeQuotes && c2 == '3' && c3 == '4') {
                return "\"";
            } else if (unescapeQuotes && c2 == '3' && c3 == '9') {
                return "\'";
            }
        }
        return str.substring(off, (off + len));
        
    }
    
    
    
    
    public static String getXmlFor(final Node node) {
        return getOutputFor(node, new XmlTemplateWriter(), "XML");
    }
    
    
    public static String getXhtmlFor(final Node node) {
        return getOutputFor(node, new XhtmlHtml5TemplateWriter(), "XHTML");
    }
    
    
    public static String getHtml5For(final Node node) {
        return getOutputFor(node, new XhtmlHtml5TemplateWriter(), "HTML5");
    }
    
    

    
    /**
     * 

* This method is for testing purposes only! It creates mock configuration, template and resource * resolution artifacts, etc. Its usage during normal operation could heavily affect performance. *

* * @param node the node which output is to be computed * @param templateWriter the template writter to be used for creating the output * @return the computed output * @since 2.0.8 */ public static String getOutputFor(final Node node, final AbstractGeneralTemplateWriter templateWriter, final String templateMode) { Validate.notNull(node, "Node cannot be null"); Validate.notNull(templateWriter, "Template writer cannot be null"); final TemplateEngine templateEngine = new TemplateEngine(); templateEngine.addTemplateResolver(new ClassLoaderTemplateResolver()); templateEngine.addMessageResolver(new StandardMessageResolver()); templateEngine.setTemplateModeHandlers(StandardTemplateModeHandlers.ALL_TEMPLATE_MODE_HANDLERS); final String templateName = "output"; final TemplateProcessingParameters templateProcessingParameters = new TemplateProcessingParameters(templateEngine.getConfiguration(), templateName, new Context()); final TemplateResolution templateResolution = new TemplateResolution(templateName, "resource:"+templateName, new ClassLoaderResourceResolver(), "UTF-8", templateMode, new AlwaysValidTemplateResolutionValidity()); templateEngine.initialize(); final TemplateRepository templateRepository = templateEngine.getTemplateRepository(); final Document document = new Document(templateName); document.addChild(node); final Arguments arguments = new Arguments(new TemplateEngine(), templateProcessingParameters, templateResolution, templateRepository, document); return getOutputFor(arguments, node, templateWriter); } /** * * @since 2.0.8 */ public static String getOutputFor(final Arguments arguments, final Node node, final AbstractGeneralTemplateWriter templateWriter) { Validate.notNull(arguments, "Arguments cannot be null"); Validate.notNull(node, "Node cannot be null"); Validate.notNull(templateWriter, "Template writer cannot be null"); try { final StringWriter writer = new StringWriter(); templateWriter.writeNode(arguments, writer, node); return writer.toString(); } catch (final IOException e) { throw new TemplateOutputException( "Exception during creation of output for node", e); } } /** * * @since 2.0.8 */ public static Document getHtml5DOMFor(final Reader source) { Validate.notNull(source, "Source cannot be null"); return getDOMFor(source, new XhtmlAndHtml5NonValidatingSAXTemplateParser(1)); } /** * * @since 2.0.8 */ public static Document getLegacyHTML5DOMFor(final Reader source) { Validate.notNull(source, "Source cannot be null"); return getDOMFor(source, new LegacyHtml5TemplateParser("LEGACYHTML5", 1)); } /** * * @since 2.0.8 */ public static Document getXmlDOMFor(final Reader source) { Validate.notNull(source, "Source cannot be null"); return getDOMFor(source, new XmlNonValidatingSAXTemplateParser(1)); } /** * * @since 2.0.8 */ public static Document getXhtmlDOMFor(final Reader source) { Validate.notNull(source, "Source cannot be null"); return getDOMFor(source, new XhtmlAndHtml5NonValidatingSAXTemplateParser(1)); } /** * * @since 2.0.8 */ public static Document getDOMFor(final Reader source, final ITemplateParser parser) { Validate.notNull(source, "Source cannot be null"); Validate.notNull(parser, "Template parser cannot be null"); final Configuration configuration = new Configuration(); configuration.addTemplateResolver(new ClassLoaderTemplateResolver()); configuration.addMessageResolver(new StandardMessageResolver()); configuration.setTemplateModeHandlers(StandardTemplateModeHandlers.ALL_TEMPLATE_MODE_HANDLERS); configuration.initialize(); return getDOMFor(configuration, source, parser); } /** * * @since 2.0.8 */ public static Document getDOMFor(final Configuration configuration, final Reader source, final ITemplateParser parser) { Validate.notNull(configuration, "Configuration cannot be null"); Validate.notNull(source, "Source cannot be null"); Validate.notNull(parser, "Template parser cannot be null"); try { return parser.parseTemplate(configuration, "input", source); } catch (final Exception e) { throw new TemplateInputException( "Exception during parsing of source", e); } } private DOMUtils() { super(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy