StringTemplate 4学习笔记
StringTemplate是一个模板引擎。模板引擎现在有很多,比如FreeMarker,比如velocity。ST有啥特别的呢?这就不提ST的兄弟项目Antlr了。Antlr是ANother Tool for Language Recognition的缩写,意思是另一个语法分析器。Antlr被大量用来开发DSL或者是分析现有语言。而作为ST的兄弟项目的ST,他的应用领域和Antlr一样,专注于语言分析之后的代码/文档生成。相较于普通的后端模板引擎,ST更加强大,也更加复杂一些。
Hello World
让我们来写一个简单的例子来体验ST的功能:
添加依赖:
1 | <dependency> |
编写例子:
1 | public class Hello { |
输出:
1 | Hello World |
StringTemplate的基本语法
StringTemplate是基于MVC思想设计的。其中View是使用ST语法编写的模板,Model是Antlr解析语法后的数据或者是其他的自定义数据,Controller就是ST类,他吧数据传递给模板,并可以渲染输出。
本质上,ST的语法很简单,分为两个部分:文本和属性表达式(attribute expressions)。文本部分会被原样输出。属性表达式会被求值后输出。默认属性表达式使用尖括号包围,当然这个是可以自定义的,比如如果你要生成HTML代码,用尖括号就非常麻烦了,可以改用%
。比如上面的例子中的模板:
1 | Hello, <name> |
这里有一个属性表达式,<name>
,通过.add("name","World")
设置属性的值后,<name>
就被替换为了World
,所以合起来的输出是Hello World
。
模板组(Groups of templates)
在说更多的ST语法之前,先说说ST的模板组。代码生成是具有复杂逻辑的,一般不会在一个模板中搞定,而是分解为多个小的模板,然后拼装起来。就算是网页模板引擎,也会支持引入子模板这样的功能。ST的模板更加强大,可以有输入参数,写起来和编程语言很类似。一个模板组里可以定义多个模板,只有在同一个模板组里的模板才可以互相调用。
看代码:
1 | STGroup stGroup = new STGroup(); |
定义了一个模板组stGroup,其中定义了两个模板。defineTemplate需要三个参数,分别是模板名称,模板参数,模板内容。代码中我们定义了两个模板,say和thing。这两个模板都需要一个name参数,并且say模板调用了thing模板。
然后我们拿到say模板的实例,传入参数,并渲染,得到输出Hello World
。
从文件中读入模板组
在代码中定义模板真是很费劲的,所以一般都是把模板定义在文件中,在代码中载入:
1 | // file /tmp/test.stg |
stg是模板组文件,其中可以定义多个模板,每个模板的格式是:模板名称(模板参数) ::= "模板内容"
如果模板的内容有多行,可以这么写:
1 | message(message) ::= << |
在代码中载入模板:
1 | STGroup group = new STGroupFile("/tmp/test.stg"); |
向模板赋值
传递数组
可以通过ST实例向模板传递参数。
1 | say(name) ::= "hello, <name>" |
如果多次add同一个参数的值,是不会覆盖的,而是追加,也就是每个参数其实是一个数组:
1 | say(name) ::= "hello, <name>" |
我们还可以控制多个值的输出格式:
1 | say(name) ::= "hello, <name;separator=\",\">" |
还可以使用模板来处理每一个元素:
1 | say(name) ::= "hello, <name:bracket();separator=\",\">" |
是不是非常灵活😁。但,其实还可以很灵活,比如我们这里定义了一个bracket模板,但其实功能很小,能不能像编程语言一样,弄个一个匿名函数呢?可以!那就是匿名模板:
1 | say(name) ::= "hello, <name:{x|[<x>]};separator=\",\">" |
传递对象
除了上面的例子中简单的字符串和数字外,ST还支持向模板传递自定义的类作为数据。比如我们定义一个User类:
1 | public static class User { |
注释的部分就是如何在模板中访问User类实例属性的方法。属性的范围是比较简单的,方法的访问有一套映射关系,比如对于o.p
,st会在类上尝试访问getP(), isP(), hasP(),如果都不存在,则会报错。
1 | ST st = new ST("<b>$u.id$</b>: $u.name$", '$', '$'); |
可以看到在ST模板中使用Java类是非常简单的。但是和js中直接使用json相比,Java中定义一个类,只是为了传递数据就定义一个类是非常繁琐的,还好,ST为我们提供了一种简便的方法来传递对象性质的信息:
1 | introduction(person) ::= "I'm <person.name>, I'm <person.age> years old." |
StringTemplate模板语
ST模板的语法很丰富,可以实现复杂逻辑,以处理复杂的代码生成需求。上面我提到的语法是最常用的语法,全面的模板语法可以参考stringtemplate4/cheatsheet.md