Enjoy 引擎扩展 freemarker nested 用法

     有同学需要 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 模板功能也是这样扩展而来的

评论区

Sohnny

2019-06-08 22:28

demo中如果外层也存在x变量会怎么样?

Sohnny

2019-06-08 22:32

感谢波总实操案例。辛苦了,赞。

JFinal

2019-06-08 23:01

@Sohnny 变量是打通的,所心所欲的使用

demo 中传递的参数完全可以去掉,模板中的变量在各处本身就是可以使用的,除非你在函数内部、指令内部使用 scope.setLocal(...)、scope.getLocal(...) 以及模板里头 #setLocal 指令,这样的变量是局部变量

山东小木

2019-06-09 11:29