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

org.thymeleaf.templateparser.text.TextParsingElementUtil Maven / Gradle / Ivy

/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2016, 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.templateparser.text;



/*
 * Class containing utility methods for parsing elements (tags).
 *
 * @author Daniel Fernandez
 * @since 3.0.0
 */
final class TextParsingElementUtil {


    

    
    private TextParsingElementUtil() {
        super();
    }




    public static void parseStandaloneElement(
            final char[] buffer,
            final int offset, final int len,
            final int line, final int col,
            final ITextHandler handler)
            throws TextParseException {

        if (len < 4 || !isOpenElementStart(buffer, offset, offset + len) || !isElementEnd(buffer, (offset + len) - 2, offset + len, true)) {
            throw new TextParseException(
                    "Could not parse as a well-formed standalone element: \"" + new String(buffer, offset, len) + "\"", line, col);
        }

        final int contentOffset = offset + 2;   // We add +2 because of '[#'
        final int contentLen = len - 4;         // -2 because of '[#' and another -2 because of '/]'

        final int maxi = contentOffset + contentLen;

        final int[] locator = new int[] {line, col + 2};

        /*
         * Extract the element name first
         */

        final int elementNameEnd =
                TextParsingUtil.findNextWhitespaceCharWildcard(buffer, contentOffset, maxi, true, locator);

        if (elementNameEnd == -1) {
            // The buffer only contains the element name (if it has a name)

            handler.handleStandaloneElementStart(
                    buffer, contentOffset, contentLen,
                    true, line, col);

            handler.handleStandaloneElementEnd(
                    buffer, contentOffset, contentLen,
                    true, locator[0], locator[1]);

            return;

        }


        handler.handleStandaloneElementStart(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                true, line, col);


        // This parseAttributeSequence will take care of calling handleInnerWhitespace when appropriate.
        TextParsingAttributeSequenceUtil.parseAttributeSequence(
                buffer, elementNameEnd, maxi - elementNameEnd, locator[0], locator[1], handler);

        // We need to forward the locator to the position corresponding with the element end (note we are discarding result)
        TextParsingUtil.findNextStructureEndAvoidQuotes(buffer, elementNameEnd, maxi, locator);

        handler.handleStandaloneElementEnd(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                true, locator[0], locator[1]);

    }




    public static void parseOpenElement(
            final char[] buffer,
            final int offset, final int len,
            final int line, final int col,
            final ITextHandler handler)
            throws TextParseException {

        if (len < 3 || !isOpenElementStart(buffer, offset, offset + len) || !isElementEnd(buffer, (offset + len) - 1, offset + len, false)) {
            throw new TextParseException(
                    "Could not parse as a well-formed open element: \"" + new String(buffer, offset, len) + "\"", line, col);
        }

        final int contentOffset = offset + 2;   // We add +2 because of '[#'
        final int contentLen = len - 3;         // -2 because of '[#' and another -1 because of ']'

        final int maxi = contentOffset + contentLen;

        final int[] locator = new int[] {line, col + 2};

        /*
         * Extract the element name first
         */

        final int elementNameEnd =
                TextParsingUtil.findNextWhitespaceCharWildcard(buffer, contentOffset, maxi, true, locator);

        if (elementNameEnd == -1) {
            // The buffer only contains the element name (if it has a name)

            handler.handleOpenElementStart(
                    buffer, contentOffset, contentLen,
                    line, col);

            handler.handleOpenElementEnd(
                    buffer, contentOffset, contentLen,
                    locator[0], locator[1]);

            return;

        }


        handler.handleOpenElementStart(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                line, col);


        // This parseAttributeSequence will take care of calling handleInnerWhitespace when appropriate.
        TextParsingAttributeSequenceUtil.parseAttributeSequence(
                buffer, elementNameEnd, maxi - elementNameEnd, locator[0], locator[1], handler);

        // We need to forward the locator to the position corresponding with the element end (note we are discarding result)
        TextParsingUtil.findNextStructureEndAvoidQuotes(buffer, elementNameEnd, maxi, locator);

        handler.handleOpenElementEnd(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                locator[0], locator[1]);

    }




    public static void parseCloseElement(
            final char[] buffer,
            final int offset, final int len,
            final int line, final int col,
            final ITextHandler handler)
            throws TextParseException {

        if (len < 3 || !isCloseElementStart(buffer, offset, offset + len) || !isElementEnd(buffer, (offset + len) - 1, offset + len, false)) {
            throw new TextParseException(
                    "Could not parse as a well-formed close element: \"" + new String(buffer, offset, len) + "\"", line, col);
        }

        final int contentOffset = offset + 2;   // We add +2 because of '[/'
        final int contentLen = len - 3;         // -2 because of '[/' and another -1 because of ']'

        final int maxi = contentOffset + contentLen;

        final int[] locator = new int[] {line, col + 2};

        /*
         * Extract the element name first
         */

        final int elementNameEnd =
                TextParsingUtil.findNextWhitespaceCharWildcard(buffer, contentOffset, maxi, true, locator);

        if (elementNameEnd == -1) {
            // The buffer only contains the element name

            handler.handleCloseElementStart(
                    buffer, contentOffset, contentLen,
                    line, col);

            handler.handleCloseElementEnd(
                    buffer, contentOffset, contentLen,
                    locator[0], locator[1]);

            return;

        }


        handler.handleCloseElementStart(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                line, col);


        final int wsEnd =
                TextParsingUtil.findNextNonWhitespaceCharWildcard(buffer, elementNameEnd, maxi, locator);

        if (wsEnd != -1) {
            // This is a close tag, so everything should be whitespace
            // until the end of the close tag
            throw new TextParseException(
                    "Could not parse as a well-formed closing element \"" + new String(buffer, offset, len) + "\": No attributes are allowed here", line, col);
        }


        handler.handleCloseElementEnd(
                buffer, contentOffset, (elementNameEnd - contentOffset),
                locator[0], locator[1]);

    }






    static boolean isOpenElementStart(final char[] buffer, final int offset, final int maxi) {

        final int len = maxi - offset;

        return (len > 2 &&
                    buffer[offset] == '[' &&
                    buffer[offset + 1] == '#' &&
                    isElementNameOrEnd(buffer, offset + 2, maxi));

    }


    static boolean isCloseElementStart(final char[] buffer, final int offset, final int maxi) {

        final int len = maxi - offset;

        return (len > 2 &&
                    buffer[offset] == '[' &&
                    buffer[offset + 1] == '/' &&
                    isElementNameOrEnd(buffer, offset + 2, maxi));

    }


    static boolean isElementEnd(final char[] buffer, final int offset, final int maxi, final boolean minimized) {

        final int len = maxi - offset;

        if (len < 1) {
            return false; // won't fit
        }

        if (minimized) {
            if (len < 2 || buffer[offset] != '/') {
                return false;
            }
            return buffer[offset + 1] == ']';
        }

        return buffer[offset] == ']';

    }




    private static boolean isElementNameOrEnd(final char[] buffer, final int offset, final int maxi) {

        if (Character.isWhitespace(buffer[offset])) {
            // We cover here the case when we don't apply an element name: [# th:something=...]
            return true;
        }

        final int len = maxi - offset;

        if (len > 1 && buffer[offset] == '/') {
            // This can still be valid if we are just closing a no-name standalone element without attributes: [#/]
            return isElementEnd(buffer, offset, maxi, true);
        }

        if (len > 0 && buffer[offset] == ']') {
            // This can still be valid if we are just closing a no-name open element without attributes: [#]
            return isElementEnd(buffer, offset, maxi, false);
        }

        // At this point the element HAS TO have a name, as we have already ruled out all possibilities that it does not.
        // The rules for an element name are basically the same, wrt allowed chars, as a normal markup (HTML,XML) element.
        // Note however that we will not be allowing a '{' just after the first # character already parsed so that we
        // don't mistake a direct output expression for an externalized message [[#{my.message}]] for an element.
        return (len > 0 &&
                buffer[offset] != '-' && buffer[offset] != '!' &&
                buffer[offset] != '/' && buffer[offset] != '?' &&
                buffer[offset] != '[' && buffer[offset] != '{' &&
                !Character.isWhitespace(buffer[offset]));

    }







    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy