我正在使用Apache的Velocity模板引擎,我想创建一个自定义指令。也就是说,我希望能够编写“#doMyThing()”并让它调用我编写的一些java代码来生成文本。
我知道我可以通过添加一行来注册自定义指令
userdirective=my.package.here.MyDirectiveName
到我的velocity.properties文件。我知道我可以通过扩展Directive class来编写这样的课程。我不知道的是如何扩展指令类 - 为新指令的作者提供某种文档。例如,我想知道我的getType()方法是否返回“BLOCK”或“LINE”,我想知道我的setLocation()方法应该做什么?
那里有没有比“Use the source, Luke”更好的文件?
答案 0 :(得分:8)
我整理了一些关于编写自定义速度指令(和工具)的article。也许有人会发现它很有用。
答案 1 :(得分:4)
还试图想出一个自定义指令。根本找不到任何文档,所以我查看了一些用户创建的指令:IfNullDirective(简单易用),MergeDirective以及速度内置指令。
这是我的简单块指令,它返回压缩内容(带有一些指令安装说明的完整项目位于here):
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;
}
}
答案 2 :(得分:4)
在Velocity wiki上,有一个演示文稿和示例代码来自我给出的名为“Hacking Velocity”的演讲。它包含一个自定义指令的示例。
答案 3 :(得分:2)
块指令始终接受正文,并且在模板中使用时必须以#end结尾。例如#foreach($ f in $ foo)这有一个身体! #END
行指令没有正文或#end。例如#parse('foo.vtl')
根本不需要使用setLocation()。解析器使用它。
我可以提供哪些其他细节?
另外,您是否考虑过使用“工具”方法?即使你没有使用VelocityTools来自动使你的工具可用,也可以创建一个工具类来完成你想要的工作类,把它放在上下文中,或者有一个你调用的方法来生成内容,或者只是拥有它toString()方法生成内容。例如$ tool.doMyThing()或只是$ myThing
当你需要搞乱Velocity内部(访问InternalContextAdapter或实际节点)时,指令是最好的。
答案 4 :(得分:2)
在速度v1.6之前,我有一个#blockset($ v)#end指令能够处理多行#set($ v),但此函数现在由#define指令处理。 自定义块指令是现代IDE的一个难点,因为它们不能正确解析结构,假设与#userBlockDirective相关联的#end是一个额外的并且将整个文件描绘为RED。如果可能的话应该避免使用它们。
我从速度源代码中复制了类似的东西,并创建了一个“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;
}
}