/*
* =============================================================================
*
* 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.processor;
import java.util.List;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
import org.thymeleaf.standard.expression.Assignation;
import org.thymeleaf.standard.expression.AssignationSequence;
import org.thymeleaf.standard.expression.AssignationUtils;
import org.thymeleaf.standard.expression.IStandardExpression;
import org.thymeleaf.standard.expression.NoOpToken;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.util.ArrayUtils;
import org.thymeleaf.util.EvaluationUtils;
import org.thymeleaf.util.StringUtils;
import org.unbescape.html.HtmlEscape;
/**
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
public abstract class AbstractStandardMultipleAttributeModifierTagProcessor extends AbstractAttributeTagProcessor {
protected enum ModificationType { SUBSTITUTION, APPEND, PREPEND, APPEND_WITH_SPACE, PREPEND_WITH_SPACE }
private final ModificationType modificationType;
protected AbstractStandardMultipleAttributeModifierTagProcessor(
final TemplateMode templateMode, final String dialectPrefix,
final String attrName, final int precedence,
final ModificationType modificationType) {
super(templateMode, dialectPrefix, null, false, attrName, true, precedence, true);
this.modificationType = modificationType;
}
@Override
protected final void doProcess(
final ITemplateContext context,
final IProcessableElementTag tag,
final AttributeName attributeName, final String attributeValue,
final IElementTagStructureHandler structureHandler) {
final AssignationSequence assignations =
AssignationUtils.parseAssignationSequence(
context, attributeValue, false /* no parameters without value */);
if (assignations == null) {
throw new TemplateProcessingException(
"Could not parse value as attribute assignations: \"" + attributeValue + "\"");
}
final List<Assignation> assignationValues = assignations.getAssignations();
final int assignationValuesLen = assignationValues.size();
for (int i = 0; i < assignationValuesLen; i++) {
final Assignation assignation = assignationValues.get(i);
final IStandardExpression leftExpr = assignation.getLeft();
final Object leftValue = leftExpr.execute(context);
final IStandardExpression rightExpr = assignation.getRight();
final Object rightValue = rightExpr.execute(context);
if (rightValue == NoOpToken.VALUE) {
// No changes to be done for this attribute
continue;
}
final String newAttributeName = (leftValue == null? null : leftValue.toString());
if (StringUtils.isEmptyOrWhitespace(newAttributeName)) {
throw new TemplateProcessingException(
"Attribute name expression evaluated as null or empty: \"" + leftExpr + "\"");
}
if (getTemplateMode() == TemplateMode.HTML &&
this.modificationType == ModificationType.SUBSTITUTION &&
ArrayUtils.contains(StandardConditionalFixedValueTagProcessor.ATTR_NAMES, newAttributeName)) {
// Attribute is a fixed-value conditional one, like "selected", which can only
// appear as selected="selected" or not appear at all.
if (EvaluationUtils.evaluateAsBoolean(rightValue)) {
structureHandler.setAttribute(newAttributeName, newAttributeName);
} else {
structureHandler.removeAttribute(newAttributeName);
}
} else {
// Attribute is a "normal" attribute, not a fixed-value conditional one - or we are not just replacing
final String newAttributeValue = HtmlEscape.escapeHtml4Xml(rightValue == null ? null : rightValue.toString());
if (newAttributeValue == null || newAttributeValue.length() == 0) {
if (this.modificationType == ModificationType.SUBSTITUTION) {
// Substituting by a no-value will be equivalent to simply removing
structureHandler.removeAttribute(newAttributeName);
}
// Prepend and append simply ignored in this case
} else {
if (this.modificationType == ModificationType.SUBSTITUTION ||
!tag.hasAttribute(newAttributeName) ||
tag.getAttributeValue(newAttributeName).length() == 0) {
// Normal value replace
structureHandler.setAttribute(newAttributeName, newAttributeValue);
} else {
String currentValue = tag.getAttributeValue(newAttributeName);
if (this.modificationType == ModificationType.APPEND) {
structureHandler.setAttribute(newAttributeName, currentValue + newAttributeValue);
} else if (this.modificationType == ModificationType.APPEND_WITH_SPACE) {
structureHandler.setAttribute(newAttributeName, currentValue + ' ' + newAttributeValue);
} else if (this.modificationType == ModificationType.PREPEND) {
structureHandler.setAttribute(newAttributeName, newAttributeValue + currentValue);
} else { // modification type is PREPEND_WITH_SPACE
structureHandler.setAttribute(newAttributeName, newAttributeValue + ' ' + currentValue);
}
}
}
}
}
}
}