Added support for the triple mustache! {{{foo}}} which means "don't escape HTML
in the replacement for this tag." This is already supported via {{&foo}} as
well, but we like to be thorough.
Also generate MustacheParseException when parsing (a subclass of
MustacheException) mostly for convenience in testing.
This commit is contained in:
@@ -163,6 +163,24 @@ public class Mustache
|
|||||||
if (text.charAt(0) == '=') {
|
if (text.charAt(0) == '=') {
|
||||||
// TODO: change delimiters
|
// TODO: change delimiters
|
||||||
} else {
|
} else {
|
||||||
|
// if we haven't remapped the delimiters, and the tag starts with {{{ then
|
||||||
|
// require that it end with }}} and disable HTML escaping
|
||||||
|
if (start1 == '{' && start2 == '{' && text.charAt(0) == '{') {
|
||||||
|
try {
|
||||||
|
// we've only parsed }} at this point, so we have to slurp in
|
||||||
|
// another character from the input stream and check it
|
||||||
|
int end3 = (char)source.read();
|
||||||
|
if (end3 != '}') {
|
||||||
|
throw new MustacheParseException(
|
||||||
|
"Invalid triple-mustache tag: {{{" + text + "}}", line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MustacheException(e);
|
||||||
|
}
|
||||||
|
// convert it into (equivalent) {{&text}} which addTagSegment handles
|
||||||
|
text.replace(0, 1, "&");
|
||||||
|
}
|
||||||
|
// process the tag between the mustaches
|
||||||
sanityCheckTag(text, line, start1, start2);
|
sanityCheckTag(text, line, start1, start2);
|
||||||
accum = accum.addTagSegment(text, line);
|
accum = accum.addTagSegment(text, line);
|
||||||
skipNewline = accum.skipNewline();
|
skipNewline = accum.skipNewline();
|
||||||
@@ -193,7 +211,7 @@ public class Mustache
|
|||||||
accum.addTextSegment(text);
|
accum.addTextSegment(text);
|
||||||
break;
|
break;
|
||||||
case TAG:
|
case TAG:
|
||||||
throw new MustacheException("Template ended while parsing a tag TODO");
|
throw new MustacheParseException("Template ended while parsing a tag: " + text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Template(accum.finish(), compiler);
|
return new Template(accum.finish(), compiler);
|
||||||
@@ -206,9 +224,8 @@ public class Mustache
|
|||||||
for (int ii = 0, ll = accum.length(); ii < ll; ii++) {
|
for (int ii = 0, ll = accum.length(); ii < ll; ii++) {
|
||||||
if (accum.charAt(ii) == start1) {
|
if (accum.charAt(ii) == start1) {
|
||||||
if (start2 == -1 || (ii < ll-1 && accum.charAt(ii+1) == start2)) {
|
if (start2 == -1 || (ii < ll-1 && accum.charAt(ii+1) == start2)) {
|
||||||
throw new MustacheException(
|
throw new MustacheParseException("Tag contains start tag delimiter, probably " +
|
||||||
"Tag contains start tag delimiter, probably missing close delimiter " +
|
"missing close delimiter '" + accum + "'", line);
|
||||||
"[line=" + line + ", tag=" + accum + "]");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,8 +277,8 @@ public class Mustache
|
|||||||
return (_segs.size() == 0) || super.skipNewline();
|
return (_segs.size() == 0) || super.skipNewline();
|
||||||
}
|
}
|
||||||
@Override public Template.Segment[] finish () {
|
@Override public Template.Segment[] finish () {
|
||||||
throw new MustacheException("Section missing close tag " +
|
throw new MustacheParseException(
|
||||||
"[line=" + tagLine + ", tag=" + tag1 + "]");
|
"Section missing close tag '" + tag1 + "'", tagLine);
|
||||||
}
|
}
|
||||||
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
||||||
requireSameName(tag1, itag, line);
|
requireSameName(tag1, itag, line);
|
||||||
@@ -278,8 +295,8 @@ public class Mustache
|
|||||||
return (_segs.size() == 0) || super.skipNewline();
|
return (_segs.size() == 0) || super.skipNewline();
|
||||||
}
|
}
|
||||||
@Override public Template.Segment[] finish () {
|
@Override public Template.Segment[] finish () {
|
||||||
throw new MustacheException("Inverted section missing close tag " +
|
throw new MustacheParseException(
|
||||||
"[line=" + tagLine + ", tag=" + tag1 + "]");
|
"Inverted section missing close tag '" + tag1 + "'", tagLine);
|
||||||
}
|
}
|
||||||
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
@Override protected Accumulator addCloseSectionSegment (String itag, int line) {
|
||||||
requireSameName(tag1, itag, line);
|
requireSameName(tag1, itag, line);
|
||||||
@@ -313,23 +330,22 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Accumulator addCloseSectionSegment (String tag, int line) {
|
protected Accumulator addCloseSectionSegment (String tag, int line) {
|
||||||
throw new MustacheException("Section close tag with no open tag " +
|
throw new MustacheParseException(
|
||||||
"[line=" + line + ", tag=" + tag + "]");
|
"Section close tag with no open tag '" + tag + "'", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void requireNoNewlines (String tag, int line) {
|
protected static void requireNoNewlines (String tag, int line) {
|
||||||
if (tag.indexOf("\n") != -1 || tag.indexOf("\r") != -1) {
|
if (tag.indexOf("\n") != -1 || tag.indexOf("\r") != -1) {
|
||||||
throw new MustacheException("Invalid tag name: contains newline " +
|
throw new MustacheParseException(
|
||||||
"[line=" + line + ", tag=" + tag + "]");
|
"Invalid tag name: contains newline '" + tag + "'", line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void requireSameName (String name1, String name2, int line)
|
protected static void requireSameName (String name1, String name2, int line)
|
||||||
{
|
{
|
||||||
if (!name1.equals(name2)) {
|
if (!name1.equals(name2)) {
|
||||||
throw new MustacheException(
|
throw new MustacheParseException("Section close tag with mismatched open tag '" +
|
||||||
"Section close tag with mismatched open tag " +
|
name2 + "' != '" + name1 + "'", line);
|
||||||
"[line=" + line + ", expected=" + name1 + ", got=" + name2 + "]");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ package com.samskivert.mustache;
|
|||||||
*/
|
*/
|
||||||
public class MustacheException extends RuntimeException
|
public class MustacheException extends RuntimeException
|
||||||
{
|
{
|
||||||
|
public static class Parse extends MustacheException {
|
||||||
|
public Parse (String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MustacheException (String message) {
|
public MustacheException (String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
package com.samskivert.mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown if we encounter an error while parsing a template.
|
||||||
|
*/
|
||||||
|
public class MustacheParseException extends MustacheException
|
||||||
|
{
|
||||||
|
public MustacheParseException (String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MustacheParseException (String message, int lineNo)
|
||||||
|
{
|
||||||
|
super(message + " @ line " + lineNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,6 +112,28 @@ public class MustacheTest
|
|||||||
test("foobar", "foo{{! nothing to see here}}bar", new Object());
|
test("foobar", "foo{{! nothing to see here}}bar", new Object());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void testUnescapeHTML () {
|
||||||
|
assertEquals("<b>", Mustache.compiler().escapeHTML(true).compile("{{&a}}").
|
||||||
|
execute(context("a", "<b>")));
|
||||||
|
assertEquals("<b>", Mustache.compiler().escapeHTML(true).compile("{{{a}}}").
|
||||||
|
execute(context("a", "<b>")));
|
||||||
|
// make sure these also work when escape HTML is off
|
||||||
|
assertEquals("<b>", Mustache.compiler().escapeHTML(false).compile("{{&a}}").
|
||||||
|
execute(context("a", "<b>")));
|
||||||
|
assertEquals("<b>", Mustache.compiler().escapeHTML(false).compile("{{{a}}}").
|
||||||
|
execute(context("a", "<b>")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = MustacheParseException.class)
|
||||||
|
public void testDanglingTag () {
|
||||||
|
Mustache.compiler().escapeHTML(true).compile("{{a").execute(context("a", "<b>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = MustacheParseException.class)
|
||||||
|
public void testInvalidUnescapeHTML () {
|
||||||
|
Mustache.compiler().escapeHTML(true).compile("{{{a}}").execute(context("a", "<b>"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void testEscapeHTML () {
|
@Test public void testEscapeHTML () {
|
||||||
assertEquals("<b>", Mustache.compiler().compile("{{a}}").
|
assertEquals("<b>", Mustache.compiler().compile("{{a}}").
|
||||||
execute(context("a", "<b>")));
|
execute(context("a", "<b>")));
|
||||||
|
|||||||
Reference in New Issue
Block a user