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:
Michael Bayne
2017-12-17 15:16:09 -08:00
parent 326f04cb1e
commit 37e31cbeac
3 changed files with 110 additions and 12 deletions
@@ -297,6 +297,39 @@ public class Mustache {
<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.
*/
@@ -719,6 +752,9 @@ public class Mustache {
@Override public void decompile (Delims delims, StringBuilder into) {
into.append(_text);
}
@Override public void visit (Visitor visitor) {
visitor.visitText(_text);
}
@Override public String toString () {
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
_leadBlank + "/" + _trailBlank;
@@ -741,12 +777,26 @@ public class Mustache {
protected final int _leadBlank, _trailBlank;
}
/** A segment that loads and executes a sub-template. */
protected static class IncludedTemplateSegment extends Template.Segment {
public IncludedTemplateSegment (Compiler compiler, String name) {
_comp = compiler;
_name = name;
}
@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
// itself (see issue #13)
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
// 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);
return _template;
}
protected final Compiler _comp;
protected final String _name;
protected Template _template;
private Template _template;
}
/** A helper class for named segments. */
@@ -808,6 +853,9 @@ public class Mustache {
@Override public void decompile (Delims delims, StringBuilder into) {
delims.addTag(' ', _name, into);
}
@Override public void visit (Visitor visitor) {
visitor.visitVariable(_name);
}
@Override public String toString () {
return "Var(" + _name + ":" + _line + ")";
}
@@ -885,6 +933,13 @@ public class Mustache {
for (Template.Segment seg : _segs) seg.decompile(delims, 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 () {
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
}
@@ -923,6 +978,13 @@ public class Mustache {
for (Template.Segment seg : _segs) seg.decompile(delims, 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 () {
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
}
@@ -932,6 +994,7 @@ public class Mustache {
protected static class FauxSegment extends Template.Segment {
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
@Override public void visit (Visitor visit) {}
@Override public String toString () { return "Faux"; }
}
@@ -149,6 +149,16 @@ public class Template {
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) {
_segs = segs;
_compiler = compiler;
@@ -367,6 +377,8 @@ public class Template {
abstract void decompile (Mustache.Delims delims, StringBuilder into);
abstract void visit (Mustache.Visitor visitor);
protected static void write (Writer out, String data) {
try {
out.write(data);
@@ -8,11 +8,7 @@ import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
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());
}
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) {
String tmpl = "list:" + sep +
"{{#items}}" + sep +