在前面我们定义了一些常用标签,本篇将会看看如何实现它们。
为了能够让用户自定义标签处理器,并让它可以被SharkDart的编译器识别,我们需要定义一些基础类和约定。
首先我们会有一个全局的标签处理器仓库,可以让我们把自定义的处理器注册上去,同时也可以让编译器来查询。
var tagRepository = new TagRepository();
class TagRepository {
var store = new Map<String, TagHandler>();
TagHandler find(String tagName) => store[tagName];
void register(String tagName, TagHandler handler) => store[tagName] = handler;
}
然后是处理器的基类和返回结果类:
abstract class TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag);
}
class TagHandleResult {
List<CompilableElement> elements;
List tail;
TagHandleResult(this.elements, this.tail);
}
我们自定义的处理器需要继承自`TagHandler`,并实现其`handle(...)`方法。
另外,在模板中经常需要生成一些不重复的变量名,所以我们再提供一个全局的id生成函数:
int idForTags = 0;
nextId() => idForTags++;
由于Dart代码只能在单线程中运行,所以不用担心拿到重复的值。
最后需要提供一个函数,把SharkDart内置的处理器注册上去:
registerBuiltInTags() {
tagRepository
..register('xxx', new XxxTagHandler())
..register('yyy', new YxxTagHandler())
}
var builtInTags = registerBuiltInTags();
后面一句定义的`builtInTags`变量实际上没有用,只是dart语法不允许在library的全局域里直接调用一个函数,真正起作用的是后面的`registerBuiltInTags()`
有了这些基础设施,我们就可以来实现自己的标签了。
## if
`if`的实现比较简单,基本上就是在代码中写上dart的`if`语句即可:
class IfTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var condition = tag.tagParams.first.paramVariable;
return new TagHandleResult([
stmt('if ($condition) {'),
tag.body.toCompilable(),
stmt('}')
], nodesAfterTag);
}
}
注意`if(...){`和`}`都是`stmt`,表示它们将会以代码形式原样输出在最终代码里。另外它把`nodesAfterTag`原封不动的传了回去,因为它不需要处理后续结点。
## else
`else`的实现也很简单,连参数都不需要:
class ElseTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
return new TagHandleResult([
stmt('else {'),
tag.body.toCompilable(),
stmt('}')
], nodesAfterTag);
}
}
跟`if`一样,不需要多讲。
## elseif
`elseif`跟前面差不多,只是它要输出为`else if`:
class ElseIfTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var condition = tag.tagParams.first.paramVariable;
return new TagHandleResult([
stmt('else if ($condition) {'),
tag.body.toCompilable(),
stmt('}')
], nodesAfterTag);
}
}
### 模板实例
将前面三个标签结合起来,写一个SharkDart的模板:
if_elseif_else1.shark
@params(int num)
Hello,
@if(num == 1) {
air!
} @elseif(num == 2) {
sky!
} @else {
world!
}
### 生成dart代码
将会编译成以下代码:
library shark.views.tags.if_elseif_else1;
String render({int num, String implicitBody_()}) {
if (implicitBody_ == null) {
implicitBody_ = () => '';
}
var _sb_ = new StringBuffer();
_sb_.writeln('Hello,');
_sb_.write('');
if (num == 1) {
_sb_.writeln('');
_sb_.writeln(' air!');
_sb_.write('');
}
else if (num == 2) {
_sb_.writeln('');
_sb_.writeln(' sky!');
_sb_.write('');
}
else {
_sb_.writeln('');
_sb_.writeln(' world!');
_sb_.write('');
}
_sb_.writeln('');
_sb_.write('');
return _sb_.toString();
}
## for
`for`标签要编译为dart中的`for(var item in list)`,但为了能让用户使用更方便,它额外提供了一些变量供使用,如`index`, `isFirst`等。同时还可以给定一个分隔符,自动放在两个元素之间,在生成json类似的数据时很有用。
它的定义如下:
class ForTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var separator = tag.getParamAsString('separator', '');
var main = tag.tagParams.first;
var type = (main.paramType == null ? 'var' : main.paramType);
var variable = main.paramVariable;
var collections = (main.paramDescription as SharkExpression).expression;
var indexVar = 'index_${nextId()}';
var countVar = 'total_${nextId()}';
return new TagHandleResult([
stmt('if ($collections != null) {'),
stmt(' int ${indexVar} = 0;'),
stmt(' int ${countVar} = ${collections}.length;'),
stmt(' for (var $variable in $collections) {'),
stmt(' int ${variable}_index = ${indexVar};'),
stmt(' bool ${variable}_isFirst = ${indexVar} == 0;'),
stmt(' bool ${variable}_isLast = ${indexVar} == ${countVar} - 1;'),
stmt(' bool ${variable}_isOdd = ${indexVar} % 2 == 1;'),
stmt(' bool ${variable}_isEven = ${indexVar} % 2 == 0;'),
stmt(' if(!${variable}_isFirst) {'),
text(separator),
stmt(' }'),
stmt(' ${indexVar}++;'),
tag.body.toCompilable(),
stmt(' }'),
stmt('}')
], nodesAfterTag
);
}
}
有这几点需要注意:
使用了很多stmt
来插入多行dart代码,以实现前面所说的额外变量的功能
nextId()
是一个全局函数,每次调用都会生成一个增1的数字,以保证变量名不相同
增加了if(collection!=null)
的判断,是为了让for
后面可加上else
标签
for3.shark
@params(List
@for(user: users, separator: '***') {
Hello, @user
} @else {
There is no user found
}
library shark.views.tags.for3;
String render({List
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
if (users != null) {
int index_2 = 0;
int total_3 = users.length;
for (var user in users) {
int user_index = index_2;
bool user_isFirst = index_2 == 0;
bool user_isLast = index_2 == total_3 - 1;
bool user_isOdd = index_2 % 2 == 1;
bool user_isEven = index_2 % 2 == 0;
if (!user_isFirst) {
_sb_.write('***');
}
index_2++;
_sb_.writeln('');
_sb_.write(' # index: ');
_sb_.write(user_index);
_sb_.writeln('');
_sb_.write(' # isFirst: ');
_sb_.write(user_isFirst);
_sb_.writeln('');
_sb_.write(' # isLast: ');
_sb_.write(user_isLast);
_sb_.writeln('');
_sb_.write(' # isOdd: ');
_sb_.write(user_isOdd);
_sb_.writeln('');
_sb_.write(' # isEven: ');
_sb_.write(user_isEven);
_sb_.writeln('');
_sb_.write(' Hello, ');
_sb_.write(user);
_sb_.writeln('');
_sb_.writeln(' ------------');
_sb_.write('');
}
}
else {
_sb_.writeln('');
_sb_.writeln(' There is no user found');
_sb_.write('');
}
sb.writeln('');
sb.write('');
return sb.toString();
}
dart
标签可以让我们在模板中嵌入一段dart代码,它的实现也比较简单:
class DartTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var content = '';
if (!tag.body.isEmpty) {
var trim = tag.getParamAsBool('trim');
if (trim) {
tag.body.trim();
}
content = tag.body.elements.elements.first.toString();
}
return new TagHandleResult([
stmt(content),
], nodesAfterTag);
}
}
注意它可以接受一个trim
参数,用于把代码段前后的空白去掉。
dart1.shark
@!dart {{{
hello(who) => 'Hello, $who!';
}}}
library shark.views.tags.dart1;
String render({String implicitBody()}) {
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
hello(who) => 'Hello, $who!';
sb.writeln('');
sb.writeln('');
sb.write('
plainText
跟dart
标签很相似,不同的是它会把内容当作文本输出。
class PlainTextTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var content = '';
if (tag.body != null) {
var trim = tag.getParamAsBool('trim', false);
if (trim) {
tag.body.trim();
}
content = tag.body.elements.elements.first.toString();
}
return new TagHandleResult([
text(content)
], nodesAfterTag);
}
}
它也同样可以接受一个trim
参数,用于去除文本两端的空白。
library shark.views.tags.plainText1;
String render({String implicitBody()}) {
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
sb.writeln('
params
可以用来给生成的render()
入口函数指定参数。
class ParamsTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
var params = "";
if (tag.tagParams.isNotEmpty) {
params = tag.tagParams.map((param) => param.toString()).join(', ');
}
if (tag.hasNoBody) {
return new TagHandleResult([
functionParams(params),
new SharkNodeList(nodesAfterTag).toCompilable(),
], []);
} else {
return new TagHandleResult([
functionParams(params),
tag.body.toCompilable(),
], nodesAfterTag);
}
}
}
需要注意的是,如果params
标签没有提供花括号括起来的主体,它会把后续结点当作主体,所以在处理时需要判断一下。
params1.shark
@params(String user) {
}
library shark.views.tags.params1;
String render({String user, String implicitBody()}) {
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
sb.writeln('');
sb.write('
extends
的实现比较复杂,因为它需要把当前模板的内容以某种方式传递给另一个模板,再把最终的结果渲染出来。有这几个地方需要注意:
extends
和render
标签共用了同一个处理器,不同的是,当没有提供标签主体时,extends
会把后续结点当主体,而render
不会。当然这个默认行为也可以在标签参数中改变:传入enableImplicitBody:true/false
当继承另一个模板时,如果模板路径以.
开头,如extends(./another)
,则会直接导入,否则会当作相对于模板根目录的相对路径。对于后者,我们需要先算出它的绝对路径,再计算出与当前模板的相对路径,然后导入它。
继承另一个模板,实际上会调用另一个模板生成的render()
函数,然后把自己的内容以another.render(implicitBody: ()=> { current content }
的方式得到结果。
下面的代码有些长,但应该可以快速理解它的意思。
class RenderTagHandler extends TagHandler {
bool enableImplicitBodyByDefault;
RenderTagHandler(this.enableImplicitBodyByDefault);
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
return new _RenderTagInternalHandler(tag, nodesAfterTag, this.enableImplicitBodyByDefault).handle();
}
}
class RenderTagInternalHandler {
final targetTemplateVar = 'shark_render_${nextId()}';
bool enableImplicitBody;
List<_ParamForRenderTag> renderParams;
SharkTag tag;
List nodesAfterTag;
_RenderTagInternalHandler(this.tag, this.nodesAfterTag, enableImplicitBodyByDefault) {
this.enableImplicitBody = this.tag.getParamAsBool('enableImplicitBody', enableImplicitBodyByDefault);
this.renderParams = _compileRenderParams();
}
TagHandleResult handle() {
var compilableItems = [ buildImportPathStmt() ];
for (var p in renderParams) {
compilableItems.addAll(_preCalculateParamValue(p));
}
compilableItems.addAll(_renderTargetTemplate());
if (tag.hasNoBody && enableImplicitBody) {
return new TagHandleResult(compilableItems, []);
} else {
return new TagHandleResult(compilableItems, nodesAfterTag);
}
}
_ImportPath buildImportPathStmt() {
var targetTemplateName = tag.tagParams.first.paramVariable;
if (targetTemplateName.startsWith('.')) {
return importStmt('import \'${targetTemplateName}.dart\' as $targetTemplateVar;');
} else {
return importStmtFromTemplate((CompilableTemplate template) {
var templateParentPath = new File(path.join(template.templateRootDir.path, template.relativePath)).parent.path;
var targetTemplateFilePath = path.join(template.templateRootDir.path, targetTemplateName);
var relative = path.relative(targetTemplateFilePath, from:templateParentPath);
return 'import \'${relative}.dart\' as $targetTemplateVar;';
});
}
}
List<_ParamForRenderTag> _compileRenderParams() {
return tag.tagParams.sublist(1).map((param) => new _ParamForRenderTag(
param.paramVariable,
'${param.paramVariable}_${nextId()}',
param.paramDescription.toCompilable()
)).toList();
}
List
return [
stmt('var ${p.paramGeneratedVariable} = () {'),
stmt(' var _sb_ = new StringBuffer();'),
p.paramValue,
stmt(' return _sb_.toString();'),
stmt('}();')
];
}
List
var items = [];
var paramStr = renderParams.map((p) => "${p.paramName}: ${p.paramGeneratedVariable}").join(', ');
items.add(stmt('_sb_.write(${targetTemplateVar}.render($paramStr, implicitBody_ : () {'));
items.add(stmt(' var _sb_ = new StringBuffer();'));
if (tag.hasNoBody && enableImplicitBody) {
items.add(new SharkNodeList(nodesAfterTag).toCompilable());
} else if (tag.body != null) {
items.add(tag.body.toCompilable());
}
items.add(stmt(' return _sb_.toString();'));
items.add(stmt('}));'));
return items;
}
}
class _ParamForRenderTag {
String paramName;
String paramGeneratedVariable;
CompilableElement paramValue;
_ParamForRenderTag(this.paramName, this.paramGeneratedVariable, this.paramValue);
toString() => '$paramName : $paramGeneratedVariable : $paramValue';
}
class _ImportPath {
String targetFilePath;
String asName;
}
layout1.shark
@params(String user)
extends1.shark
@params(String user)
@extends(./layout1, user: user)
This is inner page with @user!
将会生成以下dart代码:
layout1.dart
library shark.views.tags.layout1;
String render({String user, String implicitBody()}) {
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
sb.write('
extends1.dart
library shark.views.tags.extends1;
import './layout1.dart' as _shark_render_0;
String render({String user, String implicitBody()}) {
if (implicitBody == null) {
implicitBody_ = () => '';
}
var sb = new StringBuffer();
var user_1 = () {
var _sb_ = new StringBuffer();
_sb_.write(user);
return _sb_.toString();
}();
sb.write(shark_render_0.render(user: user_1, implicitBody : () {
var _sb_ = new StringBuffer();
_sb_.write('This is inner page with ');
_sb_.write(user);
_sb_.writeln('!');
_sb_.write('');
return _sb_.toString();
}));
return sb.toString();
}
render
标签与上面的extends
标签使用的是同一个标签处理器,因为它们的逻辑实际上非常相似,但通过不同的命名可以更准确的表达出使用者的意愿。
不同的地方在于,当标签没有提供主体时,extends
默认会使用后续节点当主体,而render
不会。
通过查看两者注册到tagReposity
的代码就能看出:
tagRepository.register('extends', new RenderTagHandler(true));
tagRepository.register('render', new RenderTagHandler(false));
后面的true/false
是指是否enableImplicitBody
的功能。
renderBody
的内容比较简单,它直接输出调用implicitBody_()
的内容。可与extends
和render
标签使用,处理它们通过标签主体传过来的内容。
class RenderBodyTagHandler extends TagHandler {
TagHandleResult handle(SharkTag tag, List nodesAfterTag) {
return new TagHandleResult([
expr('implicitBody_()')
], nodesAfterTag);
}
}
下面我们把上面定义的各标签处理器注册到全局的标签仓库中:
var builtInTags = initializeBuiltInTags();
initializeBuiltInTags() {
tagRepository.register('params', new ParamsTagHandler());
tagRepository.register('if', new IfTagHandler());
tagRepository.register('else', new ElseTagHandler());
tagRepository.register('elseif', new ElseIfTagHandler());
tagRepository.register('for', new ForTagHandler());
tagRepository.register('extends', new RenderTagHandler(true));
tagRepository.register('render', new RenderTagHandler(false));
tagRepository.register('renderBody', new RenderBodyTagHandler());
tagRepository.register('dart', new DartTagHandler());
tagRepository.register('plainText', new PlainTextTagHandler());
}
看到这里大家已经明白如何实现自己的标签了吧。再结合前几篇的内容,相信大家对模板引擎应该有一些感性的认识了吧。
下一篇将介绍如何利用第三方工具实现自动监视模板目录,自动编译修改过的模板文件,以及对SharkDart的一些总结。