From 4367ff67847bfc3dee4306b101470b5a9da997ba Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Mon, 29 Aug 2011 14:44:17 -0700 Subject: [PATCH] Only skip newlines if a tag is the only thing on the line. Otherwise you get undesirable newline skipping if you do things like, for example: {{#foos}} {{bar}}{{^-last}}, {{/-last}} {{/foos}} where you want the newline after {{/-last}}, but it was getting skipped. --- README.md | 4 +-- .../com/samskivert/mustache/Mustache.java | 31 ++++++++++--------- .../com/samskivert/mustache/MustacheTest.java | 12 ++++++- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 831be14..8f3658f 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,8 @@ By taking advantage of reflection and bean-property-style lookups, you can do ko Newline trimming ---------------- -Newlines immediately following the opening or closing section tag are trimmed. -This allows for civilized templates, like: +If the opening or closing section tag are the only thing on a line, any newline +following the tag is trimmed. This allows for civilized templates, like: Favorite foods: {{#people}} diff --git a/src/main/java/com/samskivert/mustache/Mustache.java b/src/main/java/com/samskivert/mustache/Mustache.java index 71fc13e..812da94 100644 --- a/src/main/java/com/samskivert/mustache/Mustache.java +++ b/src/main/java/com/samskivert/mustache/Mustache.java @@ -183,7 +183,8 @@ public class Mustache Accumulator accum; int state = TEXT; - int line = 1; + int line = 1, column = 0; + int tagStartColumn = -1; boolean skipNewline = false; public Parser (Compiler compiler) { @@ -206,14 +207,15 @@ public class Mustache } if (c == '\n') { + column = 0; line++; - // if we just parsed an open section or close section task, we'll skip the first - // newline character following it, if desired; TODO: handle CR, sigh + // skip this newline character if we're configured to do so; TODO: handle CR if (skipNewline) { skipNewline = false; continue; } } else { + column++; skipNewline = false; } @@ -244,6 +246,7 @@ public class Mustache case TEXT: if (c == delims.start1) { state = MATCHING_START; + tagStartColumn = column; if (delims.start2 == NO_CHAR) { parseChar(NO_CHAR); } @@ -276,6 +279,7 @@ public class Mustache // plain text and start matching a new tag from this point restoreStartTag(text, delims); accum.addTextSegment(text); + tagStartColumn = column; if (delims.start2 == NO_CHAR) { accum.addTextSegment(text); state = TAG; @@ -313,7 +317,7 @@ public class Mustache } // process the tag between the mustaches accum = accum.addTagSegment(text, line); - skipNewline = accum.skipNewline(); + skipNewline = (tagStartColumn == 1) && accum.justOpenedOrClosedCompound(); } state = TEXT; @@ -377,10 +381,9 @@ public class Mustache _compiler = compiler; } - public boolean skipNewline () { - // return true if we just added a compound segment which means we're immediately - // following the close section tag - return (_segs.size() > 0 && _segs.get(_segs.size()-1) instanceof CompoundSegment); + public boolean justOpenedOrClosedCompound () { + // return true if we just closed a compound segment; we'll handle just opened elsewhere + return (!_segs.isEmpty() && _segs.get(_segs.size()-1) instanceof CompoundSegment); } public void addTextSegment (StringBuilder text) { @@ -400,9 +403,9 @@ public class Mustache case '#': requireNoNewlines(tag, tagLine); return new Accumulator(_compiler) { - @Override public boolean skipNewline () { - // if we just opened this section, we want to skip a newline - return (_segs.size() == 0) || super.skipNewline(); + @Override public boolean justOpenedOrClosedCompound () { + // if we just opened this section, we'll have no segments + return (_segs.isEmpty()) || super.justOpenedOrClosedCompound(); } @Override public Template.Segment[] finish () { throw new MustacheParseException( @@ -422,9 +425,9 @@ public class Mustache case '^': requireNoNewlines(tag, tagLine); return new Accumulator(_compiler) { - @Override public boolean skipNewline () { - // if we just opened this section, we want to skip a newline - return (_segs.size() == 0) || super.skipNewline(); + @Override public boolean justOpenedOrClosedCompound () { + // if we just opened this section, we'll have no segments + return (_segs.isEmpty()) || super.justOpenedOrClosedCompound(); } @Override public Template.Segment[] finish () { throw new MustacheParseException( diff --git a/src/test/java/com/samskivert/mustache/MustacheTest.java b/src/test/java/com/samskivert/mustache/MustacheTest.java index 5361b5b..d46a5c7 100644 --- a/src/test/java/com/samskivert/mustache/MustacheTest.java +++ b/src/test/java/com/samskivert/mustache/MustacheTest.java @@ -306,6 +306,16 @@ public class MustacheTest "endlist", tmpl, context("items", Collections.emptyList())); } + @Test public void testNewlineNonSkipping () { + // only when a section tag is by itself on a line should we absorb the newline following it + String tmpl = "thing?: {{#thing}}yes{{/thing}}{{^thing}}no{{/thing}}\n" + + "that's nice"; + test("thing?: yes\n" + + "that's nice", tmpl, context("thing", true)); + test("thing?: no\n" + + "that's nice", tmpl, context("thing", false)); + } + @Test public void testNestedContexts () { test("foo((foobar)(foobaz))", "{{name}}({{#things}}({{name}}{{thing_name}}){{/things}})", context("name", "foo", @@ -352,7 +362,7 @@ public class MustacheTest } @Test public void testStandardsModeWithNullValuesInLoop () { - String tmpl = "first line\n{{#nonexistent}}foo{{/nonexistent}}\nsecond line"; + String tmpl = "first line\n{{#nonexistent}}foo\n{{/nonexistent}}\nsecond line"; String result = Mustache.compiler().standardsMode(true).compile(tmpl).execute(new Object()); assertEquals("first line\nsecond line", result); }