6

我正在使用 Apache 的 Velocity 模板引擎,我想创建一个自定义指令。也就是说,我希望能够编写“#doMyThing()”并让它调用我编写的一些 java 代码以生成文本。

我知道我可以通过添加一行来注册自定义指令

userdirective=my.package.here.MyDirectiveName

到我的velocity.properties 文件。而且我知道我可以通过扩展Directive 类来编写这样的类。我不知道如何扩展 Directive 类——新指令作者的某种文档。例如,我想知道我的 getType() 方法是返回“BLOCK”还是“LINE”,并且我想知道我的 setLocation() 方法应该做什么?

有没有比“使用源代码,卢克”更好的文档?

4

5 回答 5

8

我整理了一篇关于编写自定义速度指令(和工具)的小文章。也许有人会发现它很有用。

于 2009-09-10T18:57:51.333 回答
4

还试图提出一个自定义指令。根本找不到任何文档,所以我查看了一些用户创建的指令:If​​NullDirective(一个不错且简单的指令)、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;

    }

}
于 2009-07-09T20:47:55.913 回答
4

在 Velocity wiki 上,有一个演示文稿和示例代码,来自我发表的名为“ Hacking Velocity ”的演讲。它包括一个自定义指令的示例。

于 2009-08-09T16:20:06.067 回答
2

块指令总是接受一个主体,并且在模板中使用时必须以 #end 结尾。例如 #foreach( $i in $foo ) 这有一个主体!#结尾

行指令没有正文或#end。例如#parse('foo.vtl')

您根本不需要同时使用 setLocation() 。解析器使用它。

还有什么我可以帮忙的细节吗?

另外,您是否考虑过使用“工具”方法?即使您不使用 VelocityTools 来自动使您的工具可用等等,您也可以创建一个工具类来执行您想要的操作,将其放在上下文中,或者有一个您调用的方法来生成内容,或者只是拥有它toString() 方法生成内容。例如 $tool.doMyThing() 或只是 $myThing

指令最适合需要处理 Velocity 内部(访问 InternalContextAdapter 或实际节点)的情况。

于 2008-10-17T22:52:59.723 回答
2

在velocity 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;
    }

}
于 2009-06-18T14:20:25.190 回答