/*
* Copyright 2009 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.passes;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.RawTextNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
/**
* Visitor for combining any consecutive sequences of {@code RawTextNode}s into one equivalent
* {@code RawTextNode}.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public final class CombineConsecutiveRawTextNodesVisitor extends AbstractSoyNodeVisitor<Void> {
/** The node id generator for the parse tree. Retrieved from the root SoyFileSetNode. */
private final IdGenerator nodeIdGen;
public CombineConsecutiveRawTextNodesVisitor(IdGenerator nodeIdGen) {
this.nodeIdGen = nodeIdGen;
}
@Override
protected void visitSoyNode(SoyNode node) {
if (!(node instanceof ParentSoyNode<?>)) {
return;
}
ParentSoyNode<?> nodeAsParent = (ParentSoyNode<?>) node;
// where the most recent sequence of raw text nodes starts
int rawTextSeqStart = -1;
for (int i = 0; i < nodeAsParent.numChildren(); i++) {
SoyNode child = nodeAsParent.getChild(i);
if (child instanceof RawTextNode) {
if (rawTextSeqStart == -1) {
rawTextSeqStart = i;
}
// the next node is not a raw text node (or we are at the end)
if (i == nodeAsParent.numChildren() - 1
|| !(nodeAsParent.getChild(i + 1) instanceof RawTextNode)) {
// we have more than one raw text node, combine them
if (rawTextSeqStart < i) {
// This is safe because we already know it has RawTextNodes as children
@SuppressWarnings("unchecked")
ParentSoyNode<? super RawTextNode> typedParent =
(ParentSoyNode<? super RawTextNode>) nodeAsParent;
combineRawTextNodes(typedParent, rawTextSeqStart, i + 1);
// We just replaced [rawTextSeqStart, i] with a single node at rawTextSeqStart
// so reset i to be rawTextSeqStart so that on the next loop iteration, we move on to
// the next item. In other words, the item that was previously at i+1 is now at
// rawTextSeqStart+1.
i = rawTextSeqStart;
} else {
// exactly one node, is it empty?
if (((RawTextNode) child).isEmpty()) {
nodeAsParent.removeChild(i);
i--; // move back
}
}
// reset the start of the sequence
rawTextSeqStart = -1;
}
} else {
visit(child); // recurse
}
}
}
/**
* Collapses a range of RawTextNodes between start and end into a single raw text node using
* {@link RawTextNode#concat(int, RawTextNode)}
*/
private void combineRawTextNodes(ParentSoyNode<? super RawTextNode> parent, int start, int end) {
// Since we know this parent had raw text nodes as children it must be able to handle
// standalone nodes.
// We have just finished a sequence of raw text nodes that is more than one item
int newId = nodeIdGen.genId();
RawTextNode newNode = (RawTextNode) parent.getChild(start);
for (int i = start + 1; i < end; i++) {
newNode = newNode.concat(newId, (RawTextNode) parent.getChild(i));
}
// it is slightly more efficient to remove in reverse order
// TODO(lukes): add a 'removeRange' method, it would be faster
for (int i = end - 1; i > start; i--) {
parent.removeChild(i);
}
parent.replaceChild(start, newNode);
}
}