Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

respect '-' in tags to eliminate whitespace #89

Merged
merged 5 commits into from
Nov 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 62 additions & 14 deletions src/main/java/com/hubspot/jinjava/tree/TreeParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ public Node buildTree() {

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())));
new MissingEndTagException(((TagNode) parent).getEndName(),
parent.getMaster().getImage(),
parent.getLineNumber())));
}

return root;
Expand All @@ -73,36 +76,60 @@ public Node buildTree() {
/**
* @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_FIXED:
return text((TextToken) token);

case TOKEN_EXPR_START:
return expression((ExpressionToken) token);
case TOKEN_EXPR_START:
return expression((ExpressionToken) token);

case TOKEN_TAG:
return tag((TagToken) token);
case TOKEN_TAG:
return tag((TagToken) token);

case TOKEN_NOTE:
break;
case TOKEN_NOTE:
break;

default:
interpreter.addError(TemplateError.fromException(new UnexpectedTokenException(token.getImage(), token.getLineNumber())));
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;
Expand All @@ -124,6 +151,14 @@ private Node tag(TagToken tagToken) {
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);
Expand All @@ -139,6 +174,18 @@ private Node tag(TagToken tagToken) {
}

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);
}

parent.getMaster().setRightTrimAfterEnd(tagToken.isRightTrim());

while (!(parent instanceof RootNode)) {
TagNode parentTag = (TagNode) parent;
parent = parent.getParent();
Expand All @@ -147,9 +194,10 @@ private void endTag(Tag tag, TagToken tagToken) {
break;
} else {
interpreter.addError(TemplateError.fromException(
new TemplateSyntaxException(tagToken.getImage(), "Mismatched end tag, expected: " + parentTag.getEndName(), tagToken.getLineNumber())));
new TemplateSyntaxException(tagToken.getImage(),
"Mismatched end tag, expected: " + parentTag.getEndName(),
tagToken.getLineNumber())));
}
}
}

}
8 changes: 8 additions & 0 deletions src/main/java/com/hubspot/jinjava/tree/parse/TextToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public String trim() {
}

public String output() {

if (isLeftTrim() && isRightTrim()) {
return trim();
} else if (isLeftTrim()) {
return StringUtils.stripStart(content, null);
} else if (isRightTrim()) {
return StringUtils.stripEnd(content, null);
}
return content;
}

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/hubspot/jinjava/tree/parse/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public abstract class Token implements Serializable {

private boolean leftTrim;
private boolean rightTrim;
private boolean rightTrimAfterEnd;

public Token(String image, int lineNumber) {
this.image = image;
Expand All @@ -59,6 +60,10 @@ public boolean isRightTrim() {
return rightTrim;
}

public boolean isRightTrimAfterEnd() {
return rightTrimAfterEnd;
}

public void setLeftTrim(boolean leftTrim) {
this.leftTrim = leftTrim;
}
Expand All @@ -67,6 +72,10 @@ public void setRightTrim(boolean rightTrim) {
this.rightTrim = rightTrim;
}

public void setRightTrimAfterEnd(boolean rightTrimAfterEnd) {
this.rightTrimAfterEnd = rightTrimAfterEnd;
}

@Override
public String toString() {
return image;
Expand Down
49 changes: 49 additions & 0 deletions src/test/java/com/hubspot/jinjava/tree/TreeParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,55 @@ public void parseHtmlWithCommentLines() {
assertThat(interpreter.getErrors()).isEmpty();
}

@Test
public void itStripsRightWhiteSpace() throws Exception {
String expression = "{% for foo in [1,2,3] -%} \n .{{ foo }}\n{% endfor %}";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo(".1\n.2\n.3\n");
}

@Test
public void itStripsLeftWhiteSpace() throws Exception {
String expression = "{% for foo in [1,2,3] %}\n{{ foo }}. \n {%- endfor %}";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo("\n1.\n2.\n3.");
}

@Test
public void itStripsLeftAndRightWhiteSpace() throws Exception {
String expression = "{% for foo in [1,2,3] -%} \n .{{ foo }}. \n {%- endfor %}";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo(".1..2..3.");
}

@Test
public void itPreservesInnerWhiteSpace() throws Exception {
String expression = "{% for foo in [1,2,3] -%}\nL{% if true %}\n{{ foo }}\n{% endif %}R\n{%- endfor %}";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo("L\n1\nRL\n2\nRL\n3\nR");
}

@Test
public void itStripsLeftWhiteSpaceBeforeTag() throws Exception {
String expression = ".\n {%- for foo in [1,2,3] %} {{ foo }} {% endfor %} \n.";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo(". 1 2 3 \n.");
}

@Test
public void itStripsRightWhiteSpaceAfterTag() throws Exception {
String expression = ".\n {% for foo in [1,2,3] %} {{ foo }} {% endfor -%} \n.";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo(".\n 1 2 3 .");
}

@Test
public void itStripsAllOuterWhiteSpace() throws Exception {
String expression = ".\n {%- for foo in [1,2,3] -%} {{ foo }} {%- endfor -%} \n.";
final Node tree = new TreeParser(interpreter, expression).buildTree();
assertThat(interpreter.render(tree)).isEqualTo(".123.");
}

@Test
public void trimAndLstripBlocks() {
interpreter = new Jinjava(JinjavaConfig.newBuilder().withLstripBlocks(true).withTrimBlocks(true).build()).newInterpreter();
Expand Down