mangala shenoy
mangala shenoy

Reputation: 325

Getting the variables in Velocity

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

Answers (4)

Roman
Roman

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

Catalin Ciurea
Catalin Ciurea

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

dantonini
dantonini

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

alvi
alvi

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

Related Questions