有同学需要 freemarker 的 nested 用法,现给出代码实现
1、添加 #slot 指令
import com.jfinal.template.io.Writer; import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.ast.Stat; public class SlotDirective extends Directive { public void exec(Env env, Scope scope, Writer writer) { Stat stat = (Stat)scope.get(InsertDirective._SLOT_TEMPLATE_KEY_); stat.exec(env, scope, writer); } }
2、添加 #insert 指令
import java.util.ArrayList; import java.util.List; import com.jfinal.template.Directive; import com.jfinal.template.Env; import com.jfinal.template.TemplateException; import com.jfinal.template.expr.ast.Const; import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.io.Writer; import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.ast.Define; public class InsertDirective extends Directive { static final String _SLOT_TEMPLATE_KEY_ = "_SLOT_TEMPLATE_KEY_"; private String slotTemplate; private ExprList exprList; public void setExprList(ExprList exprList) { if (exprList.length() < 1) { throw new ParseException("function name of slot template cant not be null", location); } if (! (exprList.getExpr(0) instanceof Const) ) { throw new ParseException("function name of slot template must be String", location); } Const c = (Const)exprList.getExpr(0); if (! c.isStr()) { throw new ParseException("function name of slot template must be String", location); } this.slotTemplate = c.getStr(); this.exprList = getExprList(exprList); } /** * 第一个参数以后的参数作为 slot 所属模板定义之处的参数 */ private ExprList getExprList(ExprList exprList) { if (exprList.length() == 1) { return ExprList.NULL_EXPR_LIST; } List<Expr> ret = new ArrayList<>(); for (int i=1; i<exprList.length(); i++) { ret.add(exprList.getExpr(i)); } return new ExprList(ret); } public void exec(Env env, Scope scope, Writer writer) { Define function = env.getFunction(slotTemplate); if (function == null) { throw new TemplateException("Template function not defined: " + slotTemplate, location); } scope.set(_SLOT_TEMPLATE_KEY_, this.stat); function.call(env, scope, this.exprList, writer); } public boolean hasEnd() { return true; } }
3、测试用的模板
在 src/main/resources 目录下创建一个名为 slot.jf 的模板文件(文件名与 java 测试代码保持一致即可)内容如下:
#define template(list, value) #for(x : list) #slot() #end #(value) #end ### 第一个参数为函数名,后续所有参数为传递给 slot 所在模板的函数的参数 #insert("template", [1..12], "再传一个参数 value") <div>这里是向 slot 插入的内容 = #(x) </div> #end
4、测试用的 java 代码
import com.jfinal.template.Engine; public class Test { public static void main(String[] args) { Engine engine = Engine.use().setToClassPathSourceFactory(); engine.addDirective("insert", InsertDirective.class); engine.addDirective("slot", SlotDirective.class); // render 方法中可以传入参数供模板中使用 String ret = engine.getTemplate("slot.jf").renderToString(null); System.out.println(ret); } }
要点:freemarker 的 nested 在 enjoy 之下可以看成是模板函数与 slot 调用次序的倒置,所以实现起来易如反掌
Enjoy 引擎虽然概念极少,学习成本极低,但功能却十分强大,碰到个别没有的功能也可通过 enjoy 提供的各种扩展机制进行扩展。jfinal 自带的 sql 模板功能也是这样扩展而来的