Mustache 模板教程

Mustache 模板教程

本文学习Mustache 模板,并使用Java api动态生成HTML内容。Mustache是创建动态内的无逻辑模板引擎,如HTML,配置文件等。

1. 概述

Mustache属于无逻辑模板引擎,因为其不支持if-else和for语句,主要是有{{}}括起来的模板变量及包含模板数据的模型对象组成,因为双括号看起来像胡子,因此得名mustache

模板支持多种语言的客户端和服务器,当然也可以使用java库解析模板,需要引入相应依赖:
Java 8+

<dependency>
    <groupId>com.github.spullara.mustache.java</groupId>
    <artifactId>compiler</artifactId>
    <version>0.9.4</version>
</dependency>

Java 6/7:

<dependency>
    <groupId>com.github.spullara.mustache.java</groupId>
    <artifactId>compiler</artifactId>
    <version>0.8.18</version>
</dependency>

读者也可以查找最新版本。

2. 实战使用

示例需求如下:

  1. 写个简单模板
  2. 使用Java Api编译模板
  3. 提供必要的数据生成动态内容

2.1. 简单模板

下面模板用于显示代办任务信息,命名为todo.mustache:

<h2>{{title}}</h2>
<small>Created on {{createdOn}}</small>
<p>{{text}}</p>

在{{}}中的模板变量可以是Java类的方法和属性,也是Map对象的key。

2.2. 编译模板

编译模板代码:

MustacheFactory mf = new DefaultMustacheFactory();
Mustache m = mf.compile("todo.mustache");

MustacheFactory 在类路径下搜索模板文件,我们的模板文件在src/main/resources路径下。

2.3. 执行模板

提供模板数据是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();

3. Mustache的节(Section)和迭代

下面看如何列出所有代办事项,即迭代列表数据。这需要使用Mustache的节(Section),节是根据上下文中key的值决定重复一次或多次的代码块。
示例如下:

{{#todo}}
<!-- Other code -->
{{/todo}}

节以#号开头,/结尾,其中的变量会被解析用于渲染实际内容。下面介绍依据键的值可能遇到的场景。

3.1. 非空列表或非假值

首先定义 todo-section.mustache 模板:

{{#todo}}
<h2>{{title}}</h2>
<small>Created on {{createdOn}}</small>
<p>{{text}}</p>
{{/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<String, Object> context = new HashMap<>();
    context.put("todo", todo);
  
    String expected = "<h2>Todo 1</h2>";
    assertThat(executeTemplate(m, todo)).contains(expected);
}

我们再看看另一个模板:

{{#todos}}
<h2>{{title}}</h2>
{{/todos}}

使用代办列表数据进行测试:

@Test
public void givenTodoList_whenGetHtml_thenSuccess() 
  throws IOException {
  
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todos.mustache");
  
    List<Todo> todos = Arrays.asList(
      new Todo("Todo 1", "Todo description"),
      new Todo("Todo 2", "Todo description another"),
      new Todo("Todo 3", "Todo description another")
    );
    Map<String, Object> context = new HashMap<>();
    context.put("todos", todos);
  
    assertThat(executeTemplate(m, context))
      .contains("<h2>Todo 1</h2>")
      .contains("<h2>Todo 2</h2>")
      .contains("<h2>Todo 3</h2>");
}

3.2. 空列表、假值或Null值

首先测试null值:

@Test
public void givenNullTodoObject_whenGetHtml_thenEmptyHtml() 
  throws IOException {
    Mustache m = MustacheUtil.getMustacheFactory().compile("todo-section.mustache");
    Map<String, Object> 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<String, Object> context = new HashMap<>();
    assertThat(executeTemplate(m, context)).isEmpty();;
}

3.3. 条件表达式

else节(inverted section)用于当上下文变量值为false、null或空列表时渲染一次,类似于if...else...,但else部分只执行一次。
使用^符号开始,/结束:

{{#todos}}
<h2>{{title}}</h2>
{{/todos}}
{{^todos}}
<p>No todos!</p>
{{/todos}}

使用空列表进行测试:

@Test
public void givenEmptyList_whenGetHtmlUsingInvertedSection_thenHtml() 
  throws IOException {
  
    Mustache m = MustacheUtil.getMustacheFactory()
      .compile("todos-inverted-section.mustache");
   
    Map<String, Object> context = new HashMap<>();
    assertThat(executeTemplate(m, context).trim())
      .isEqualTo("<p>No todos!</p>");
}

3.4. lambda表达式

模板中变量的值可以来自函数或lambda表达式。下面示例传入lambda表达式:

首先定义todos-lambda.mustache模板:

{{#todos}}
<h2>{{title}}{{#handleDone}}{{doneSince}}{{/handleDone}}</h2>
{{/todos}}

然后再Todo类中增加两个函数:

public Function<Object, Object> handleDone() {
    return (obj) -> done ? 
      String.format("<small>Done %s minutes ago<small>", obj) : "";
}

public long doneSince() {
   return done ? Duration
     .between(createdOn.toInstant(), completedOn.toInstant())
     .toMinutes() : 0;
}

最终生成内容为:

<h2>Todo 1</h2>
<h2>Todo 2</h2>
<h2>Todo 3<small>Done 5 minutes ago<small></h2>

完整测试代码为:

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<String, Object> 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<Todo> 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<String, Object> 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;
    }
}

4. 总结

本文介绍了如何使用mustache 模板引擎,包括条件语法和lambda表达式,以及如何使用Java Api编译、渲染模板并生成目标内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值