162 lines
5.7 KiB
Java
162 lines
5.7 KiB
Java
/**
|
||
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
package com.jfinal.template.stat.ast;
|
||
|
||
import com.jfinal.template.EngineConfig;
|
||
import com.jfinal.template.Env;
|
||
import com.jfinal.template.expr.ast.Assign;
|
||
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.source.ISource;
|
||
import com.jfinal.template.stat.Ctrl;
|
||
import com.jfinal.template.stat.Location;
|
||
import com.jfinal.template.stat.ParseException;
|
||
import com.jfinal.template.stat.Parser;
|
||
import com.jfinal.template.stat.Scope;
|
||
|
||
/**
|
||
* Include
|
||
*
|
||
* 1:父模板被缓存时,被 include 的模板会被间接缓存,无需关心缓存问题
|
||
* 2:同一个模板文件被多个父模板 include,所处的背景环境不同,例如各父模板中定义的模板函数不同
|
||
* 各父模板所处的相对路径不同,所以多个父模板不能共用一次 parse 出来的结果,而是在每个被include
|
||
* 的地方重新 parse
|
||
*
|
||
* <pre>
|
||
* 两种用法:
|
||
* 1:只传入一个参数,参数必须是 String 常量,如果希望第一个参数是变量可以使用 #render 指令去实现
|
||
* #include("_hot.html")
|
||
*
|
||
* 2:传入任意多个参数,除第一个参数以外的所有参数必须是赋值表达式,用于实现参数传递功能
|
||
* #include("_hot.html", title = "热门新闻", list = newsList)
|
||
*
|
||
* 上例中传递了 title、list 两个参数,可以代替父模板中的 #set 指令传参方式
|
||
* 并且此方式传入的参数只在子模板作用域有效,不会污染父模板作用域
|
||
*
|
||
* 这种传参方式有利于将子模板模块化,例如上例的调用改成如下的参数:
|
||
* #include("_hot.html", title = "热门项目", list = projectList)
|
||
* 通过这种传参方式在子模板 _hot.html 之中,完全不需要修改对于 title 与 list
|
||
* 这两个变量的处理代码,就实现了对 “热门项目” 数据的渲染
|
||
* </pre>
|
||
*/
|
||
public class Include extends Stat {
|
||
|
||
private Assign[] assignArray;
|
||
private Stat stat;
|
||
|
||
public Include(Env env, ExprList exprList, String parentFileName, Location location) {
|
||
int len = exprList.length();
|
||
if (len == 0) {
|
||
throw new ParseException("The parameter of #include directive can not be blank", location);
|
||
}
|
||
// 第一个参数必须为 String 类型
|
||
Expr expr = exprList.getExpr(0);
|
||
if (expr instanceof Const && ((Const)expr).isStr()) {
|
||
} else {
|
||
throw new ParseException("The first parameter of #include directive must be String", location);
|
||
}
|
||
// 其它参数必须为赋值表达式
|
||
if (len > 1) {
|
||
for (int i = 1; i < len; i++) {
|
||
if (!(exprList.getExpr(i) instanceof Assign)) {
|
||
throw new ParseException("The " + (i + 1) + "th parameter of #include directive must be an assignment expression", location);
|
||
}
|
||
}
|
||
}
|
||
|
||
parseSubTemplate(env, ((Const)expr).getStr(), parentFileName, location);
|
||
getAssignExpression(exprList);
|
||
}
|
||
|
||
private void parseSubTemplate(Env env, String fileName, String parentFileName, Location location) {
|
||
String subFileName = getSubFileName(fileName, parentFileName);
|
||
EngineConfig config = env.getEngineConfig();
|
||
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||
ISource fileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
|
||
try {
|
||
Parser parser = new Parser(env, fileSource.getContent(), subFileName);
|
||
if (config.isDevMode()) {
|
||
env.addSource(fileSource);
|
||
}
|
||
this.stat = parser.parse().getActualStat();
|
||
} catch (Exception e) {
|
||
// 文件路径不正确抛出异常时添加 location 信息
|
||
throw new ParseException(e.getMessage(), location, e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取在父模板之下子模板的最终文件名,子模板目录相对于父模板文件目录来确定
|
||
* 以 "/" 打头则以 baseTemplatePath 为根,否则以父文件所在路径为根
|
||
*/
|
||
public static String getSubFileName(String fileName, String parentFileName) {
|
||
if (parentFileName == null) {
|
||
return fileName;
|
||
}
|
||
if (fileName.startsWith("/")) {
|
||
return fileName;
|
||
}
|
||
int index = parentFileName.lastIndexOf('/');
|
||
if (index == -1) {
|
||
return fileName;
|
||
}
|
||
return parentFileName.substring(0, index + 1) + fileName;
|
||
}
|
||
|
||
private void getAssignExpression(ExprList exprList) {
|
||
int len = exprList.length();
|
||
if (len > 1) {
|
||
assignArray = new Assign[len - 1];
|
||
for (int i = 0; i < assignArray.length; i++) {
|
||
assignArray[i] = (Assign)exprList.getExpr(i + 1);
|
||
}
|
||
} else {
|
||
assignArray = null;
|
||
}
|
||
}
|
||
|
||
public void exec(Env env, Scope scope, Writer writer) {
|
||
scope = new Scope(scope);
|
||
if (assignArray != null) {
|
||
evalAssignExpression(scope);
|
||
}
|
||
stat.exec(env, scope, writer);
|
||
scope.getCtrl().setJumpNone();
|
||
}
|
||
|
||
private void evalAssignExpression(Scope scope) {
|
||
Ctrl ctrl = scope.getCtrl();
|
||
try {
|
||
ctrl.setLocalAssignment();
|
||
for (Assign assign : assignArray) {
|
||
assign.eval(scope);
|
||
}
|
||
} finally {
|
||
ctrl.setWisdomAssignment();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|