/* * Copyright 2013 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 static com.google.common.base.Preconditions.checkState; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.base.internal.IdGenerator; import com.google.template.soy.basetree.CopyState; import com.google.template.soy.exprparse.SoyParsingContext; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.exprtree.VarDefn; import com.google.template.soy.exprtree.VarRefNode; import com.google.template.soy.soytree.SoyNode.SplitLevelTopNode; import com.google.template.soy.soytree.SoyNode.StandaloneNode; import com.google.template.soy.soytree.SoyNode.StatementNode; import javax.annotation.Nullable; /** * Represents one message or a pair of message and fallback message. * * <p>Only one {@code fallbackmsg} is allowed by the parser. {@link * com.google.template.soy.soyparse.TemplateParserTest.java#testRecognizeCommands} TODO(user): * fix the grammar. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>All children are {@code MsgNode}s. And conversely, all {@code MsgNode}s must be children of * {@code MsgFallbackGroupNode}s through parsing and middleend passes (backends may have their own * special structure for messages). * */ public final class MsgFallbackGroupNode extends AbstractParentSoyNode<MsgNode> implements StandaloneNode, SplitLevelTopNode<MsgNode>, StatementNode { /** * Escaping directives names (including the vertical bar) to apply to the return value. With * strict autoescape, the result of each call site is escaped, which is potentially a no-op if the * template's return value is the correct SanitizedContent object. */ private ImmutableList<String> escapingDirectiveNames = ImmutableList.of(); @Nullable private HtmlContext htmlContext; /** * @param id The id for this node. * @param sourceLocation The node's source location. */ public MsgFallbackGroupNode(int id, SourceLocation sourceLocation) { super(id, sourceLocation); } /** * Copy constructor. * * @param orig The node to copy. */ private MsgFallbackGroupNode(MsgFallbackGroupNode orig, CopyState copyState) { super(orig, copyState); this.escapingDirectiveNames = orig.escapingDirectiveNames; this.htmlContext = orig.htmlContext; } /** * Gets the HTML context (typically tag, attribute value, HTML PCDATA, or plain text) which this * node appears in. This affects how the node is escaped (for traditional backends) or how it's * passed to incremental DOM APIs. */ public HtmlContext getHtmlContext() { return Preconditions.checkNotNull( htmlContext, "Cannot access HtmlContext before HtmlTransformVisitor"); } public void setHtmlContext(HtmlContext value) { this.htmlContext = value; } /** Creates a print node that corresponds to this node, for tree rewriting. */ public PrintNode makePrintNode(IdGenerator nodeIdGen, VarDefn var) { PrintNode printNode = new PrintNode.Builder(nodeIdGen.genId(), true /* implicit */, getSourceLocation()) .exprRoot( new ExprRootNode( new VarRefNode(var.name(), getSourceLocation(), false /* not ij */, var))) .build(SoyParsingContext.exploding()); printNode.setHtmlContext(htmlContext); for (String escapingDirective : getEscapingDirectiveNames()) { printNode.addChild( new PrintDirectiveNode.Builder( nodeIdGen.genId(), escapingDirective, "" /* argsText */, getSourceLocation()) .build(SoyParsingContext.exploding())); } return printNode; } @Override public Kind getKind() { return Kind.MSG_FALLBACK_GROUP_NODE; } @Override public String toSourceString() { StringBuilder sb = new StringBuilder(); // Note: The first MsgNode takes care of generating the 'msg' tag. appendSourceStringForChildren(sb); sb.append("{/msg}"); return sb.toString(); } @SuppressWarnings("unchecked") @Override public ParentSoyNode<StandaloneNode> getParent() { return (ParentSoyNode<StandaloneNode>) super.getParent(); } public boolean hasFallbackMsg() { return numChildren() > 1; } public MsgNode getMsg() { return getChild(0); } public MsgNode getFallbackMsg() { checkState(hasFallbackMsg(), "This node doesn't have a {fallbackmsg}"); return getChild(1); } @Override public MsgFallbackGroupNode copy(CopyState copyState) { return new MsgFallbackGroupNode(this, copyState); } /** Sets the inferred escaping directives from the contextual engine. */ public void setEscapingDirectiveNames(ImmutableList<String> escapingDirectiveNames) { this.escapingDirectiveNames = escapingDirectiveNames; } /** * Returns the escaping directives, applied from left to right. * * <p>It is an error to call this before the contextual rewriter has been run. */ public ImmutableList<String> getEscapingDirectiveNames() { return escapingDirectiveNames; } }