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.
This commit is contained in:
Michael Bayne
2011-08-29 14:44:17 -07:00
parent a3eb9e22e1
commit 4367ff6784
3 changed files with 30 additions and 17 deletions
+2 -2
View File
@@ -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}}
@@ -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(
@@ -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);
}