本文学习Mustache 模板,并使用Java api动态生成HTML内容。Mustache是创建动态内的无逻辑模板引擎,如HTML,配置文件等。
Mustache属于无逻辑模板引擎,因为其不支持if-else和for语句,主要是有{ {}}括起来的模板变量及包含模板数据的模型对象组成,因为双括号看起来像胡子,因此得名mustache
模板支持多种语言的客户端和服务器,当然也可以使用java库解析模板,需要引入相应依赖:
Java 8+
com.github.spullara.mustache.java
compiler
0.9.4
Java 6/7:
com.github.spullara.mustache.java
compiler
0.8.18
读者也可以查找最新版本。
示例需求如下:
下面模板用于显示代办任务信息,命名为todo.mustache:
{
{title}}
Created on {
{createdOn}}
{
{text}}
在{ {}}中的模板变量可以是Java类的方法和属性,也是Map对象的key。
编译模板代码:
MustacheFactory mf = new DefaultMustacheFactory();
Mustache m = mf.compile("todo.mustache");
MustacheFactory 在类路径下搜索模板文件,我们的模板文件在src/main/resources
路径下。
提供模板数据是Todo类的实例:
public class Todo {
private String title;
private String text;
private boolean done;
private Date createdOn;
private Date completedOn;
// constructors, getters and setters
}
执行模板生成HTML内容的代码为:
Todo todo = new Todo("Todo 1", "Description");
StringWriter writer = new StringWriter();
m.execute(writer, todo).flush();
String html = writer.toString();
下面看如何列出所有代办事项,即迭代列表数据。这需要使用Mustache的节(Section),节是根据上下文中key的值决定重复一次或多次的代码块。
示例如下:
{
{#todo}}
{
{/todo}}
节以#
号开头,/
结尾,其中的变量会被解析用于渲染实际内容。下面介绍依据键的值可能遇到的场景。
首先定义 todo-section.mustache 模板:
{
{#todo}}
{
{title}}
Created on {
{createdOn}}
{
{text}}
{
{/todo}}
来看看解析动作:
@Test
public void givenTodoObject_whenGetHtml_thenSuccess()
throws IOException {
Todo todo = new Todo("Todo 1", "Todo description");
Mustache m = MustacheUtil.getMustacheFactory()
.compile("todo.mustache");
Map context = new HashMap<>();
context.put("todo", todo);
String expected = "Todo 1
";
assertThat(executeTemplate(m, todo)).contains(expected);
}
我们再看看另一个模板:
{
{#todos}}
{
{title}}
{
{/todos}}
使用代办列表数据进行测试:
@Test
public void givenTodoList_whenGetHtml_thenSuccess()
throws IOException {
Mustache m = MustacheUtil.getMustacheFactory()
.compile("todos.mustache");
List todos = Arrays.asList(
new Todo("Todo 1", "Todo description"),
new Todo("Todo 2", "Todo description another"),
new Todo("Todo 3", "Todo description another")
);
Map context = new HashMap<>();
context.put("todos", todos);
assertThat(executeTemplate(m, context))
.contains("Todo 1
")
.contains("Todo 2
")
.contains("Todo 3
");
}
首先测试null值:
@Test
public void givenNullTodoObject_whenGetHtml_thenEmptyHtml()
throws IOException {
Mustache m = MustacheUtil.getMustacheFactory().compile("todo-section.mustache");
Map context = new HashMap<>();
assertThat(executeTemplate(m, context)).isEmpty();
}
同样使用空列表测试todos.mustache :
@Test
public void givenEmptyList_whenGetHtml_thenEmptyHtml()
throws IOException {
Mustache m = MustacheUtil.getMustacheFactory()
.compile("todos.mustache");
Map context = new HashMap<>();
assertThat(executeTemplate(m, context)).isEmpty();;
}
else节(inverted section)用于当上下文变量值为false、null或空列表时渲染一次,类似于if...else...
,但else
部分只执行一次。
使用^
符号开始,/
结束:
{
{#todos}}
{
{title}}
{
{/todos}}
{
{^todos}}
No todos!
{
{/todos}}
使用空列表进行测试:
@Test
public void givenEmptyList_whenGetHtmlUsingInvertedSection_thenHtml()
throws IOException {
Mustache m = MustacheUtil.getMustacheFactory()
.compile("todos-inverted-section.mustache");
Map context = new HashMap<>();
assertThat(executeTemplate(m, context).trim())
.isEqualTo("No todos!
");
}
模板中变量的值可以来自函数或lambda表达式。下面示例传入lambda表达式:
首先定义todos-lambda.mustache模板:
{
{#todos}}
{
{title}}{
{#handleDone}}{
{doneSince}}{
{/handleDone}}
{
{/todos}}
然后再Todo类中增加两个函数:
public Function
最终生成内容为:
Todo 1
Todo 2
Todo 3Done 5 minutes ago
完整测试代码为:
import com.github.mustachejava.Mustache;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.time.Instant;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
class MustacheTest {
private String executeTemplate(Mustache m, Map context) throws IOException {
StringWriter writer = new StringWriter();
m.execute(writer, context).flush();
return writer.toString();
}
@Test
public void givenTodoList_whenGetHtmlUsingLamdba_thenHtml() throws IOException {
Mustache m = MustacheUtil.getMustacheFactory().compile("todos-lambda.mustache");
List todos = Arrays.asList(
new Todo("Todo 1", "Todo description"),
new Todo("Todo 2", "Todo description another"),
new Todo("Todo 3", "Todo description another")
);
todos.get(2).setDone(true);
todos.get(2).setCompletedOn(Date.from(Instant.now().plusSeconds(300)));
Map context = new HashMap<>();
context.put("todos", todos);
assertThat(executeTemplate(m, context).trim()).contains("Done 5 minutes ago");
}
}
工具类MustacheUtil代码:
public class MustacheUtil {
private MustacheUtil(){}
private static final MustacheFactory MF = new DefaultMustacheFactory();
public static MustacheFactory getMustacheFactory(){
return MF;
}
}
本文介绍了如何使用mustache 模板引擎,包括条件语法和lambda表达式,以及如何使用Java Api编译、渲染模板并生成目标内容。