/*
* =============================================================================
*
* 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 attribute sequences. This class is almost a copy of AttoParser's
* org.attoparser.ParsingAttributeSequenceUtil, except for the fact that ITextHandler does not have an event for
* inner whitespace (they do not have to be preserved in text parsing), so the code here is a bit simpler.
*
* @author Daniel Fernandez
* @since 3.0.0
*/
final class TextParsingAttributeSequenceUtil {
private TextParsingAttributeSequenceUtil() {
super();
}
public static void parseAttributeSequence(
final char[] buffer,
final int offset, final int len,
final int line, final int col,
final ITextHandler handler)
throws TextParseException {
// Any string will be recognized as an "attribute sequence", so this will always either return a not-null result
// or raise an exception.
final int maxi = offset + len;
final int[] locator = new int[] {line, col};
int i = offset;
int current = i;
int currentArtifactLine;
int currentArtifactCol;
while (i < maxi) {
/*
* STEP ONE: Look for whitespaces between attributes
*/
final int wsEnd =
TextParsingUtil.findNextNonWhitespaceCharWildcard(buffer, i, maxi, locator);
if (wsEnd == -1) {
// Everything is whitespace until the end of the tag
i = maxi;
continue;
}
if (wsEnd > current) {
// We avoid empty whitespace fragments
i = wsEnd;
current = i;
}
/*
* STEP TWO: Detect the attribute name
*/
currentArtifactLine = locator[0];
currentArtifactCol = locator[1];
final int attributeNameEnd =
TextParsingUtil.findNextOperatorCharWildcard(buffer, i, maxi, locator);
if (attributeNameEnd == -1) {
// This is a no-value and no-equals-sign attribute, equivalent to value = ""
handler.handleAttribute(
buffer, // name
current, (maxi - current), // name
currentArtifactLine, currentArtifactCol, // name
0, 0, // operator
locator[0], locator[1], // operator
0, 0, 0, 0, // value
locator[0], locator[1]); // value
i = maxi;
continue;
}
if (attributeNameEnd <= current) {
// This attribute name starts by an equals sign, which is forbidden
throw new TextParseException(
"Bad attribute name in sequence \"" + new String(buffer, offset, len) + "\": attribute names " +
"cannot start with an equals sign", currentArtifactLine, currentArtifactCol);
}
final int attributeNameOffset = current;
final int attributeNameLen = attributeNameEnd - current;
final int attributeNameLine = currentArtifactLine;
final int attributeNameCol = currentArtifactCol;
i = attributeNameEnd;
current = i;
/*
* STEP THREE: Detect the operator
*/
currentArtifactLine = locator[0];
currentArtifactCol = locator[1];
final int operatorEnd =
TextParsingUtil.findNextNonOperatorCharWildcard(buffer, i, maxi, locator);
if (operatorEnd == -1) {
// This could be:
// 1. A no-value and no-equals-sign attribute
// 2. A no-value WITH equals sign attribute
boolean equalsPresent = false;
for (int j = i; j < maxi; j++) {
if (buffer[j] == '=') {
equalsPresent = true;
break;
}
}
if (equalsPresent) {
// It is a no value with equals, so we will consider everything
// to be an operator
handler.handleAttribute(
buffer, // name
attributeNameOffset, attributeNameLen, // name
attributeNameLine, attributeNameCol, // name
current, (maxi - current), // operator
currentArtifactLine, currentArtifactCol, // operator
0, 0, 0, 0, // value
locator[0], locator[1]); // value
} else {
// There is no "=", so we will output the attribute with no operator and not value
handler.handleAttribute(
buffer, // name
attributeNameOffset, attributeNameLen, // name
attributeNameLine, attributeNameCol, // name
0, 0, // operator
currentArtifactLine, currentArtifactCol, // operator
0, 0, 0, 0, // value
currentArtifactLine, currentArtifactCol); // value
}
i = maxi;
continue;
// end: (operatorEnd == -1)
}
boolean equalsPresent = false;
for (int j = current; j < operatorEnd; j++) {
if (buffer[j] == '=') {
equalsPresent = true;
break;
}
}
if (!equalsPresent) {
// It is not an operator, but a whitespace between this and the next attribute,
// so we will output the attribute with no operator and no value
handler.handleAttribute(
buffer, // name
attributeNameOffset, attributeNameLen, // name
attributeNameLine, attributeNameCol, // name
0, 0, // operator
currentArtifactLine, currentArtifactCol, // operator
0, 0, 0, 0, // value
currentArtifactLine, currentArtifactCol); // value
i = operatorEnd;
current = i;
continue;
}
final int operatorOffset = current;
final int operatorLen = operatorEnd - current;
final int operatorLine = currentArtifactLine;
final int operatorCol = currentArtifactCol;
i = operatorEnd;
current = i;
/*
* STEP FOUR: Detect the value
*/
currentArtifactLine = locator[0];
currentArtifactCol = locator[1];
final boolean attributeEndsWithQuotes = (i < maxi && (buffer[current] == '"' || buffer[current] == '\''));
final int valueEnd =
(attributeEndsWithQuotes?
TextParsingUtil.findNextAnyCharAvoidQuotesWildcard(buffer, i, maxi, locator) :
TextParsingUtil.findNextWhitespaceCharWildcard(buffer, i, maxi, false, locator));
if (valueEnd == -1) {
// This value ends the attribute
int valueContentOffset = current;
int valueContentLen = (maxi - current);
if (isValueSurroundedByCommas(buffer, current, (maxi - current))) {
valueContentOffset = valueContentOffset + 1;
valueContentLen = valueContentLen - 2;
}
handler.handleAttribute(
buffer, // name
attributeNameOffset, attributeNameLen, // name
attributeNameLine, attributeNameCol, // name
operatorOffset, operatorLen, // operator
operatorLine, operatorCol, // operator
valueContentOffset, valueContentLen, current, (maxi - current), // value
currentArtifactLine, currentArtifactCol); // value
i = maxi;
continue;
}
final int valueOuterOffset = current;
final int valueOuterLen = valueEnd - current;
int valueContentOffset = valueOuterOffset;
int valueContentLen = valueOuterLen;
if (isValueSurroundedByCommas(buffer, valueOuterOffset, valueOuterLen)) {
valueContentOffset = valueOuterOffset + 1;
valueContentLen = valueOuterLen - 2;
}
handler.handleAttribute(
buffer, // name
attributeNameOffset, attributeNameLen, // name
attributeNameLine, attributeNameCol, // name
operatorOffset, operatorLen, // operator
operatorLine, operatorCol, // operator
valueContentOffset, valueContentLen, valueOuterOffset, valueOuterLen, // value
currentArtifactLine, currentArtifactCol); // value
i = valueEnd;
current = i;
}
}
private static boolean isValueSurroundedByCommas(final char[] buffer, final int offset, final int len) {
return len >= 2 && ((buffer[offset] == '"' && buffer[offset + len - 1] == '"') || (buffer[offset] == '\'' && buffer[offset + len - 1] == '\''));
}
}