PebbleTemplates 模版解析处理简单说明
以下是一个简单的说明如何集成起来的,详细的可以阅读完整源码
调用
在getPebbleTemplate 部分,参考处理
private PebbleTemplate getPebbleTemplate(String templateName, Loader loader, Object cacheKey) {
// 获取reader
Reader templateReader = loader.getReader(cacheKey);
try {
this.logger.trace("Tokenizing template named {}", templateName);
// 词法解析
LexerImpl lexer = new LexerImpl(this.syntax,
this.extensionRegistry.getUnaryOperators().values(),
this.extensionRegistry.getBinaryOperators().values());
// 转换为TokenStream
TokenStream tokenStream = lexer.tokenize(templateReader, templateName);
this.logger.trace("TokenStream: {}", tokenStream);
// 解析器
Parser parser = new ParserImpl(this.extensionRegistry.getUnaryOperators(),
this.extensionRegistry.getBinaryOperators(), this.extensionRegistry.getTokenParsers(),
this.parserOptions);
RootNode root = parser.parse(tokenStream);
PebbleTemplateImpl instance = new PebbleTemplateImpl(this, root, templateName);
for (NodeVisitorFactory visitorFactory : this.extensionRegistry.getNodeVisitors()) {
visitorFactory.createVisitor(instance).visit(root);
}
return instance;
} finally {
try {
templateReader.close();
} catch (IOException e) {
// can't do much about it
}
}
}
解析
public BodyNode subparse(StoppingCondition stopCondition) {
// 通过rootnode ,进行遍历解析转换为RenderableNode(这个也是我们进行扩展开发的处理,对于自己开发的TokenParser 需要返回一个RenderableNode)
// these nodes will be the children of the root node
List<RenderableNode> nodes = new ArrayList<>();
Token token;
while (!this.stream.isEOF()) {
switch (this.stream.current().getType()) {
case TEXT:
/*
* The current token is a text token. Not much to do here other
* than convert it to a text Node.
*/
token = this.stream.current();
nodes.add(new TextNode(token.getValue(), token.getLineNumber()));
this.stream.next();
break;
case PRINT_START:
/*
* We are entering a print delimited region at this point. These
* regions will contain some sort of expression so let's pass
* control to our expression parser.
*/
// go to the next token because the current one is just the
// opening delimiter
token = this.stream.next();
Expression<?> expression = this.expressionParser.parseExpression();
nodes.add(new PrintNode(expression, token.getLineNumber()));
// we expect to see a print closing delimiter
this.stream.expect(Token.Type.PRINT_END);
break;
case EXECUTE_START:
// go to the next token because the current one is just the
// opening delimiter
this.stream.next();
token = this.stream.current();
/*
* We expect a name token at the beginning of every block.
*
* We do not use stream.expect() because it consumes the current
* token. The current token may be needed by a token parser
* which has provided a stopping condition. Ex. the 'if' token
* parser may need to check if the current token is either
* 'endif' or 'else' and act accordingly, thus we should not
* consume it.
*/
if (!Token.Type.NAME.equals(token.getType())) {
throw new ParserException(null, "A block must start with a tag name.",
token.getLineNumber(),
this.stream.getFilename());
}
// If this method was executed using a TokenParser and
// that parser provided a stopping condition (ex. checking
// for the 'endif' token) let's check for that condition
// now.
if (stopCondition != null && stopCondition.evaluate(token)) {
return new BodyNode(token.getLineNumber(), nodes);
}
// find an appropriate parser for this name
TokenParser tokenParser = this.tokenParsers.get(token.getValue());
if (tokenParser == null) {
throw new ParserException(null,
String.format("Unexpected tag name \"%s\"", token.getValue()),
token.getLineNumber(), this.stream.getFilename());
}
RenderableNode node = tokenParser.parse(token, this);
// node might be null (ex. "extend" token parser)
if (node != null) {
nodes.add(node);
}
break;
default:
throw new ParserException(null, "Parser ended in undefined state.",
this.stream.current().getLineNumber(),
this.stream.getFilename());
}
}
// create the root node with the children that we have found
return new BodyNode(this.stream.current().getLineNumber(), nodes);
}
渲染处理
// 使用调用了rootNode 的render方法,当然还包含了对于层级关系的处理
// 每个node 的实现都会包含一个render 方法,内部会调用下PebbleTemplateImpl 提供的一些替换方法
private void evaluate(Writer writer, EvaluationContextImpl context) throws IOException {
if (context.getExecutorService() != null) {
writer = new FutureWriter(writer);
}
writer = LimitedSizeWriter.from(writer, context);
this.rootNode.render(this, writer, context);
/*
* If the current template has a parent then we know the current template
* was only used to evaluate a very small subset of tags such as "set" and "import".
* We now evaluate the parent template as to evaluate all of the actual content.
* When evaluating the parent template, it will check the child template for overridden blocks.
*/
if (context.getHierarchy().getParent() != null) {
PebbleTemplateImpl parent = context.getHierarchy().getParent();
context.getHierarchy().ascend();
parent.evaluate(writer, context);
}
writer.flush();
}
说明
PebbleTemplates 的灵活性以及代码还是比较简洁的,简单了解一些内部模版解析处理,对于开发还是比较有用的
参考资料
https://github.com/PebbleTemplates/pebble
https://pebbletemplates.io/
https://github.com/rongfengliang/pebbletemplates-learning.git