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:
@@ -276,8 +276,8 @@ By taking advantage of reflection and bean-property-style lookups, you can do ko
|
|||||||
Newline trimming
|
Newline trimming
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Newlines immediately following the opening or closing section tag are trimmed.
|
If the opening or closing section tag are the only thing on a line, any newline
|
||||||
This allows for civilized templates, like:
|
following the tag is trimmed. This allows for civilized templates, like:
|
||||||
|
|
||||||
Favorite foods:
|
Favorite foods:
|
||||||
{{#people}}
|
{{#people}}
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ public class Mustache
|
|||||||
Accumulator accum;
|
Accumulator accum;
|
||||||
|
|
||||||
int state = TEXT;
|
int state = TEXT;
|
||||||
int line = 1;
|
int line = 1, column = 0;
|
||||||
|
int tagStartColumn = -1;
|
||||||
boolean skipNewline = false;
|
boolean skipNewline = false;
|
||||||
|
|
||||||
public Parser (Compiler compiler) {
|
public Parser (Compiler compiler) {
|
||||||
@@ -206,14 +207,15 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
|
column = 0;
|
||||||
line++;
|
line++;
|
||||||
// if we just parsed an open section or close section task, we'll skip the first
|
// skip this newline character if we're configured to do so; TODO: handle CR
|
||||||
// newline character following it, if desired; TODO: handle CR, sigh
|
|
||||||
if (skipNewline) {
|
if (skipNewline) {
|
||||||
skipNewline = false;
|
skipNewline = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
column++;
|
||||||
skipNewline = false;
|
skipNewline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +246,7 @@ public class Mustache
|
|||||||
case TEXT:
|
case TEXT:
|
||||||
if (c == delims.start1) {
|
if (c == delims.start1) {
|
||||||
state = MATCHING_START;
|
state = MATCHING_START;
|
||||||
|
tagStartColumn = column;
|
||||||
if (delims.start2 == NO_CHAR) {
|
if (delims.start2 == NO_CHAR) {
|
||||||
parseChar(NO_CHAR);
|
parseChar(NO_CHAR);
|
||||||
}
|
}
|
||||||
@@ -276,6 +279,7 @@ public class Mustache
|
|||||||
// plain text and start matching a new tag from this point
|
// plain text and start matching a new tag from this point
|
||||||
restoreStartTag(text, delims);
|
restoreStartTag(text, delims);
|
||||||
accum.addTextSegment(text);
|
accum.addTextSegment(text);
|
||||||
|
tagStartColumn = column;
|
||||||
if (delims.start2 == NO_CHAR) {
|
if (delims.start2 == NO_CHAR) {
|
||||||
accum.addTextSegment(text);
|
accum.addTextSegment(text);
|
||||||
state = TAG;
|
state = TAG;
|
||||||
@@ -313,7 +317,7 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
// process the tag between the mustaches
|
// process the tag between the mustaches
|
||||||
accum = accum.addTagSegment(text, line);
|
accum = accum.addTagSegment(text, line);
|
||||||
skipNewline = accum.skipNewline();
|
skipNewline = (tagStartColumn == 1) && accum.justOpenedOrClosedCompound();
|
||||||
}
|
}
|
||||||
state = TEXT;
|
state = TEXT;
|
||||||
|
|
||||||
@@ -377,10 +381,9 @@ public class Mustache
|
|||||||
_compiler = compiler;
|
_compiler = compiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean skipNewline () {
|
public boolean justOpenedOrClosedCompound () {
|
||||||
// return true if we just added a compound segment which means we're immediately
|
// return true if we just closed a compound segment; we'll handle just opened elsewhere
|
||||||
// following the close section tag
|
return (!_segs.isEmpty() && _segs.get(_segs.size()-1) instanceof CompoundSegment);
|
||||||
return (_segs.size() > 0 && _segs.get(_segs.size()-1) instanceof CompoundSegment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTextSegment (StringBuilder text) {
|
public void addTextSegment (StringBuilder text) {
|
||||||
@@ -400,9 +403,9 @@ public class Mustache
|
|||||||
case '#':
|
case '#':
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
return new Accumulator(_compiler) {
|
return new Accumulator(_compiler) {
|
||||||
@Override public boolean skipNewline () {
|
@Override public boolean justOpenedOrClosedCompound () {
|
||||||
// if we just opened this section, we want to skip a newline
|
// if we just opened this section, we'll have no segments
|
||||||
return (_segs.size() == 0) || super.skipNewline();
|
return (_segs.isEmpty()) || super.justOpenedOrClosedCompound();
|
||||||
}
|
}
|
||||||
@Override public Template.Segment[] finish () {
|
@Override public Template.Segment[] finish () {
|
||||||
throw new MustacheParseException(
|
throw new MustacheParseException(
|
||||||
@@ -422,9 +425,9 @@ public class Mustache
|
|||||||
case '^':
|
case '^':
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
return new Accumulator(_compiler) {
|
return new Accumulator(_compiler) {
|
||||||
@Override public boolean skipNewline () {
|
@Override public boolean justOpenedOrClosedCompound () {
|
||||||
// if we just opened this section, we want to skip a newline
|
// if we just opened this section, we'll have no segments
|
||||||
return (_segs.size() == 0) || super.skipNewline();
|
return (_segs.isEmpty()) || super.justOpenedOrClosedCompound();
|
||||||
}
|
}
|
||||||
@Override public Template.Segment[] finish () {
|
@Override public Template.Segment[] finish () {
|
||||||
throw new MustacheParseException(
|
throw new MustacheParseException(
|
||||||
|
|||||||
@@ -306,6 +306,16 @@ public class MustacheTest
|
|||||||
"endlist", tmpl, context("items", Collections.emptyList()));
|
"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 public void testNestedContexts () {
|
||||||
test("foo((foobar)(foobaz))", "{{name}}({{#things}}({{name}}{{thing_name}}){{/things}})",
|
test("foo((foobar)(foobaz))", "{{name}}({{#things}}({{name}}{{thing_name}}){{/things}})",
|
||||||
context("name", "foo",
|
context("name", "foo",
|
||||||
@@ -352,7 +362,7 @@ public class MustacheTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testStandardsModeWithNullValuesInLoop () {
|
@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());
|
String result = Mustache.compiler().standardsMode(true).compile(tmpl).execute(new Object());
|
||||||
assertEquals("first line\nsecond line", result);
|
assertEquals("first line\nsecond line", result);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user