Reworked user-defined escaping to suit my tastes.
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// JMustache - A Java implementation of the Mustache templating language
|
||||||
|
// http://github.com/samskivert/jmustache/blob/master/LICENSE
|
||||||
|
|
||||||
|
package com.samskivert.mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines some standard {@link Mustache.Escaper}s.
|
||||||
|
*/
|
||||||
|
public class Escapers
|
||||||
|
{
|
||||||
|
/** Escapes HTML entities. */
|
||||||
|
public static final Mustache.Escaper HTML = simple(new String[][] {
|
||||||
|
{ "&", "&" },
|
||||||
|
{ "'", "'" },
|
||||||
|
{ "\"", """ },
|
||||||
|
{ "<", "<" },
|
||||||
|
{ ">", ">" }
|
||||||
|
});
|
||||||
|
|
||||||
|
/** An escaper that does no escaping. */
|
||||||
|
public static final Mustache.Escaper NONE = new Mustache.Escaper() {
|
||||||
|
@Override public String escape (String text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Returns an escaper that replaces a list of text sequences with canned replacements.
|
||||||
|
* @param repls a list of {@code (text, replacement)} pairs. */
|
||||||
|
public static Mustache.Escaper simple (final String[]... repls) {
|
||||||
|
return new Mustache.Escaper() {
|
||||||
|
@Override public String escape (String text) {
|
||||||
|
for (String[] escape : repls) {
|
||||||
|
text = text.replace(escape[0], escape[1]);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.samskivert.mustache;
|
|
||||||
|
|
||||||
public interface Escaping {
|
|
||||||
String escape(String raw);
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
package com.samskivert.mustache;
|
package com.samskivert.mustache;
|
||||||
|
|
||||||
import com.samskivert.mustache.formats.HtmlEscaping;
|
|
||||||
import com.samskivert.mustache.formats.NoEscaping;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
@@ -33,9 +30,6 @@ public class Mustache
|
|||||||
/** An interface to the Mustache compilation process. See {@link Mustache}. */
|
/** An interface to the Mustache compilation process. See {@link Mustache}. */
|
||||||
public static class Compiler
|
public static class Compiler
|
||||||
{
|
{
|
||||||
/** How characters are escaped by default. */
|
|
||||||
public final Escaping escaping;
|
|
||||||
|
|
||||||
/** Whether or not standards mode is enabled. */
|
/** Whether or not standards mode is enabled. */
|
||||||
public final boolean standardsMode;
|
public final boolean standardsMode;
|
||||||
|
|
||||||
@@ -61,6 +55,9 @@ public class Mustache
|
|||||||
* mustache implementation. Default is false. */
|
* mustache implementation. Default is false. */
|
||||||
public final boolean zeroIsFalse;
|
public final boolean zeroIsFalse;
|
||||||
|
|
||||||
|
/** Handles escaping characters in substituted text. */
|
||||||
|
public final Escaper escaper;
|
||||||
|
|
||||||
/** The template loader in use during this compilation. */
|
/** The template loader in use during this compilation. */
|
||||||
public final TemplateLoader loader;
|
public final TemplateLoader loader;
|
||||||
|
|
||||||
@@ -80,33 +77,28 @@ public class Mustache
|
|||||||
return Mustache.compile(source, this);
|
return Mustache.compile(source, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that either does or does not escape HTML by default. */
|
/** Returns a compiler that either does or does not escape HTML by default. Note: this
|
||||||
|
* overrides any escaper set via {@link #withEscaper}. */
|
||||||
public Compiler escapeHTML (boolean escapeHTML) {
|
public Compiler escapeHTML (boolean escapeHTML) {
|
||||||
return escaping(escapeHTML ? new HtmlEscaping() : new NoEscaping());
|
return withEscaper(escapeHTML ? Escapers.HTML : Escapers.NONE);
|
||||||
}
|
|
||||||
|
|
||||||
public Compiler escaping(Escaping escaping) {
|
|
||||||
return new Compiler(escaping, this.standardsMode, this.nullValue, this.missingIsNull,
|
|
||||||
this.emptyStringIsFalse, this.zeroIsFalse, this.loader,
|
|
||||||
this.collector, this.delims);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that either does or does not use standards mode. Standards mode
|
/** Returns a compiler that either does or does not use standards mode. Standards mode
|
||||||
* disables the non-standard JMustache extensions like looking up missing names in a parent
|
* disables the non-standard JMustache extensions like looking up missing names in a parent
|
||||||
* context. */
|
* context. */
|
||||||
public Compiler standardsMode (boolean standardsMode) {
|
public Compiler standardsMode (boolean standardsMode) {
|
||||||
return new Compiler(this.escaping, standardsMode, this.nullValue, this.missingIsNull,
|
return new Compiler(standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.emptyStringIsFalse, this.zeroIsFalse, this.loader,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.collector, this.delims);
|
this.loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that will use the given value for any variable that is missing, or
|
/** Returns a compiler that will use the given value for any variable that is missing, or
|
||||||
* otherwise resolves to null. This is like {@link #nullValue} except that it returns the
|
* otherwise resolves to null. This is like {@link #nullValue} except that it returns the
|
||||||
* supplied default for missing keys and existing keys that return null values. */
|
* supplied default for missing keys and existing keys that return null values. */
|
||||||
public Compiler defaultValue (String defaultValue) {
|
public Compiler defaultValue (String defaultValue) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, defaultValue, true,
|
return new Compiler(this.standardsMode, defaultValue, true,
|
||||||
this.emptyStringIsFalse, this.zeroIsFalse, this.loader,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.collector, this.delims);
|
this.loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that will use the given value for any variable that resolves to
|
/** Returns a compiler that will use the given value for any variable that resolves to
|
||||||
@@ -121,36 +113,43 @@ public class Mustache
|
|||||||
* raised.</li>
|
* raised.</li>
|
||||||
* </ul> */
|
* </ul> */
|
||||||
public Compiler nullValue (String nullValue) {
|
public Compiler nullValue (String nullValue) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, nullValue, false,
|
return new Compiler(this.standardsMode, nullValue, false,
|
||||||
this.emptyStringIsFalse, this.zeroIsFalse, this.loader,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.collector, this.delims);
|
this.loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that will treat empty string as a false value if parameter is true. */
|
/** Returns a compiler that will treat empty string as a false value if parameter is true. */
|
||||||
public Compiler emptyStringIsFalse (boolean emptyStringIsFalse) {
|
public Compiler emptyStringIsFalse (boolean emptyStringIsFalse) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, this.nullValue,
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.missingIsNull, emptyStringIsFalse, this.zeroIsFalse,
|
emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.loader, this.collector, this.delims);
|
this.loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler that will treat zero as a false value if parameter is true. */
|
/** Returns a compiler that will treat zero as a false value if parameter is true. */
|
||||||
public Compiler zeroIsFalse (boolean zeroIsFalse) {
|
public Compiler zeroIsFalse (boolean zeroIsFalse) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, this.nullValue,
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.missingIsNull, this.emptyStringIsFalse, zeroIsFalse,
|
this.emptyStringIsFalse, zeroIsFalse, this.escaper,
|
||||||
|
this.loader, this.collector, this.delims);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures the {@link Escaper} used to escape substituted text. */
|
||||||
|
public Compiler withEscaper (Escaper escaper) {
|
||||||
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
|
this.emptyStringIsFalse, this.zeroIsFalse, escaper,
|
||||||
this.loader, this.collector, this.delims);
|
this.loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler configured to use the supplied template loader to handle partials. */
|
/** Returns a compiler configured to use the supplied template loader to handle partials. */
|
||||||
public Compiler withLoader (TemplateLoader loader) {
|
public Compiler withLoader (TemplateLoader loader) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, this.nullValue,
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.missingIsNull, this.emptyStringIsFalse, this.zeroIsFalse,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
loader, this.collector, this.delims);
|
loader, this.collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a compiler configured to use the supplied collector. */
|
/** Returns a compiler configured to use the supplied collector. */
|
||||||
public Compiler withCollector (Collector collector) {
|
public Compiler withCollector (Collector collector) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, this.nullValue,
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.missingIsNull, this.emptyStringIsFalse, this.zeroIsFalse,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.loader, collector, this.delims);
|
this.loader, collector, this.delims);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +157,8 @@ public class Mustache
|
|||||||
* @param delims a string of the form {@code AB CD} or {@code A D} where A and B are
|
* @param delims a string of the form {@code AB CD} or {@code A D} where A and B are
|
||||||
* opening delims and C and D are closing delims. */
|
* opening delims and C and D are closing delims. */
|
||||||
public Compiler withDelims (String delims) {
|
public Compiler withDelims (String delims) {
|
||||||
return new Compiler(this.escaping, this.standardsMode, this.nullValue,
|
return new Compiler(this.standardsMode, this.nullValue, this.missingIsNull,
|
||||||
this.missingIsNull, this.emptyStringIsFalse, this.zeroIsFalse,
|
this.emptyStringIsFalse, this.zeroIsFalse, this.escaper,
|
||||||
this.loader, this.collector, new Delims().updateDelims(delims));
|
this.loader, this.collector, new Delims().updateDelims(delims));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,29 +176,21 @@ public class Mustache
|
|||||||
(zeroIsFalse && (value instanceof Number) && ((Number)value).longValue() == 0);
|
(zeroIsFalse && (value instanceof Number) && ((Number)value).longValue() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Compiler (Escaping escaping, boolean standardsMode, String nullValue,
|
protected Compiler (boolean standardsMode, String nullValue, boolean missingIsNull,
|
||||||
boolean missingIsNull, boolean emptyStringIsFalse, boolean zeroIsFalse,
|
boolean emptyStringIsFalse, boolean zeroIsFalse, Escaper escaper,
|
||||||
TemplateLoader loader, Collector collector, Delims delims) {
|
TemplateLoader loader, Collector collector, Delims delims) {
|
||||||
this.escaping = escaping;
|
|
||||||
this.standardsMode = standardsMode;
|
this.standardsMode = standardsMode;
|
||||||
this.nullValue = nullValue;
|
this.nullValue = nullValue;
|
||||||
this.missingIsNull = missingIsNull;
|
this.missingIsNull = missingIsNull;
|
||||||
this.emptyStringIsFalse = emptyStringIsFalse;
|
this.emptyStringIsFalse = emptyStringIsFalse;
|
||||||
this.zeroIsFalse = zeroIsFalse;
|
this.zeroIsFalse = zeroIsFalse;
|
||||||
|
this.escaper = escaper;
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
this.collector = collector;
|
this.collector = collector;
|
||||||
this.delims = delims;
|
this.delims = delims;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used to handle partials. */
|
|
||||||
public interface TemplateLoader
|
|
||||||
{
|
|
||||||
/** Returns a reader for the template with the supplied name.
|
|
||||||
* @throws Exception if the template could not be loaded for any reason. */
|
|
||||||
Reader getTemplate (String name) throws Exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Used to handle lambdas. */
|
/** Used to handle lambdas. */
|
||||||
public interface Lambda
|
public interface Lambda
|
||||||
{
|
{
|
||||||
@@ -219,6 +210,21 @@ public class Mustache
|
|||||||
Object get (Object ctx, String name) throws Exception;
|
Object get (Object ctx, String name) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles escaper characters in substituted text. */
|
||||||
|
public interface Escaper
|
||||||
|
{
|
||||||
|
/** Returns {@code raw} with the appropriate characters replaced with escape sequences. */
|
||||||
|
String escape (String raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Used to handle partials. */
|
||||||
|
public interface TemplateLoader
|
||||||
|
{
|
||||||
|
/** Returns a reader for the template with the supplied name.
|
||||||
|
* @throws Exception if the template could not be loaded for any reason. */
|
||||||
|
Reader getTemplate (String name) throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
/** Handles interpreting objects as collections. */
|
/** Handles interpreting objects as collections. */
|
||||||
public interface Collector
|
public interface Collector
|
||||||
{
|
{
|
||||||
@@ -241,8 +247,8 @@ public class Mustache
|
|||||||
* Returns a compiler that escapes HTML by default and does not use standards mode.
|
* Returns a compiler that escapes HTML by default and does not use standards mode.
|
||||||
*/
|
*/
|
||||||
public static Compiler compiler () {
|
public static Compiler compiler () {
|
||||||
return new Compiler(new HtmlEscaping(), false, null, true, false, false, FAILING_LOADER,
|
return new Compiler(false, null, true, false, false, Escapers.HTML,
|
||||||
new DefaultCollector(), new Delims());
|
FAILING_LOADER, new DefaultCollector(), new Delims());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,12 +274,6 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String escapeHTML (String text) {
|
|
||||||
return htmlEscaping.escape(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Escaping htmlEscaping = new HtmlEscaping();
|
|
||||||
|
|
||||||
// TODO: this method was never called, what was my intention here?
|
// TODO: this method was never called, what was my intention here?
|
||||||
protected static boolean allowsWhitespace (char typeChar) {
|
protected static boolean allowsWhitespace (char typeChar) {
|
||||||
return (typeChar == '=') || // change delimiters
|
return (typeChar == '=') || // change delimiters
|
||||||
@@ -416,7 +416,7 @@ public class Mustache
|
|||||||
text.setLength(0);
|
text.setLength(0);
|
||||||
} else {
|
} else {
|
||||||
// if the delimiters are {{ and }}, and the tag starts with {{{ then
|
// if the delimiters are {{ and }}, and the tag starts with {{{ then
|
||||||
// require that it end with }}} and disable HTML escaping
|
// require that it end with }}} and disable escaping
|
||||||
if (delims.isStaches() && text.charAt(0) == delims.start1) {
|
if (delims.isStaches() && text.charAt(0) == delims.start1) {
|
||||||
try {
|
try {
|
||||||
// we've only parsed }} at this point, so we have to slurp in
|
// we've only parsed }} at this point, so we have to slurp in
|
||||||
@@ -580,12 +580,12 @@ public class Mustache
|
|||||||
|
|
||||||
case '&':
|
case '&':
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
_segs.add(new VariableSegment(tag1, tagLine, new NoEscaping()));
|
_segs.add(new VariableSegment(tag1, tagLine, Escapers.NONE));
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
requireNoNewlines(tag, tagLine);
|
requireNoNewlines(tag, tagLine);
|
||||||
_segs.add(new VariableSegment(tag, tagLine, _comp.escaping));
|
_segs.add(new VariableSegment(tag, tagLine, _comp.escaper));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -669,9 +669,9 @@ public class Mustache
|
|||||||
|
|
||||||
/** A segment that substitutes the contents of a variable. */
|
/** A segment that substitutes the contents of a variable. */
|
||||||
protected static class VariableSegment extends NamedSegment {
|
protected static class VariableSegment extends NamedSegment {
|
||||||
public VariableSegment(String name, int line, Escaping escaping) {
|
public VariableSegment (String name, int line, Escaper escaper) {
|
||||||
super(name, line);
|
super(name, line);
|
||||||
_escaping = escaping;
|
_escaper = escaper;
|
||||||
}
|
}
|
||||||
@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.getValueOrDefault(ctx, _name, _line);
|
Object value = tmpl.getValueOrDefault(ctx, _name, _line);
|
||||||
@@ -680,9 +680,9 @@ public class Mustache
|
|||||||
"' on line " + _line, _name, _line);
|
"' on line " + _line, _name, _line);
|
||||||
}
|
}
|
||||||
String text = String.valueOf(value);
|
String text = String.valueOf(value);
|
||||||
write(out, _escaping.escape(text));
|
write(out, _escaper.escape(text));
|
||||||
}
|
}
|
||||||
protected Escaping _escaping;
|
protected Escaper _escaper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A helper class for block segments. */
|
/** A helper class for block segments. */
|
||||||
@@ -766,5 +766,4 @@ public class Mustache
|
|||||||
throw new UnsupportedOperationException("Template loading not configured");
|
throw new UnsupportedOperationException("Template loading not configured");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.samskivert.mustache.formats;
|
|
||||||
|
|
||||||
public class HtmlEscaping extends SimpleEscaping {
|
|
||||||
public HtmlEscaping() {
|
|
||||||
super(new String[][]{
|
|
||||||
{"&", "&"},
|
|
||||||
{"'", "'"},
|
|
||||||
{"\"", """},
|
|
||||||
{"<", "<"},
|
|
||||||
{">", ">"},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.samskivert.mustache.formats;
|
|
||||||
|
|
||||||
import com.samskivert.mustache.Escaping;
|
|
||||||
|
|
||||||
public class NoEscaping implements Escaping {
|
|
||||||
@Override
|
|
||||||
public String escape(String raw) {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.samskivert.mustache.formats;
|
|
||||||
|
|
||||||
import com.samskivert.mustache.Escaping;
|
|
||||||
|
|
||||||
public class SimpleEscaping implements Escaping {
|
|
||||||
private final String[][] substringReplacements;
|
|
||||||
|
|
||||||
public SimpleEscaping(String[][] substringReplacements) {
|
|
||||||
this.substringReplacements = substringReplacements;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String escape(String text) {
|
|
||||||
for (String[] escape : substringReplacements) {
|
|
||||||
text = text.replace(escape[0], escape[1]);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.samskivert.mustache.formats.SimpleEscaping;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -297,13 +296,12 @@ public class MustacheTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testUserDefinedEscaping() {
|
@Test public void testUserDefinedEscaping() {
|
||||||
Escaping escaping = new SimpleEscaping(new String[][] {
|
Mustache.Escaper escaper = Escapers.simple(new String[][] {
|
||||||
{"[", ":BEGIN:"},
|
{ "[", ":BEGIN:" },
|
||||||
{"]", ":END:"}
|
{ "]", ":END:" }
|
||||||
});
|
});
|
||||||
|
assertEquals(":BEGIN:b:END:", Mustache.compiler().withEscaper(escaper).
|
||||||
assertEquals(":BEGIN:b:END:", Mustache.compiler().escaping(escaping).compile("{{a}}").
|
compile("{{a}}").execute(context("a", "[b]")));
|
||||||
execute(context("a", "[b]")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testPartialDelimiterMatch () {
|
@Test public void testPartialDelimiterMatch () {
|
||||||
|
|||||||
Reference in New Issue
Block a user