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