如何为Apache Velocity创建自定义指令
我正在使用Apache的Velocity模板引擎,并且我想创建一个自定义指令。也就是说,我希望能够编写" #doMyThing()"并使其调用我编写的一些Java代码以生成文本。
我知道我可以通过添加一行来注册自定义指令
userdirective=my.package.here.MyDirectiveName
到我的velocity.properties文件。而且我知道我可以通过扩展Directive类来编写此类。我不知道如何扩展Directive类-为新Directive的作者提供的某种文档。例如,我想知道我的getType()方法是否返回" BLOCK"或者" LINE",并且我想知道我的setLocation()方法应该做什么?
有没有比"使用源代码,卢克"更好的文档?
解决方案
块指令始终接受一个主体,并且在模板中使用时必须以#end结尾。例如#foreach($ i在$ foo中)这有一个身体! #结尾
行指令没有正文或者#end。例如#parse('foo.vtl')
我们根本不需要同时使用setLocation()。解析器使用它。
我还有其他需要帮助的细节吗?
另外,我们是否考虑过使用"工具"方法?即使我们不使用VelocityTools自动使工具可用,我们也可以创建一个满足我们需要的工具类,将其放在上下文中,或者使用一种调用方法来生成内容,或者只是使用它来生成内容。 toString()方法生成内容。例如$ tool.doMyThing()或者仅$ myThing
指令最适合我们需要弄乱Velocity内部结构(访问InternalContextAdapter或者实际节点)时使用的方法。
在速度v1.6之前,我有一个#blockset($ v)#end指令可以处理多行#set($ v),但此功能现在由#define指令处理。
自定义块指令对于现代IDE来说是一个痛苦,因为它们不能正确地解析结构,假设我们与#userBlockDirective关联的#end是多余的,并且将整个文件涂成红色。如果可能,应避免使用它们。
我从速度源代码中复制了类似的内容,并创建了" blockset"(多行)指令。
import org.apache.velocity.runtime.directive.Directive; import org.apache.velocity.runtime.RuntimeServices; import org.apache.velocity.runtime.parser.node.Node; import org.apache.velocity.context.InternalContextAdapter; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.TemplateInitException; import java.io.Writer; import java.io.IOException; import java.io.StringWriter; public class BlockSetDirective extends Directive { private String blockKey; /** * Return name of this directive. */ public String getName() { return "blockset"; } /** * Return type of this directive. */ public int getType() { return BLOCK; } /** * simple init - get the blockKey */ public void init( RuntimeServices rs, InternalContextAdapter context, Node node ) throws TemplateInitException { super.init( rs, context, node ); /* * first token is the name of the block. I don't even check the format, * just assume it looks like this: $block_name. Should check if it has * a '$' or not like macros. */ blockKey = node.jjtGetChild( 0 ).getFirstToken().image.substring( 1 ); } /** * Renders node to internal string writer and stores in the context at the * specified context variable */ public boolean render( InternalContextAdapter context, Writer writer, Node node ) throws IOException, MethodInvocationException, ResourceNotFoundException, ParseErrorException { StringWriter sw = new StringWriter(256); boolean b = node.jjtGetChild( 1 ).render( context, sw ); context.put( blockKey, sw.toString() ); return b; } }
还试图提出一个自定义指令。根本找不到任何文档,因此我查看了一些用户创建的指令:IfNullDirective(既好又简单),MergeDirective以及速度内置指令。
这是我的简单块指令,它返回压缩的内容(带有一些指令安装说明的完整项目位于此处):
import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import org.apache.velocity.context.InternalContextAdapter; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.TemplateInitException; import org.apache.velocity.runtime.RuntimeServices; import org.apache.velocity.runtime.directive.Directive; import org.apache.velocity.runtime.parser.node.Node; import org.apache.velocity.runtime.log.Log; import com.googlecode.htmlcompressor.compressor.HtmlCompressor; /** * Velocity directive that compresses an HTML content within #compressHtml ... #end block. */ public class HtmlCompressorDirective extends Directive { private static final HtmlCompressor htmlCompressor = new HtmlCompressor(); private Log log; public String getName() { return "compressHtml"; } public int getType() { return BLOCK; } @Override public void init(RuntimeServices rs, InternalContextAdapter context, Node node) throws TemplateInitException { super.init(rs, context, node); log = rs.getLog(); //set compressor properties htmlCompressor.setEnabled(rs.getBoolean("userdirective.compressHtml.enabled", true)); htmlCompressor.setRemoveComments(rs.getBoolean("userdirective.compressHtml.removeComments", true)); } public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { //render content to a variable StringWriter content = new StringWriter(); node.jjtGetChild(0).render(context, content); //compress try { writer.write(htmlCompressor.compress(content.toString())); } catch (Exception e) { writer.write(content.toString()); String msg = "Failed to compress content: "+content.toString(); log.error(msg, e); throw new RuntimeException(msg, e); } return true; } }
在Velocity Wiki上,有一个演讲和示例代码,这些演讲和示例代码来自我所说的" Hacking Velocity"。它包含一个自定义指令的示例。
我整理了一篇有关编写自定义速度指令(和工具)的文章。也许有人会发现它有用。