/**********************************************************************
* Copyright (c) 2014 HubSpot 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.hubspot.jinjava.tree;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_EXPR_START;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_FIXED;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_NOTE;
import static com.hubspot.jinjava.tree.parse.TokenScannerSymbols.TOKEN_TAG;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.hubspot.jinjava.interpret.DisabledException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.MissingEndTagException;
import com.hubspot.jinjava.interpret.TemplateError;
import com.hubspot.jinjava.interpret.TemplateError.ErrorItem;
import com.hubspot.jinjava.interpret.TemplateError.ErrorReason;
import com.hubspot.jinjava.interpret.TemplateError.ErrorType;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import com.hubspot.jinjava.interpret.UnexpectedTokenException;
import com.hubspot.jinjava.interpret.UnknownTagException;
import com.hubspot.jinjava.lib.tag.EndTag;
import com.hubspot.jinjava.lib.tag.Tag;
import com.hubspot.jinjava.tree.parse.ExpressionToken;
import com.hubspot.jinjava.tree.parse.TagToken;
import com.hubspot.jinjava.tree.parse.TextToken;
import com.hubspot.jinjava.tree.parse.Token;
import com.hubspot.jinjava.tree.parse.TokenScanner;
public class TreeParser {
private final PeekingIterator<Token> scanner;
private final JinjavaInterpreter interpreter;
private Node parent;
public TreeParser(JinjavaInterpreter interpreter, String input) {
this.scanner = Iterators.peekingIterator(new TokenScanner(input, interpreter.getConfig()));
this.interpreter = interpreter;
}
public Node buildTree() {
Node root = new RootNode();
parent = root;
while (scanner.hasNext()) {
Node node = nextNode();
if (node != null) {
parent.getChildren().add(node);
}
}
if (parent != root) {
interpreter.addError(TemplateError.fromException(
new MissingEndTagException(((TagNode) parent).getEndName(),
parent.getMaster().getImage(),
parent.getLineNumber())));
}
return root;
}
/**
* @return null if EOF or error
*/
private Node nextNode() {
Token token = scanner.next();
switch (token.getType()) {
case TOKEN_FIXED:
return text((TextToken) token);
case TOKEN_EXPR_START:
return expression((ExpressionToken) token);
case TOKEN_TAG:
return tag((TagToken) token);
case TOKEN_NOTE:
break;
default:
interpreter.addError(TemplateError.fromException(new UnexpectedTokenException(token.getImage(),
token.getLineNumber())));
}
return null;
}
private Node getLastSibling() {
if (parent == null || parent.getChildren().isEmpty()) {
return null;
}
return parent.getChildren().getLast();
}
private Node text(TextToken textToken) {
if (interpreter.getConfig().isLstripBlocks()) {
if (scanner.hasNext() && scanner.peek().getType() == TOKEN_TAG) {
textToken = new TextToken(StringUtils.stripEnd(textToken.getImage(), "\t "), textToken.getLineNumber());
}
}
final Node lastSibling = getLastSibling();
// if last sibling was a tag and has rightTrimAfterEnd, strip whitespace
if (lastSibling != null
&& lastSibling instanceof TagNode
&& lastSibling.getMaster().isRightTrimAfterEnd()) {
textToken.setLeftTrim(true);
}
// for first TextNode child of TagNode where rightTrim is enabled, mark it for left trim
if (parent instanceof TagNode
&& lastSibling == null
&& parent.getMaster().isRightTrim()) {
textToken.setLeftTrim(true);
}
TextNode n = new TextNode(textToken);
n.setParent(parent);
return n;
}
private Node expression(ExpressionToken expressionToken) {
ExpressionNode n = new ExpressionNode(expressionToken);
n.setParent(parent);
return n;
}
private Node tag(TagToken tagToken) {
Tag tag;
try {
tag = interpreter.getContext().getTag(tagToken.getTagName());
if (tag == null) {
interpreter.addError(TemplateError.fromException(new UnknownTagException(tagToken)));
return null;
}
} catch (DisabledException e) {
interpreter.addError(new TemplateError(ErrorType.FATAL, ErrorReason.DISABLED, ErrorItem.TAG,
e.getMessage(), tagToken.getTagName(), interpreter.getLineNumber(), e));
return null;
}
if (tag instanceof EndTag) {
endTag(tag, tagToken);
return null;
} else {
// if a tag has left trim, mark the last sibling to trim right whitespace
if (tagToken.isLeftTrim()) {
final Node lastSibling = getLastSibling();
if (lastSibling != null && lastSibling instanceof TextNode) {
lastSibling.getMaster().setRightTrim(true);
}
}
}
TagNode node = new TagNode(tag, tagToken);
node.setParent(parent);
if (node.getEndName() != null) {
parent.getChildren().add(node);
parent = node;
return null;
}
return node;
}
private void endTag(Tag tag, TagToken tagToken) {
final Node lastSibling = getLastSibling();
if (parent instanceof TagNode
&& tagToken.isLeftTrim()
&& lastSibling != null
&& lastSibling instanceof TextNode) {
lastSibling.getMaster().setRightTrim(true);
}
if (parent.getMaster() != null) { // root node
parent.getMaster().setRightTrimAfterEnd(tagToken.isRightTrim());
}
while (!(parent instanceof RootNode)) {
TagNode parentTag = (TagNode) parent;
parent = parent.getParent();
if (parentTag.getEndName().equals(tag.getEndTagName())) {
break;
} else {
interpreter.addError(TemplateError.fromException(
new TemplateSyntaxException(tagToken.getImage(),
"Mismatched end tag, expected: " + parentTag.getEndName(),
tagToken.getLineNumber())));
}
}
}
}