/* * ============================================================================= * * 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.standard.expression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.IExpressionContext; import org.thymeleaf.util.Validate; /** * <p> * Text literal (Thymeleaf Standard Expressions) * </p> * <p> * Note a class with this name existed since 1.1, but it was completely reimplemented * in Thymeleaf 3.0 * </p> * * @author Daniel Fernández * * @since 3.0.0 * */ public final class TextLiteralExpression extends SimpleExpression { private static final Logger logger = LoggerFactory.getLogger(TextLiteralExpression.class); private static final long serialVersionUID = 6511847028638506552L; static final char ESCAPE_PREFIX = '\\'; static final char DELIMITER = '\''; private final LiteralValue value; public TextLiteralExpression(final String value) { super(); Validate.notNull(value, "Value cannot be null"); this.value = new LiteralValue(unwrapLiteral(value)); } public LiteralValue getValue() { return this.value; } private static String unwrapLiteral(final String input) { // We know input is not null final int inputLen = input.length(); if (inputLen > 1 && input.charAt(0) == '\'' && input.charAt(inputLen - 1) == '\'') { return unescapeLiteral(input.substring(1, inputLen - 1)); } return input; } @Override public String getStringRepresentation() { return String.valueOf(DELIMITER) + this.value.getValue().replace(String.valueOf(DELIMITER),("\\" + DELIMITER)) + String.valueOf(DELIMITER); } static TextLiteralExpression parseTextLiteralExpression(final String input) { return new TextLiteralExpression(input); } static Object executeTextLiteralExpression( final IExpressionContext context, final TextLiteralExpression expression, final StandardExpressionExecutionContext expContext) { if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] Evaluating text literal: \"{}\"", TemplateEngine.threadIndex(), expression.getStringRepresentation()); } return expression.getValue(); } public static String wrapStringIntoLiteral(final String str) { if (str == null) { return null; } int n = str.length(); while (n-- != 0) { if (str.charAt(n) == '\'') { final StringBuilder strBuilder = new StringBuilder(str.length() + 5); strBuilder.append('\''); final int strLen = str.length(); for (int i = 0; i < strLen; i++) { final char c = str.charAt(i); if (c == '\'') { strBuilder.append('\\'); } strBuilder.append(c); } strBuilder.append('\''); return strBuilder.toString(); } } return '\'' + str + '\''; } static boolean isDelimiterEscaped(final String input, final int pos) { // Only an odd number of \'s will indicate escaping if (pos == 0 || input.charAt(pos - 1) != '\\') { return false; } int i = pos - 1; boolean odd = false; while (i >= 0) { if (input.charAt(i) == '\\') { odd = !odd; } else { return odd; } i--; } return odd; } /* * This unescape operation will perform two transformations: * \' -> ' * \\ -> \ */ private static String unescapeLiteral(final String text) { if (text == null) { return null; } StringBuilder strBuilder = null; final int max = text.length(); int readOffset = 0; int referenceOffset = 0; char c; for (int i = 0; i < max; i++) { c = text.charAt(i); /* * Check the need for an unescape operation at this point */ if (c != ESCAPE_PREFIX || (i + 1) >= max) { continue; } if (c == ESCAPE_PREFIX) { switch (text.charAt(i + 1)) { case '\'': c = '\''; referenceOffset = i + 1; break; case '\\': c = '\\'; referenceOffset = i + 1; break; // We weren't able to consume any valid escape chars, just not consider it an escape operation default: referenceOffset = i; break; } } /* * At this point we know for sure we will need some kind of unescape, so we * can increase the offset and initialize the string builder if needed, along with * copying to it all the contents pending up to this point. */ if (strBuilder == null) { strBuilder = new StringBuilder(max + 5); } if (i - readOffset > 0) { strBuilder.append(text, readOffset, i); } i = referenceOffset; readOffset = i + 1; /* * Write the unescaped char */ strBuilder.append(c); } /* * ----------------------------------------------------------------------------------------------- * Final cleaning: return the original String object if no unescape was actually needed. Otherwise * append the remaining escaped text to the string builder and return. * ----------------------------------------------------------------------------------------------- */ if (strBuilder == null) { return text; } if (max - readOffset > 0) { strBuilder.append(text, readOffset, max); } return strBuilder.toString(); } }