/* * Copyright 2010 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.collect.ImmutableList; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.basetree.CopyState; import com.google.template.soy.error.ErrorReporter.Checkpoint; 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.ExprNode; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.soytree.CommandTextAttributesParser.Attribute; import com.google.template.soy.soytree.SoyNode.ExprHolderNode; import com.google.template.soy.soytree.SoyNode.MsgSubstUnitNode; import com.google.template.soy.soytree.SoyNode.SplitLevelTopNode; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Node representing a 'plural' block. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public final class MsgPluralNode extends AbstractParentCommandNode<CaseOrDefaultNode> implements MsgSubstUnitNode, SplitLevelTopNode<CaseOrDefaultNode>, ExprHolderNode { private static final SoyErrorKind INVALID_PLURAL_COMMAND_TEXT = SoyErrorKind.of("Invalid ''plural'' command text \"{0}\"."); private static final SoyErrorKind PLURAL_OFFSET_OUT_OF_BOUNDS = SoyErrorKind.of("The ''offset'' for plural must be a nonnegative integer."); private static final SoyErrorKind MALFORMED_PLURAL_OFFSET = SoyErrorKind.of("Invalid offset in ''plural'' command text \"{0}\"."); /** An expression, and optional "offset" attribute. */ private static final Pattern COMMAND_TEXT_PATTERN = Pattern.compile("(.+?) ( \\s+ offset= .+ )?", Pattern.COMMENTS); /** Parser for the attributes in the command text. */ private static final CommandTextAttributesParser ATTRIBUTES_PARSER = new CommandTextAttributesParser( "plural", new Attribute("offset", Attribute.ALLOW_ALL_VALUES, null)); /** Fallback base plural var name. */ public static final String FALLBACK_BASE_PLURAL_VAR_NAME = "NUM"; /** The offset. */ private final int offset; /** The parsed expression. */ private final ExprRootNode pluralExpr; /** The base plural var name (what the translator sees). */ private final String basePluralVarName; private MsgPluralNode( int id, SourceLocation sourceLocation, String commandText, int offset, ExprRootNode pluralExpr, String basePluralVarName) { super(id, sourceLocation, "plural", commandText); this.offset = offset; this.pluralExpr = pluralExpr; this.basePluralVarName = basePluralVarName; } /** * Copy constructor. * * @param orig The node to copy. */ private MsgPluralNode(MsgPluralNode orig, CopyState copyState) { super(orig, copyState); this.offset = orig.offset; this.pluralExpr = orig.pluralExpr.copy(copyState); this.basePluralVarName = orig.basePluralVarName; } @Override public Kind getKind() { return Kind.MSG_PLURAL_NODE; } /** Returns the offset. */ public int getOffset() { return offset; } /** Returns the expression text. */ public String getExprText() { return pluralExpr.toSourceString(); } /** Returns the parsed expression. */ public ExprRootNode getExpr() { return pluralExpr; } /** Returns the base plural var name (what the translator sees). */ @Override public String getBaseVarName() { return basePluralVarName; } @Override public boolean shouldUseSameVarNameAs(MsgSubstUnitNode other) { return (other instanceof MsgPluralNode) && this.getCommandText().equals(((MsgPluralNode) other).getCommandText()); } @Override public ImmutableList<ExprRootNode> getExprList() { return ImmutableList.of(pluralExpr); } @Override public MsgBlockNode getParent() { return (MsgBlockNode) super.getParent(); } @Override public MsgPluralNode copy(CopyState copyState) { return new MsgPluralNode(this, copyState); } /** Builder for {@link MsgPluralNode}. */ public static final class Builder { private static MsgPluralNode error() { return new Builder(-1, "plural", SourceLocation.UNKNOWN) .build(SoyParsingContext.exploding()); // guaranteed to be valid } private final int id; private final String commandText; private final SourceLocation sourceLocation; /** * @param id The node's id. * @param commandText The node's command text. * @param sourceLocation The node's source location. */ public Builder(int id, String commandText, SourceLocation sourceLocation) { this.id = id; this.commandText = commandText; this.sourceLocation = sourceLocation; } /** * Returns a new {@link MsgPluralNode} built from the builder's state. If the builder's state is * invalid, errors are reported to {@code errorReporter} and {@link Builder#error} is returned. */ public MsgPluralNode build(SoyParsingContext context) { Checkpoint checkpoint = context.errorReporter().checkpoint(); Matcher matcher = COMMAND_TEXT_PATTERN.matcher(commandText); if (!matcher.matches()) { context.report(sourceLocation, INVALID_PLURAL_COMMAND_TEXT, commandText); } ExprNode pluralExpr = new ExpressionParser(matcher.group(1), sourceLocation, context).parseExpression(); int offset = 0; // If attributes were given, parse them. if (matcher.group(2) != null) { try { Map<String, String> attributes = ATTRIBUTES_PARSER.parse(matcher.group(2).trim(), context, sourceLocation); String offsetAttribute = attributes.get("offset"); offset = Integer.parseInt(offsetAttribute); if (offset < 0) { context.report(sourceLocation, PLURAL_OFFSET_OUT_OF_BOUNDS); } } catch (NumberFormatException nfe) { context.report(sourceLocation, MALFORMED_PLURAL_OFFSET, commandText); } } String basePluralVarName = MsgSubstUnitBaseVarNameUtils.genNaiveBaseNameForExpr( pluralExpr, FALLBACK_BASE_PLURAL_VAR_NAME); if (context.errorReporter().errorsSince(checkpoint)) { return error(); } return new MsgPluralNode( id, sourceLocation, commandText, offset, new ExprRootNode(pluralExpr), basePluralVarName); } } }