Reputation: 325
I have a plugin project and I am using Velocity templates. The user can change the template body from the preference page, and I want to get the variables in the template body when the user clicks "OK" in the preference page. I need help to extract the variables from the Velocity template body.
Upvotes: 3
Views: 5871
Reputation: 1
I do it by registering an event that memorizes instances and then evaluating the template with the event in the cartridge. This is not 100% bullet proof, since references can contain a lot of different things, but I haven't yet seen a clear example where it fails either.
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class TemplateAnalyzer{
public static Set<String> getReferences(String template){
HashSet<String> names = new HashSet<>();
VelocityContext velocityContext = new VelocityContext();
EventCartridge ec = new EventCartridge();
ec.addEventHandler((ReferenceInsertionEventHandler)
(reference, value) -> names.add(reference)
);
ec.attachToContext(velocityContext);
Velocity.evaluate(velocityContext, new StringWriter(), "velocity", template);
return names;
}
private static Pattern namePattern = Pattern.compile("\\$!?\\{?([a-zA-Z][\\w*\\-])");
public static Set<String> getVariableNames(String template)
{
Set<String> references = getReferences(template);
return references.stream()
.map(r -> namePattern.matcher(r))
.filter(Matcher::find)
.map(m -> m.group(1))
.collect(Collectors.toSet());
}
}
You need the second method to filter out anything that is not a simple variable and clean up dollar signs and braces.
Upvotes: 0
Reputation: 781
xdocreport project did this work.
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>xdocreport</artifactId>
<version>1.0.6</version>
</dependency>
StringReader templateReader = new StringReader(stringyTemplateContent);
FieldsExtractor<FieldExtractor> extractor = FieldsExtractor.create();
VelocityFieldsExtractor.getInstance().extractFields(templateReader, templateName, extractor);
for (FieldExtractor fieldExtractor : extractor.getFields()) {
System.out.println(fieldExtractor.getName());
}
I used their VelocityFieldsExtractorTestCase
as an example.
Upvotes: 0
Reputation: 61
Velocity use JavaCC to parse a template and create an AST.
RuntimeInstance is all you need to parse a template.
RuntimeInstance ri = new RuntimeInstance();
SimpleNode node = ri.parse( reader, "templateName" );
Now you must extends BaseVisitor according your needed. BaseVisitor is the abstract class for all visitors. BaseVisitor has one method for node type so you can easily filter AST nodes.
ParserVisitor visitor = new BaseVisitor() {
@Override
public Object visit(final ASTReference node, final Object data) {
//insert here your logic ...
System.out.println(node.getFirstToken();
//use super.visit( node, data) if you need to traverse all node children
return null;
}
};
and then visit the node ...
visitor.visit(node, null);
If you have a template as the following:
some text $var other text
suggested code print out only $var
.
Note that ASTReference is ANY reference. If you have a template as following:
some text $var other text
#set( $primate = "monkey" )
this code prints out: $var
and $primate
.
Upvotes: 5
Reputation: 2702
The only way I can think of is that you add something like this to the velocity engine:
VelocityContext context = new VelocityContext();
context.put("parameters", new HashMap());
... within the template, let the user put values into the parameters hashmap like so:
#set ($t = $parameters.put("value", "key"))
(important: the user must assign a value to a temporary parameter, e.g. $t)
... and then, after rendering, take the values out:
HashMap map = (HashMap)context.get("parameters");
for (String key : map.keySet()) {
// ...
}
Upvotes: 0