打造一个自己的模板引擎(一)
再开始之前,需要先了解html解析器工作原理,如果不明白的请参看前篇 html解析器工作原理 http://blog.csdn.net/xuesong123/article/details/8637159
首先定义语法,你打算用什么样的语法作为模板语言的语法. 可以选择的方式有很多种,这里我们用jstl的语法,用什么样的语法都没有关系,这个是解析器识别的问题。
我们的语法这样定义, 以<开头, 后面是节点名, 例如: <c:forEach items="1,2,3">, 这表示一个循环标签. 为了支持自定义标签,我们定义一个map, 把所以支持的标签都放到map里面,假设有如下标签:
map.put("c:if", "test.mytest.taglib.IfTag");
map.put("c:forEach", "test.mytest.taglib.forEachTag");
...
先随便写几个,跑起来之后在扩展标签.
先来说一下如何渲染一个模板, 对于任何一个包含标签的模板文件我们都可以把他看成一个xml文档, 渲染的过程其实就是一个xml格式化的文档, 这样理解的话这个问题就很简单了。
任何一段程序,其实都只有三种结构:顺序执行,条件执行,循环执行。其实xml的树形结构本来就跟这种结构很相似。我们只需要在渲染的时候去执行以下节点,根据节点的返回值来决定是否渲染节点的子节点就可以了。
例如如下一个html文档:
<c:if test="${1 == 1}">
<h1>Hello World!</h1>
</c:if>
假设我们能够用dom4j把他读出来,并变成一个dom结构的树, 然后我们自己实现一个格式化的类,对每一个节点都运算一下,来决定是否要渲染子节点就可以了。
这样说就太简单了,而且一个模板文件也不可能是一个语法严格的xml文档. 但是大体思路是可以的。
现在需要我们自己实现一个解析器,这个解析器跟html解析器的一个区别就是,当遇到一个节点的时候,需要检查一下它究竟是一个我们定义的标签还是认为他是一个普通的文本.
上面我们已经定义好了支持的标签,因此只需要从map里面检查一下即可,如果没有就认为是普通文本,解析为文本节点即可。
对于普通的文本我们需要检查一下里面是否包含${, 如果有的话,要尝试读取出表达式,并封装成一个表达式节点,按照普通的文本节点处理即可。另外用一个nodeType标识一下。
解析完成之后,我们已经有了整棵dom树,现在来渲染它。
最简单的方式就是使用递归渲染:
void render(Node node, PageContext context){ Writer writer out = pageContext.getOut(); // 如果是文本节点直接输出 if(node.nodeType == TEXT) { out.write(node.toString()); return; } // el表达式 if(node.nodeType == EXPR) { out.write(context.eval(node.toString())); return; } String className = tagLibrary.getClassName(node.getNodeName()); Tag tag = TagFactory.create(className); tag.setPageContext(context); tag.setParent(...); // 设置标签的所有属性 Map<String, String> attributes = node.getAttributes(); // 依次调用标签的setter方法 tag.setXXX(context.eval(attributes[XXX])); int flag = tag.startTag(); // 如果标签实现了BodyTag, 还需要调用一下doInitBody // 如果标签返回了EVAL_BODY_BUFFERED, 还需要掉pageContext的pushBody方法 // 为了简单起见,这些问题暂时都不考虑,跑起来再说 if(flag != Tag.SKIP_BODY) { flag = TAG.EVAL_BODY_AGAIN; // 如果标签返回的是再执行一次的话,那么就重新执行一次 // 像c:forEach标签都是这样运行的 while(flag == TAG.EVAL_BODY_AGAIN) { List<Node> childNodes = node.getChildNodes(); for(Node n : childNodes) { render(n, context); } flag = tag.doEndTag(); } }}总之有很多差强人意的地方,下一篇再说用另外一种方式渲染。避免递归,并且要增加对tag的完整支持。
完整的工程源码可以去 http://ayada.googlecode.com/svn/trunk/ 下载
也可以发邮件给我索取: xuesong.net@163.com