Pebble

2016/5/5 posted in  Java comments

Why Pebble

Java 也该拥有像 EJS 一样功能强大的轻量级模板。

What is Pebble

Pebble 是一个 Java 后端模板,和 Pebble Watch 没有关系(但它们我都使用过^_^)。

Pebble 模板的灵感来自于 PHP 的 Twig,类似的模板还有:NodeJs 中 SwigNunjucks 、 Python 中的 jinja。这些模板的语法都是相通的,可以说是:Learn once, write everywhere。他们的共性是:支持模板继承以达到代码复用的目的,保证项目的组织性和可维护性,内置了常用的操作符(==, !=, >, <, +, -, *, /, %, is ...)、过滤器,函数,校验,标签。并随时可以拓展自己想要的标签、过滤器、校验、函数、操作符...

以下翻译参考自 官方文档

Hello Pebble

Pom.xml 中添加

<dependency>
    <groupId>com.mitchellbosecke</groupId>
    <artifactId>pebble</artifactId>
    <version>2.2.1</version>
</dependency>

WEB-INF 中添加 hello.html,并添加以下内容:

<div>
    <h1>Welcome</h1>
    <p>{{msg}}</p>
</div>

Java 中使用 Pebble

PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate = engine.getTemplate("hello.html");

Map<String, Object> context = new HashMap<>();
context.put("msg", "Hello Pebble!");

Writer writer = new StringWriter();
compiledTemplate.evaluate(writer, context);

String output = writer.toString();
System.out.println(output);

输出结果:

<div>
    <h1>Welcome</h1>
    <p>Hello Pebble!</p>
</div>

语法属性

Pebble 模板语言基于 Mustache(前端模板 Vuejs 也是基于 Mustache ,虽看起来比较亲切,但混用时又会容易混淆...1 ),Pebble 主要有两种分隔符 {{ ... }}{% ... %}{{ ... }} 用于输出表达式的结果,{% ... %} 用于模板的流程控制:它可以包含 if 语句,定义一个父模板,定义一个模板块等。

变量

你可以直接输出变量,例如模板的数据中存在一个名为 foo 值为字符串 "bar" 的变量,你可以通过下面的方式输出 "bar"

{{ foo }}

你也可以使用 . 来访问变量的属性,如果属性名称包含特殊字符,还可以使用 [] 访问属性

{{ foo.bar }}
{{ foo["bar"] }}

foo.bar 的背后将会尝试以下方法访问变量 foobar 属性:

  • 如果 foo 是一个 Mapfoo.get("bar")
  • foo.getBar()
  • foo.isBar()
  • foo.hasBar()
  • foo.bar()
  • foo.bar 如果变量或者属性的值是 null,那么将输出空字符串。

类型安全

Pebble 模板的类型是动态的,类型安全问题只会在模板编译运行时发生。pebble 允许你选择如何处理类型安全问题,使用 strictVariables 进行设置。strictVariables 默认为 false,意味着:

{{ foo.bar }}

如果对象 foo 没有 bar 属性时将会被处理成 null(因此将会输出空字符串),如果 strictVariables 设置为 true,上面的表达式将会抛出异常。
strictVariables 设置为 false 时,你的表达式也是 null 保护的,以下表达式将会输出空字符串,即使 foo 和/或 bar 是 null:

{{ foo.bar.baz }}

过滤器

输出结果可以使用 filter 进行修改。过滤器通过管道符(|)与变量连接并且可以使用括号中传递参数。多个过滤器可以同时使用并且前一个过滤器的输出值将被作为后一个过滤器的输入值。

{{ "If life gives you lemons, eat lemons." | upper | abbreviate(13) }}

上面的例子将输出如下值:

IF LIFE GI...

内置的过滤器

abbreviate

使用省略号截取字符串,它接收一个参数表示包括省略符在内输出的最大长度。

{{ "this is a long sentence." | abbreviate(7) }}

结果:

this...

参数

  • length 保留字符串的最大长度(包括省略号)

abs

获取数字的绝对值

{{ -7 | abs }}

结果:

7

capitalize

将字符串的第一个字母转换成大写

{{ "artical title" | capitalize }}

结果:

Atical title

date

用于转换 java.util.Date 对象,它将使用给定的日期表达式创建 java.text.SimpleDateFormat 对象,然后格式化提供的 Date 对象。

{{ user.birthday | date("yyyy-MM-dd") }}

另一种使用方式是作用在字符串上,但必须提供两个参数:第一个为日期表达式,第二个为已存在的日期格式用于将输入字符串转换成 java.util.Date 对象

{{ "July 24, 2001" | date("yyyy-MM-dd", existingFormat="MMMM dd, yyyy") }}

结果:

2001-07-24

参数

  • format
  • existingFormat

default

当且仅当过滤的对象为空时,过滤器将输出默认值。变量为空的情况有:null,空字符串,空集合,空Map

{{ user.phoneNumber | default("No phone number") }}

如果 PebbleEginestrictVariables 设置为 false(默认值) 时,属性表达式为 null 保护的,在如下例子中,如果 foobarbaz 为 null 时,输出将是空字符串,这是 default 过滤器的一个很好的案例。

{{ foo.bar.baz | default("No baz") }}

如果 strictVariables 设置为 true,上面的例子中如果 foobar 为 null 时 将会抛出空指针异常。
参数

  • default 默认值

escape

为了避免 XSS 攻击,它将特殊字符转义。这个过滤器只有在 autoescaping 关闭时才需要使用。

{{ "<div>" | escape }}

结果

&lt;div&gt;

参数

  • strategy 转换策略

first

返回集合的第一个元素,或者字符串的第一个字母。

{{ users | first }}

输出

集合 users 的第一个元素
{{ 'Mitch' | first }}

输出

"M"

join

连接集合的所有元素至一个字符串中,并可以传递一个参数作为连接符。

{# 
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Joe");
names.add("Bob");
#}
{{ names | json(','}}

输出

Alex,Joe,Bob

参数

  • separator 分隔符

last

返回集合的最后一个元素,或者字符串的最后一个字母。

{{ users | first }}

输出

集合 users 的最后一个元素
{{ 'Mitch' | last }}

输出

h

lower

将字符串转换成小写

{{ "THIS IS A LOUD SENTENCE" | lower }}

结果

this is a loud sentence

numberformat

格式化数字,原理是使用 java.text.DecimalFormat 进行数字格式化。

{{ 3.141592653 | numberformat("#.##") }}

结果

3.14

参数

  • format 数字格式

raw

防止表达式的输出结果被自动转义,raw 过滤器必须位于表达式的最后,否则表达式仍然会被转义。

{% set danger = "<div>" %}
{{ danger | upper | raw }}

结果

<DIV>

如果 raw 过滤器不是在表达式的最后,表达式的结果会被转义:

{% set danger = "<div>" %}
{{ danger | raw | upper }}

结果

&lt;DIV&gt;

rsort

颠倒 list 的顺序,list 的条目必须实现 Comparable

{% for user in users | sort %}
    {{ user.name }}
{% endfor %}

slice

返回 list/ array/ String 的部分结果

{{ ['apple', 'peach', 'pear', 'banana'] | slice(1, 3) }}

结果

[peach, pear]
{{ 'Mitchell' | slice(1, 3) }}

结果

it

参数

  • fromIndex 开始位置,从 0 开始,包括此位置
  • toIndex 结束位置,从 0 开始,不包括此位置

sort

对 list 进行排序,list 的条目必须实现 Comparable 接口。

{% for user in users | sort %}
    {{ user.name }}
{% endfor %}

title

将每个单词的第一个字母转换成大写

{{ "article title" | capitalize }}

结果

Article Title

trim

去除字符串的前后空格

{{ "    This text has too much whitespace.    " | trim }}

结果

This text has too much whitespace.

upper

将字符串转换成大写。

{{ "this is a quiet sentence." | upper }}

结果

THIS IS A QUIET SENTENCE.

urlencode

使用 "UTF-8" 字符串将字符串转换成 application/x-www-form-urlencoded 格式。

{{ "The string ü@foo-bar" | urlencode }}

结果

The+string+%C3%BC%40foo-bar

replace

替换内容

{{ "I like %this% and %that%." | replace({'%this%': "foo", '%that%: "bar"'}) }}

结果

I like foo and bar

参数

  • replace_pairs 占位符的键和值 Map

函数

过滤器的用途是修改已存在内容/变量,而函数的用途是生成新的内容。和其他编程语言类似,函数可以通过函数名称加上后方括号中的参数来调用。

{{ max(user.score, hightscore) }}

内置函数

block

用于多次输出一段内容。不要与 block 标签(用于声明语句块)混淆。
下面的例子中将会两次输出 "post" 语句块,一次是声明并输出,第二次是使用 block 函数输出。

{% block "post" %} content {% endblock %}
{{ block("post") }}

输出

content
content

性能警告: TODO
block 函数将会削弱语句块中的 flush 标签的用途。
The block function will impair the use of the flush tag used within the block being rendered. It is typically okay for a block to use the flush tag which will flush the already-rendered content to the user-provided Writer but the block function will internally use it's own StringWriter and therefore flushing inside the block will no longer do any good (nor will it do harm).

i18n TODO

TODO

max

返回参数中的最大值

{% set age = 25 %}
{{ max(age, 80) }}

结果

80

min

返回参数中的最小值

{% set age = 25 %}
{{ min(age, 80) }}

结果

25

parent

用于在语句块中输出父模板的内容,类似于 java 的 super 关键字。
假设有一个 parent.peb 模板:

{% block "content" %} 
    parent contents 
{% endblock %}

然后有一个 child.peb 模板继承 parent.peb

{% extends "parent.peb" %}

{% block "content" %} 
    child contents
    {{ parent() }}
{% endblock %}

结果

child contents
parent contents

  1. 百一测评商店页面 就是 Pebble + Vue 实现的。