Moved falsey-ness detection into compiler.
Fixes #39. This fixes a bug where falsey values were not properly triggering inverted sections, and consolidates some logic. Also did some renaming. Couldn't help myself.
This commit is contained in:
@@ -162,6 +162,14 @@ public class Mustache
|
|||||||
return (nullValue == null) ? null : nullValue.replace("{{name}}", name);
|
return (nullValue == null) ? null : nullValue.replace("{{name}}", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if the supplied value is "falsey". If {@link #emptyStringIsFalse} is true,
|
||||||
|
* then empty strings are considered falsey. If {@link #zeroIsFalse} is true, then zero
|
||||||
|
* values are considered falsey. */
|
||||||
|
public boolean isFalsey (Object value) {
|
||||||
|
return (emptyStringIsFalse && "".equals(value)) ||
|
||||||
|
(zeroIsFalse && (value instanceof Number) && ((Number)value).longValue() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
protected Compiler (boolean escapeHTML, boolean standardsMode, String nullValue,
|
protected Compiler (boolean escapeHTML, boolean standardsMode, String nullValue,
|
||||||
boolean missingIsNull, boolean emptyStringIsFalse, boolean zeroIsFalse,
|
boolean missingIsNull, boolean emptyStringIsFalse, boolean zeroIsFalse,
|
||||||
TemplateLoader loader, Collector collector, Delims delims) {
|
TemplateLoader loader, Collector collector, Delims delims) {
|
||||||
@@ -494,7 +502,7 @@ public class Mustache
|
|||||||
|
|
||||||
protected static class Accumulator {
|
protected static class Accumulator {
|
||||||
public Accumulator (Compiler compiler) {
|
public Accumulator (Compiler compiler) {
|
||||||
_compiler = compiler;
|
_comp = compiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean justOpenedOrClosedBlock () {
|
public boolean justOpenedOrClosedBlock () {
|
||||||
@@ -518,7 +526,7 @@ public class Mustache
|
|||||||
switch (tag.charAt(0)) {
|
switch (tag.charAt(0)) {
|
||||||
case '#':
|
case '#':
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
return new Accumulator(_compiler) {
|
return new Accumulator(_comp) {
|
||||||
@Override public boolean justOpenedOrClosedBlock () {
|
@Override public boolean justOpenedOrClosedBlock () {
|
||||||
// if we just opened this section, we'll have no segments
|
// if we just opened this section, we'll have no segments
|
||||||
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
|
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
|
||||||
@@ -529,19 +537,18 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
||||||
requireSameName(tag1, itag, line);
|
requireSameName(tag1, itag, line);
|
||||||
outer._segs.add(
|
outer._segs.add(new SectionSegment(_comp, itag, super.finish(), tagLine));
|
||||||
new SectionSegment(itag, super.finish(), tagLine, _compiler));
|
|
||||||
return outer;
|
return outer;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
case '>':
|
case '>':
|
||||||
_segs.add(new IncludedTemplateSegment(tag1, _compiler));
|
_segs.add(new IncludedTemplateSegment(_comp, tag1));
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
case '^':
|
case '^':
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
return new Accumulator(_compiler) {
|
return new Accumulator(_comp) {
|
||||||
@Override public boolean justOpenedOrClosedBlock () {
|
@Override public boolean justOpenedOrClosedBlock () {
|
||||||
// if we just opened this section, we'll have no segments
|
// if we just opened this section, we'll have no segments
|
||||||
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
|
return (_segs.isEmpty()) || super.justOpenedOrClosedBlock();
|
||||||
@@ -552,7 +559,7 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
||||||
requireSameName(tag1, itag, line);
|
requireSameName(tag1, itag, line);
|
||||||
outer._segs.add(new InvertedSectionSegment(itag, super.finish(), tagLine));
|
outer._segs.add(new InvertedSegment(_comp, itag, super.finish(), tagLine));
|
||||||
return outer;
|
return outer;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -572,7 +579,7 @@ public class Mustache
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
_segs.add(new VariableSegment(tag, _compiler.escapeHTML, tagLine));
|
_segs.add(new VariableSegment(tag, _comp.escapeHTML, tagLine));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,7 +608,7 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Compiler _compiler;
|
protected final Compiler _comp;
|
||||||
protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>();
|
protected final List<Template.Segment> _segs = new ArrayList<Template.Segment>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,16 +624,16 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static class IncludedTemplateSegment extends Template.Segment {
|
protected static class IncludedTemplateSegment extends Template.Segment {
|
||||||
public IncludedTemplateSegment (String name, Compiler compiler) {
|
public IncludedTemplateSegment (Compiler compiler, String name) {
|
||||||
|
_comp = compiler;
|
||||||
_name = name;
|
_name = name;
|
||||||
_compiler = compiler;
|
|
||||||
}
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
// we compile our template lazily to avoid infinie recursion if a template includes
|
// we compile our template lazily to avoid infinie recursion if a template includes
|
||||||
// itself (see issue #13)
|
// itself (see issue #13)
|
||||||
if (_template == null) {
|
if (_template == null) {
|
||||||
try {
|
try {
|
||||||
_template = _compiler.compile(_compiler.loader.getTemplate(_name));
|
_template = _comp.compile(_comp.loader.getTemplate(_name));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e instanceof RuntimeException) {
|
if (e instanceof RuntimeException) {
|
||||||
throw (RuntimeException)e;
|
throw (RuntimeException)e;
|
||||||
@@ -639,8 +646,8 @@ public class Mustache
|
|||||||
// would happen if we just called execute() with ctx.data
|
// would happen if we just called execute() with ctx.data
|
||||||
_template.executeSegs(ctx, out);
|
_template.executeSegs(ctx, out);
|
||||||
}
|
}
|
||||||
|
protected final Compiler _comp;
|
||||||
protected final String _name;
|
protected final String _name;
|
||||||
protected final Compiler _compiler;
|
|
||||||
protected Template _template;
|
protected Template _template;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,13 +695,13 @@ public class Mustache
|
|||||||
|
|
||||||
/** A segment that represents a section. */
|
/** A segment that represents a section. */
|
||||||
protected static class SectionSegment extends BlockSegment {
|
protected static class SectionSegment extends BlockSegment {
|
||||||
public SectionSegment (String name, Template.Segment[] segs, int line, Compiler compiler) {
|
public SectionSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
|
||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
_compiler = compiler;
|
_comp = compiler;
|
||||||
}
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
Iterator<?> iter = tmpl._compiler.collector.toIterator(value);
|
Iterator<?> iter = _comp.collector.toIterator(value);
|
||||||
if (iter != null) {
|
if (iter != null) {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
@@ -712,26 +719,24 @@ public class Mustache
|
|||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new MustacheException(ioe);
|
throw new MustacheException(ioe);
|
||||||
}
|
}
|
||||||
} else if (_compiler.emptyStringIsFalse && "".equals(value)) {
|
} else if (_comp.isFalsey(value)) {
|
||||||
// omit the section
|
|
||||||
} else if (_compiler.zeroIsFalse && (value instanceof Number) &&
|
|
||||||
((Number)value).longValue() == 0) {
|
|
||||||
// omit the section
|
// omit the section
|
||||||
} else {
|
} else {
|
||||||
executeSegs(tmpl, ctx.nest(value, 0, false, false), out);
|
executeSegs(tmpl, ctx.nest(value, 0, false, false), out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected final Compiler _compiler;
|
protected final Compiler _comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A segment that represents an inverted section. */
|
/** A segment that represents an inverted section. */
|
||||||
protected static class InvertedSectionSegment extends BlockSegment {
|
protected static class InvertedSegment extends BlockSegment {
|
||||||
public InvertedSectionSegment (String name, Template.Segment[] segs, int line) {
|
public InvertedSegment (Compiler compiler, String name, Template.Segment[] segs, int line) {
|
||||||
super(name, segs, line);
|
super(name, segs, line);
|
||||||
|
_comp = compiler;
|
||||||
}
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
Object value = tmpl.getSectionValue(ctx, _name, _line); // won't return null
|
||||||
Iterator<?> iter = tmpl._compiler.collector.toIterator(value);
|
Iterator<?> iter = _comp.collector.toIterator(value);
|
||||||
if (iter != null) {
|
if (iter != null) {
|
||||||
if (!iter.hasNext()) {
|
if (!iter.hasNext()) {
|
||||||
executeSegs(tmpl, ctx, out);
|
executeSegs(tmpl, ctx, out);
|
||||||
@@ -740,8 +745,11 @@ public class Mustache
|
|||||||
if (!(Boolean)value) {
|
if (!(Boolean)value) {
|
||||||
executeSegs(tmpl, ctx, out);
|
executeSegs(tmpl, ctx, out);
|
||||||
}
|
}
|
||||||
|
} else if (_comp.isFalsey(value)) {
|
||||||
|
executeSegs(tmpl, ctx, out);
|
||||||
} // TODO: fail?
|
} // TODO: fail?
|
||||||
}
|
}
|
||||||
|
protected final Compiler _comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Map of strings that must be replaced inside html attributes and their replacements. (They
|
/** Map of strings that must be replaced inside html attributes and their replacements. (They
|
||||||
|
|||||||
@@ -146,9 +146,17 @@ public class MustacheTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testSectionWithFalseyEmptyString () {
|
@Test public void testSectionWithFalseyEmptyString () {
|
||||||
test(Mustache.compiler().emptyStringIsFalse(true), "", "{{#foo}}test{{/foo}}", new Object() {
|
Object ctx = new Object() {
|
||||||
String foo = "";
|
String foo = "";
|
||||||
});
|
String bar = "nonempty";
|
||||||
|
};
|
||||||
|
// test normal sections with falsey empty string
|
||||||
|
Mustache.Compiler compiler = Mustache.compiler().emptyStringIsFalse(true);
|
||||||
|
test(compiler, "", "{{#foo}}test{{/foo}}", ctx);
|
||||||
|
test(compiler, "test", "{{#bar}}test{{/bar}}", ctx);
|
||||||
|
// test inverted sections with falsey empty string
|
||||||
|
test(compiler, "test", "{{^foo}}test{{/foo}}", ctx);
|
||||||
|
test(compiler, "", "{{^bar}}test{{/bar}}", ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testSectionWithNonFalseyZero () {
|
@Test public void testSectionWithNonFalseyZero () {
|
||||||
|
|||||||
Reference in New Issue
Block a user