/*
* Copyright 2015 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.soyparse;
import static com.google.common.base.Preconditions.checkState;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.IdGenerator;
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.ForeachIfemptyNode;
import com.google.template.soy.soytree.ForeachNode;
import com.google.template.soy.soytree.ForeachNonemptyNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper for building {@link ForeachNode}s and its two immediate children: {@link
* ForeachNonemptyNode} and {@link ForeachIfemptyNode}.
*/
final class ForeachBuilder {
static final SoyErrorKind INVALID_COMMAND_TEXT =
SoyErrorKind.of("Invalid ''foreach'' command text \"{0}\".");
/** Regex pattern for the command text. */
// 2 capturing groups: local var name, expression
private static final Pattern FOR_EACH_COMMAND_TEXT_PATTERN =
Pattern.compile("( [$] \\w+ ) \\s+ in \\s+ (\\S .*)", Pattern.COMMENTS | Pattern.DOTALL);
static ForeachBuilder create(IdGenerator nodeIdGen, SoyParsingContext context) {
return new ForeachBuilder(nodeIdGen, context);
}
private final IdGenerator nodeIdGen;
private final SoyParsingContext context;
private String cmdText;
private List<StandaloneNode> templateBlock;
private SourceLocation ifEmptyLocation;
private List<StandaloneNode> ifEmptyBlock;
private SourceLocation commandLocation;
private ForeachBuilder(IdGenerator nodeIdGen, SoyParsingContext context) {
this.nodeIdGen = nodeIdGen;
this.context = context;
}
ForeachBuilder setCommandLocation(SourceLocation location) {
this.commandLocation = location;
return this;
}
ForeachBuilder setCommandText(String cmdText) {
this.cmdText = cmdText;
return this;
}
ForeachBuilder setLoopBody(List<StandaloneNode> templateBlock) {
this.templateBlock = templateBlock;
return this;
}
ForeachBuilder setIfEmptyBody(SourceLocation ifEmptyLocation, List<StandaloneNode> ifEmptyBlock) {
this.ifEmptyLocation = ifEmptyLocation;
this.ifEmptyBlock = ifEmptyBlock;
return this;
}
ForeachNode build() {
checkState(cmdText != null, "You must call .setCommandText()");
checkState(commandLocation != null, "You must call .setCommandLocation()");
checkState(templateBlock != null, "You must call .setLoopBody()");
String varName = "__error__";
ExprRootNode expr = null;
Matcher matcher = FOR_EACH_COMMAND_TEXT_PATTERN.matcher(cmdText);
if (!matcher.matches()) {
context.report(commandLocation, INVALID_COMMAND_TEXT, cmdText);
} else {
varName =
new ExpressionParser(matcher.group(1), commandLocation, context)
.parseVariable()
.getName();
expr =
new ExprRootNode(
new ExpressionParser(matcher.group(2), commandLocation, context).parseExpression());
}
ForeachNode foreach = new ForeachNode(nodeIdGen.genId(), expr, cmdText, commandLocation);
ForeachNonemptyNode nonEmpty =
new ForeachNonemptyNode(nodeIdGen.genId(), varName, commandLocation);
nonEmpty.addChildren(templateBlock);
foreach.addChild(nonEmpty);
if (ifEmptyBlock != null) {
ForeachIfemptyNode ifEmpty = new ForeachIfemptyNode(nodeIdGen.genId(), ifEmptyLocation);
ifEmpty.addChildren(ifEmptyBlock);
foreach.addChild(ifEmpty);
}
return foreach;
}
}