Add mechism to visit template tags.
This is useful when you want to inspect the structure of the template without executing it.
This commit is contained in:
@@ -297,6 +297,39 @@ public class Mustache {
|
|||||||
<K,V> Map<K,V> createFetcherCache ();
|
<K,V> Map<K,V> createFetcherCache ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Used to visit the tags in a template without executing it. */
|
||||||
|
public interface Visitor {
|
||||||
|
|
||||||
|
/** Visits a text segment. These are blocks of text that are normally just reproduced as
|
||||||
|
* is when executing a template.
|
||||||
|
* @param text the block of text. May contain newlines.
|
||||||
|
*/
|
||||||
|
void visitText (String text);
|
||||||
|
|
||||||
|
/** Visits a variable tag.
|
||||||
|
* @param name the name of the variable.
|
||||||
|
*/
|
||||||
|
void visitVariable (String name);
|
||||||
|
|
||||||
|
/** Visits an include (partial) tag.
|
||||||
|
* @param name the name of the partial template specified by the tag.
|
||||||
|
* @return true if the template should be processed and visited, false to skip it.
|
||||||
|
*/
|
||||||
|
boolean visitInclude (String name);
|
||||||
|
|
||||||
|
/** Visits a section tag.
|
||||||
|
* @param name the name of the section.
|
||||||
|
* @return true if the contents of the section should be visited, false to skip.
|
||||||
|
*/
|
||||||
|
boolean visitSection (String name);
|
||||||
|
|
||||||
|
/** Visits an inverted section tag.
|
||||||
|
* @param name the name of the inverted section.
|
||||||
|
* @return true if the contents of the section should be visited, false to skip.
|
||||||
|
*/
|
||||||
|
boolean visitInvertedSection (String name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
@@ -719,6 +752,9 @@ public class Mustache {
|
|||||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
into.append(_text);
|
into.append(_text);
|
||||||
}
|
}
|
||||||
|
@Override public void visit (Visitor visitor) {
|
||||||
|
visitor.visitText(_text);
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
||||||
_leadBlank + "/" + _trailBlank;
|
_leadBlank + "/" + _trailBlank;
|
||||||
@@ -741,12 +777,26 @@ public class Mustache {
|
|||||||
protected final int _leadBlank, _trailBlank;
|
protected final int _leadBlank, _trailBlank;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A segment that loads and executes a sub-template. */
|
||||||
protected static class IncludedTemplateSegment extends Template.Segment {
|
protected static class IncludedTemplateSegment extends Template.Segment {
|
||||||
public IncludedTemplateSegment (Compiler compiler, String name) {
|
public IncludedTemplateSegment (Compiler compiler, String name) {
|
||||||
_comp = compiler;
|
_comp = compiler;
|
||||||
_name = name;
|
_name = name;
|
||||||
}
|
}
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
|
// we must take care to preserve our context rather than creating a new one, which
|
||||||
|
// would happen if we just called execute() with ctx.data
|
||||||
|
getTemplate().executeSegs(ctx, out);
|
||||||
|
}
|
||||||
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
|
delims.addTag('>', _name, into);
|
||||||
|
}
|
||||||
|
@Override public void visit (Visitor visitor) {
|
||||||
|
if (visitor.visitInclude(_name)) {
|
||||||
|
getTemplate().visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected Template getTemplate () {
|
||||||
// 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) {
|
||||||
@@ -768,16 +818,11 @@ public class Mustache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we must take care to preserve our context rather than creating a new one, which
|
return _template;
|
||||||
// would happen if we just called execute() with ctx.data
|
|
||||||
_template.executeSegs(ctx, out);
|
|
||||||
}
|
|
||||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
|
||||||
delims.addTag('>', _name, into);
|
|
||||||
}
|
}
|
||||||
protected final Compiler _comp;
|
protected final Compiler _comp;
|
||||||
protected final String _name;
|
protected final String _name;
|
||||||
protected Template _template;
|
private Template _template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A helper class for named segments. */
|
/** A helper class for named segments. */
|
||||||
@@ -808,6 +853,9 @@ public class Mustache {
|
|||||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
delims.addTag(' ', _name, into);
|
delims.addTag(' ', _name, into);
|
||||||
}
|
}
|
||||||
|
@Override public void visit (Visitor visitor) {
|
||||||
|
visitor.visitVariable(_name);
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Var(" + _name + ":" + _line + ")";
|
return "Var(" + _name + ":" + _line + ")";
|
||||||
}
|
}
|
||||||
@@ -885,6 +933,13 @@ public class Mustache {
|
|||||||
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
||||||
delims.addTag('/', _name, into);
|
delims.addTag('/', _name, into);
|
||||||
}
|
}
|
||||||
|
@Override public void visit (Visitor visitor) {
|
||||||
|
if (visitor.visitSection(_name)) {
|
||||||
|
for (Template.Segment seg : _segs) {
|
||||||
|
seg.visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
}
|
}
|
||||||
@@ -923,6 +978,13 @@ public class Mustache {
|
|||||||
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
||||||
delims.addTag('/', _name, into);
|
delims.addTag('/', _name, into);
|
||||||
}
|
}
|
||||||
|
@Override public void visit (Visitor visitor) {
|
||||||
|
if (visitor.visitInvertedSection(_name)) {
|
||||||
|
for (Template.Segment seg : _segs) {
|
||||||
|
seg.visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
}
|
}
|
||||||
@@ -932,6 +994,7 @@ public class Mustache {
|
|||||||
protected static class FauxSegment extends Template.Segment {
|
protected static class FauxSegment extends Template.Segment {
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
||||||
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
|
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
|
||||||
|
@Override public void visit (Visitor visit) {}
|
||||||
@Override public String toString () { return "Faux"; }
|
@Override public String toString () { return "Faux"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ public class Template {
|
|||||||
executeSegs(new Context(context, pctx, 0, false, false), out);
|
executeSegs(new Context(context, pctx, 0, false, false), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits the tags in this template (via {@code visitor}) without executing it.
|
||||||
|
* @param visitor the visitor to be called back on each tag in the template.
|
||||||
|
*/
|
||||||
|
public void visit (Mustache.Visitor visitor) {
|
||||||
|
for (Segment seg : _segs) {
|
||||||
|
seg.visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected Template (Segment[] segs, Mustache.Compiler compiler) {
|
protected Template (Segment[] segs, Mustache.Compiler compiler) {
|
||||||
_segs = segs;
|
_segs = segs;
|
||||||
_compiler = compiler;
|
_compiler = compiler;
|
||||||
@@ -367,6 +377,8 @@ public class Template {
|
|||||||
|
|
||||||
abstract void decompile (Mustache.Delims delims, StringBuilder into);
|
abstract void decompile (Mustache.Delims delims, StringBuilder into);
|
||||||
|
|
||||||
|
abstract void visit (Mustache.Visitor visitor);
|
||||||
|
|
||||||
protected static void write (Writer out, String data) {
|
protected static void write (Writer out, String data) {
|
||||||
try {
|
try {
|
||||||
out.write(data);
|
out.write(data);
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ import java.io.IOException;
|
|||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.gwt.junit.client.GWTTestCase;
|
import com.google.gwt.junit.client.GWTTestCase;
|
||||||
|
|
||||||
@@ -348,6 +344,33 @@ public abstract class SharedTests extends GWTTestCase
|
|||||||
check("Text( \\r\\n)3/-1", str.trimTrailBlank().toString());
|
check("Text( \\r\\n)3/-1", str.trimTrailBlank().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class GetKeysVisitor implements Mustache.Visitor {
|
||||||
|
public final Set<String> keys = new HashSet<String>();
|
||||||
|
public void visitText (String text) {}
|
||||||
|
public void visitVariable (String name) { keys.add(name); }
|
||||||
|
public boolean visitInclude (String name) { keys.add(name); return true; }
|
||||||
|
public boolean visitSection (String name) { keys.add(name); return true; }
|
||||||
|
public boolean visitInvertedSection (String name) { keys.add(name); return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testVisit() {
|
||||||
|
String template = "{{#one}}1{{/one}} {{^two}}2{{three}}{{/two}}{{four}}";
|
||||||
|
GetKeysVisitor viz = new GetKeysVisitor();
|
||||||
|
Mustache.compiler().compile(template).visit(viz);
|
||||||
|
List<String> expect = Arrays.asList("one", "two", "three", "four");
|
||||||
|
assertEquals(new HashSet<>(expect), viz.keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testVisitNested() {
|
||||||
|
String template = "{{#one}}1{{/one}} {{^two}}" +
|
||||||
|
"2{{two_and_half}}{{#five}}{{three}}{{/five}}" +
|
||||||
|
"{{/two}} {{#one}}{{three}}{{/one}} {{four}}{{! ignore me }}";
|
||||||
|
GetKeysVisitor viz = new GetKeysVisitor();
|
||||||
|
Mustache.compiler().compile(template).visit(viz);
|
||||||
|
List<String> expect = Arrays.asList("one", "two", "three", "four", "two_and_half", "five");
|
||||||
|
assertEquals(new HashSet<>(expect), viz.keys);
|
||||||
|
}
|
||||||
|
|
||||||
protected void testNewlineSkipping (String sep) {
|
protected void testNewlineSkipping (String sep) {
|
||||||
String tmpl = "list:" + sep +
|
String tmpl = "list:" + sep +
|
||||||
"{{#items}}" + sep +
|
"{{#items}}" + sep +
|
||||||
|
|||||||
Reference in New Issue
Block a user