/*
* Copyright 2008 Google Inc.
*
* 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 com.google.template.soy.soytree;
import com.google.common.base.Preconditions;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.data.internalutils.NodeContentKinds;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprparse.ExpressionParser;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.soytree.CommandTextAttributesParser.Attribute;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/**
* Abstract node representing a 'param'.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public abstract class CallParamNode extends AbstractCommandNode {
private static final SoyErrorKind INVALID_COMMAND_TEXT =
SoyErrorKind.of("Invalid param command text \"{0}\"");
/** Return value for {@code parseCommandTextHelper()}. */
public static final class CommandTextParseResult {
final String originalCommantText;
/** The parsed key. */
final String key;
/** The parsed value expr, or null if none. */
@Nullable final ExprRootNode valueExpr;
/** The parsed param's content kind, or null if none. */
@Nullable public final ContentKind contentKind;
private CommandTextParseResult(
String originalCommantText,
String key,
@Nullable ExprRootNode valueExpr,
@Nullable ContentKind contentKind) {
this.originalCommantText = originalCommantText;
this.key = key;
this.valueExpr = valueExpr;
this.contentKind = contentKind;
}
}
/** Pattern for a key plus optional value or attributes (but not both). */
//Note: group 1 = key, group 2 = value (or null), group 3 = trailing attributes (or null).
private static final Pattern NONATTRIBUTE_COMMAND_TEXT =
Pattern.compile(
"^ \\s* (\\w+) (?: \\s* : \\s* (\\S .*) | \\s* (\\S .*) )? $",
Pattern.COMMENTS | Pattern.DOTALL);
/** Parser for the command text. */
private static final CommandTextAttributesParser ATTRIBUTES_PARSER =
new CommandTextAttributesParser(
"param", new Attribute("kind", NodeContentKinds.getAttributeValues(), null));
/**
* @param id The id for this node.
* @param sourceLocation The node's source location.
* @param commandText The command text.
*/
protected CallParamNode(int id, SourceLocation sourceLocation, String commandText) {
super(id, sourceLocation, "param", commandText);
}
/**
* Copy constructor.
*
* @param orig The node to copy.
*/
protected CallParamNode(CallParamNode orig, CopyState copyState) {
super(orig, copyState);
}
/** Returns the param key. */
public abstract String getKey();
@Override
public CallNode getParent() {
return (CallNode) super.getParent();
}
/** Base class for {@link CallParamContentNode.Builder} and {@link CallParamValueNode.Builder}. */
static class Builder {
protected final int id;
protected final CommandTextParseResult parseResult;
protected final SourceLocation sourceLocation;
protected Builder(int id, CommandTextParseResult parseResult, SourceLocation sourceLocation) {
this.id = id;
this.parseResult = parseResult;
this.sourceLocation = sourceLocation;
}
}
/**
* Helper used by subclass builders to parse the command text.
*
* @return An info object containing the parse results.
*/
public static CommandTextParseResult parseCommandTextHelper(
String commandText, SoyParsingContext context, SourceLocation location) {
// Parse the command text into key and optional valueExprText or extra attributes
// TODO(user): instead of munging the command text, use a parser that understands
// the actual content.
Matcher nctMatcher = NONATTRIBUTE_COMMAND_TEXT.matcher(commandText);
if (!nctMatcher.matches()) {
context.report(location, INVALID_COMMAND_TEXT, commandText);
return new CommandTextParseResult(
commandText, "bad_key", null /* valueExpr */, null /* contentKind */);
}
// Convert {param foo : $bar/} and {param foo kind="xyz"/} syntax into attributes.
String key = nctMatcher.group(1);
// Check the validity of the key name, this will report appropriate errors to the
// reporter if it fails.
new ExpressionParser("$" + key, location, context).parseVariable();
ContentKind contentKind;
if (nctMatcher.group(3) != null) {
Preconditions.checkState(nctMatcher.group(2) == null);
Map<String, String> attributes =
ATTRIBUTES_PARSER.parse(nctMatcher.group(3), context, location);
contentKind = NodeContentKinds.forAttributeValue(attributes.get("kind"));
} else {
contentKind = null;
}
String valueExprText = nctMatcher.group(2);
if (valueExprText == null) {
return new CommandTextParseResult(commandText, key, null /* valueExpr */, contentKind);
}
ExprRootNode expr =
new ExprRootNode(new ExpressionParser(valueExprText, location, context).parseExpression());
return new CommandTextParseResult(commandText, key, expr, contentKind);
}
}