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 (); <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 +