一. 字符串
1. 在文本中确定字符串值的方法是看双引号, 比如: "some text", 或单引号, 比如: 'some text'。这两种形式是等同的。
2. 如果文本自身包含用于字符引用的引号("或')或反斜杠时, 应该在它们的前面再加一个反斜杠; 这就是转义。转义允许直接在文本中输入任何字符, 也包括换行。
3. 下面的表格是FreeMarker支持的所有转义字符。在字符串使用反斜杠的其他所有情况都是错误的, 运行这样的模板都会失败。
4. 在\x之后的Code是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号: "\xA9 1999-2001", "\x0A9 1999-2001", "\x00A9 1999-2001"。如果紧跟16进制码后一位的字符也能解释成16进制码时, 就必须把4位补全, 否则FreeMarker就会误解你的意图。
5. 请注意, 字符序列${和#{有特殊的含义, 它们被用做插入表达式的数值(典型的应用是变量的值: "Hello ${user}!")。如果想要打印${或#{, 就要使用下面所说的原生字符串, 或者进行转义。就像"foo $\{bar}"中的{。
6. 原生字符串是一种特殊的字符串。在原生字符串中, 反斜杠和${没有特殊含义, 它们被视为普通的字符。为了表明字符串是原生字符串, 在开始的引号或单引号之前放置字母r, 例如:
${r"${foo}"}
${r"C:\foo\bar"}
二. 数字
1. 输入不带引号的数字就可以直接指定一个数字, 必须使用点作为小数的分隔符。可以使用-或+来表明符号(+是多余的)。科学记数法暂不支持使用(1E3就是错误的), 而且也不能在小数点之前不写0(.5也是错误的)。
2. 整数和非整数是不区分的; 只有单一的数字类型。
三. 布尔值
1. 直接写true或者false就表示一个布尔值了, 不需使用引号。
四. 序列
1. 指定一个文字的序列, 使用逗号来分隔其中的每个子变量, 然后把整个列表放到方括号中。例如:
<#list ["foo", "bar", "baz"] as x>
${x}
</#list>
五. 值域
1. 值域也是序列, 但它们由指定包含的数字范围所创建, 而不需指定序列中每一项。比如: 0..<m, 这里假定m变量的值是5, 那么这个序列就包含[0, 1, 2, 3, 4]。值域的主要作用有: 使用<#list...>来迭代一定范围内的数字, 序列切分和字符串切分。
2. 值域表达式的通用形式是(start和end可以是任意的结果为数字表达式):
2.1. start..end: 包含结尾的值域。比如, 1..4就是[1, 2, 3, 4]; 而4..1就是[4, 3, 2, 1]。当心一点, 包含结尾的值域不会是一个空序列, 至少有一个值。
2.2. start..<end或start..!end: 不包含结尾的值域。比如, 1..<4就是[1, 2, 3]; 4..<1就是[4, 3, 2]; 而1..<1表示[]。请注意最后一个示例, 结果可以是空序列。
2.3. start..*length: 限定长度的值域。比如, 10..*4就是[10, 11, 12, 13]; 10..*-4就是[10, 9, 8, 7]; 而10..*0表示[]。当这些值域被用来切分时, 如果切分后的序列或者字符串结尾在指定值域长度之前, 则切分不会有问题。
2.4. start..: 无右边界值域。这和限制长度的值域很像, 只是长度是无限的。比如, 1..就是[1, 2, 3, 4, 5, 6, ... ]直到无穷大。但是处理(比如列表显示)这种值域时要万分小心, 处理所有项时, 会花费很长时间, 直到内存溢出应用程序崩溃。和限定长度的值域一样, 当它们被切分时, 遇到切分后的序列或字符串结尾时, 切分就结束了。
3. 无右边界值域在FreeMarker 2.3.21版本以前只能用于切分, 若用于其它用途, 它就像空序列一样了。要使用新的特性, 使用FreeMarker 2.3.21版本是不够的, 程序员要设置incompatible_improvements至少到2.3.21版本。
4. .., ..<, ..!和..*是运算符, 所以它们中间不能有空格。就像n .. <m这样是错误的, 但是n ..< m这样就可以。
5. 无右边界值域的定义大小是2147483647(如果incompatible_improvements低于2.3.21版本, 那么就是0), 这是由于技术上的限制(32位)。但当列表显示它们的时候, 实际的长度是无穷大。
6. 值域并不存储它们包含的数字, 那么对于0..1和0..100000000来说, 创建速度都是一样的, 并且占用的内存也是一样的。
六. 哈希表
1. 在模板中指定一个哈希表, 就可以遍历用逗号分隔开的"键/值"对, 把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如: {"name": "green mouse", "price": 150}。请注意名和值都是表达式, 但是用来检索的名称就必须是字符串类型, 而值可以是任意类型。
七. 获取字符
1. 在给定索引值时可以获取字符串中的一个字符, 这和序列的子变量是相似的, 比如: str[0]。这个操作执行的结果是一个长度为1的字符串, FTL并没有独立的字符类型。和序列中的子变量一样, 这个索引也必须是数字, 范围是从0到字符串的长度, 否则模板的执行将会发生错误并终止。
2. 由于序列的子变量语法和字符串的getter语法冲突, 那么只能在变量不是序列时使用字符的getter语法。因为FTL支持多类型值, 变量既可以是序列, 同时也可以是字符串, 这种情况下使用序列方式就比较多。为了变通, 可以使用内建函数string, 比如: user?string[0]。
八. 序列连接
1. 序列的连接可以按照字符串那样使用 + 号来进行, 例如:
<#list ["苹果", "香蕉"] + ["葡萄", "梨子"] as fruit>
${fruit}
</#list>
2. 请注意, 不要在很多重复连接时使用序列连接操作, 比如在循环中往序列上追加项目。尽管序列连接的速度很快, 而且速度是和被连接序列的大小相独立的, 但是最终的结果序列的读取却比原先的两个序列慢那么一点。通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。
九. 序列切分
1. 使用seq[range], 这里range是一个值域, 就可以得到序列的一个切分。结果序列会包含原序列(seq)中的项, 而且索引在值域中。例如:
<#assign seqs = ["苹果", "香蕉", "葡萄", "梨子", "菠萝"]>
<#list seqs[1..3] as seq1>
${seq1}
</#list>
2. 此外, 切分后序列中的项会和值域的顺序相同。那么上面的示例中, 如果值域是3..1将会输出: 梨子葡萄香蕉。
3. 值域中的数字必须是序列可使用的合法索引, 否则模板的处理将会终止并报错。
4. 限制长度的值域(start..*length)和无右边界值域(start..)适用于切分后序列的长度。它们会切分可用项中尽可能多的部分。
十. 字符串切分
1. 可以按照切分序列的相同方式来切分字符串, 这就是使用字符来代替序列。不同的是:
1.1. 降序域不允许进行字符串切分。
1.2. 如果变量的值既是字符串又是序列(多类型值), 那么切分将会对序列进行, 而不是字符串。
1.3. 一个遗留的bug: 值域包含结尾时, 结尾小于开始索引并且是是非负的(就像在"abc"[1..0]中), 会返回空字符串而不是错误。现在这个bug已经向后兼容, 但是不应该使用它, 否在就会埋下一个错误。
十一. 哈希表连接
1. 像连接字符串那样, 也可以使用+号的方式来连接哈希表。如果两个哈希表含有键相同的项, 那么在+号右侧的哈希表中的项优先。
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
Joe is ${ages.Joe}
Fred is ${ages.Fred}
Julia is ${ages.Julia}
2. 请注意, 很多项连接时不要使用哈希表连接, 比如: 在循环时往哈希表中添加新项。这和序列连接的情况是一致的。
十二. 算数运算
1. 算数运算包含基本的四则运算和求模运算, 运算符有:
加法: +
减法: -
乘法: *
除法: /
求模(求余): %
2. 要保证两个操作数都是结果为数字的表达式。下面的这个例子在运行时, FreeMarker就会发生错误, 因为是字符串"5"而不是数字5:
${3 * "5"}
3. 但这种情况也有一个例外, 就是+号, 它是用来连接字符串的。如果+号的一端是字符串, +号的另外一端是数字, 那么数字就会自动转换为字符串类型, 之后使用+号作为字符串连接操作符。
4. 通常来说, FreeMarker不会自动将字符串转换为数字, 反之会自动进行。
十三. 比较运算
1. 测试两个值相等使用==。
2. 测试两个值不等使用!=。
3. ==或!=两边的表达式的结果都必须是标量, 而且两个标量都必须是相同类型。
4. 对数字和日期类型的比较, 也可以使用<, <=, >=和>。
5. 使用>=和>的时候有一点小问题。FreeMarker解释>的时候可以把它当作FTL标签的结束符。为了避免这种问题, 可以使用lt代替<, lte代替<=, gt代替>还有gte代替>=。
十四. 逻辑操作
1. 常用的逻辑操作符
逻辑或: ||
逻辑与: &&
逻辑非: !
2. 逻辑操作符仅仅在布尔值之间有效, 若用在其他类型将会产生错误导致模板执行中止。
<#if hot < 28 && color == "blue">
今天温度小于28度, 并且天是蓝色的。
</#if>
<#if !hot>
今天不热。
</#if>
十五. 赋值操作符
1. <#assign x += y>是<#assign x = x + y>的简写。
2. <#assign x -= y>是<#assign x = x - y>的简写。
3. <#assign x *= y>是<#assign x = x * y>的简写。
4. <#assign x /= y>是<#assign x = x / y>的简写。
5. <#assign x %= y>是<#assign x = x % y>的简写。
6. <#assign x++>是<#assign x += 1>的简写。只有后加加, 没有前加加。
7. <#assign x-->是<#assign x -= 1>的简写。只有后减减, 没有前减减。
十六. 默认值操作符
1. 使用形式: unsafe_expr!或default_expr或unsafe_expr!或(unsafe_expr)!default_expr或(unsafe_expr)!操作符允, 许你为可能不存在的变量指定一个默认值。
2. 默认值可以是任何类型的表达式, 也可以不必是字符串。 也可以这么写:hits!0 或 colors!["red", "green", "blue"]。默认值表达式的复杂程度没有严格限制, 还可以这么来写: cargo.weight!(item.weight * itemCount + 10)。
3. 如果在!后面有复合表达式, 如: 1 + x, 通常使用括号, 如: ${(x!1) + y}, 这样就根据你的意图来确定优先级。由于FreeMarker2.3.x版本的源码中的小失误所以必须这么来做。!(作为默认值操作)右侧的优先级非常低。这就意味着${x!1 + y}会被FreeMarker误解为${x!(1 + y)}, 而真实的意义是${(x!1) + y}。这个源码错误在FreeMarker 2.4中会得到修正。在编程中注意这个错误, 要么就使用FreeMarker 2.4!
4. 如果默认值被省略了, 那么结果将会是空串, 空序列或空哈希表(这是FreeMarker允许多类型值的体现)。请注意, 如果想让默认值为0或false, 则不能省略它。
5. 因为语法的含糊<#assign a=x! b=y />将会解释为<#assign a=x!(b=y) />, 那就是说b=y将会被视为是比较运算, 然后结果作为x的默认值, 而不是想要的参数b。 为了避免这种情况, 如下编写代码即可: <#assign a=(x!) b=y />。
6. 用于非顶层变量时, 默认值操作符可以有两种使用方式:
6.1. product.color!"red"如果是这样的写法, 那么在product中, 当color不存在时(返回"red"), 将会被处理, 但是如果连product都不存在时将不会处理。也就是说这样写时变量 product必须存在, 否则模板就会报错。
6.2. (product.color)!"red"这时, 如果当product.color不存在时也会被处理, 那就是说, 如果product不存在或者product存在而color不存在, 都能显示默认值"red"而不会报错。 本例和上例写法的重要区别在于用括号时, 就允许其中表达式的任意部分可以未定义。而没有括号时, 仅允许表达式的最后部分可以不被定义。
7. 当然, 默认值操作也可以作用于序列子变量${seq[0]!'-'}。如果序列索引是负数(比如: seq[-1]!'-')也会发生错误, 不能使用该运算符或者其它运算符去压制它。
十七. 不存在值检测操作符
1. 使用形式: unsafe_expr??或(unsafe_expr)??操作符告诉我们一个值是否存在。基于这种情况, 结果是true或false。
2. 访问非顶层变量的使用规则和默认值操作符也是一样的, 也就是说, 可以写product.color??和(product.color)??。
十八. 例子
1. 新建一个名为FMExpression的动态Web项目, 同时添加相关jar包。
2. 新建FMFactory.java
package com.fm.util;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
public class FMFactory {
private final static FMFactory instance = new FMFactory();
private FMFactory() {}
public static FMFactory getInstance() {
return instance;
}
private Map<String, Configuration> map = new ConcurrentHashMap<String, Configuration>();
// 创建单个Configuration实例
public synchronized Configuration getCfg(Object servletContext, String path) {
if(null != map.get(path)) {
return map.get(path);
}
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setServletContextForTemplateLoading(servletContext, path);
cfg.setDefaultEncoding("utf-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
map.put(path, cfg);
return cfg;
}
}
3. 新建Expression.java
package com.fm.action;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fm.util.FMFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class Expression extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Configuration cfg = FMFactory.getInstance().getCfg(req.getServletContext(), "/WEB-INF/templates");
Map<String, Object> root = new HashMap<String, Object>();
root.put("createTime", new Date(888888888));
root.put("byeTime", new Date(999999999999L));
// 获取模板
Template temp = cfg.getTemplate("expression.html");
Writer out = new OutputStreamWriter(resp.getOutputStream());
try {
// 合并模板和数据模型
temp.process(root, out);
} catch (TemplateException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
4. 修改web.xml
5. 在/WEB-INF/templates下创建expression.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>表达式</title>
</head>
<body>
<h2>字符串</h2>
转义字符串:
${"\""}${"\'"}${"\{"}${"\\"}${"\l"}${"\g"}${"\a"}${"\x00A9 1999-2001"}"foo $\{bar}"<br />
原生字符串:
${r"${foo}"}
${r"C:\foo\bar"}
<h2>值域</h2>
带边界的值域:
<#list 0..0 as vari>
${vari}
</#list><br />
不带边界的值域:
<#list 0..!0 as vari>
${vari}
</#list><br />
限定长度的值域:
<#list 0..*10 as vari>
${vari}
</#list>
<h2>哈希表</h2>
<#-- assign指令定义变量 -->
<#assign hash = {"name": "苹果", "price": 15} />
${hash.name} ${hash.price}
<h2>获取字符</h2>
${"我们都是中国人"[0]}<br />
${"我们都是中国人"?string[1]}
<h2>序列连接</h2>
<#list ["苹果", "香蕉"] + ["葡萄", "梨子"] as fruit>
${fruit}
</#list>
<h2>序列切分</h2>
<#assign seqs = ["苹果", "香蕉", "葡萄", "梨子", "菠萝"]>
<#list seqs[1..3] as seq1>
${seq1}
</#list><br />
<#list seqs[3..1] as seq2>
${seq2}
</#list><br />
<#list seqs[1..*10] as seq3>
${seq3}
</#list><br />
<#list seqs[1..] as seq4>
${seq4}
</#list>
<h2>字符串切分</h2>
<#assign str = "我爱吃苹果、普通、梨子..." />
${str[2..3]}<br />
${str[2..!4]}<br />
${str[2..*3]}<br />
${str[2..]}
<h2>哈希表连接</h2>
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
Joe is ${ages.Joe}<br />
Fred is ${ages.Fred}<br />
Julia is ${ages.Julia}
<h2>比较运算</h2>
创建时间: ${createTime}<br />
购买时间: ${byeTime}<br />
<#if createTime gt byeTime>
创建时间大于购买时间。
<#else>
创建时间小于购买时间。
</#if>
<h2>逻辑操作</h2>
<#assign hot = 25 color = "blue" />
<#if hot lt 28 && color == "blue">
今天温度小于28度, 并且天是蓝色的。
</#if>
<#if !(hot gte 28)>
今天不热。
</#if>
<h2>赋值操作符</h2>
<#assign x = 100 y = 10 />
${x} + ${y} = ${(x + y)?c}<br />
${x} - ${y} = ${(x - y)?c}<br />
${x} * ${y} = ${(x * y)?c}<br />
${x} / ${y} = ${(x / y)?c}<br />
${x} % ${y} = ${(x % y)?c}<br />
${x} += ${y} = <#assign x += y />${x}<br />
${x} -= ${y} = <#assign x -= y />${x}<br />
${x} *= ${y} = <#assign x *= y />${x}<br />
${x} /= ${y} = <#assign x /= y />${x}<br />
${x} %= ${y} = <#assign x %= y />${x}<br />
${x}++ = <#assign x++ />${x}<br />
${x}-- = <#assign x-- />${x}<br />
<h2>默认值操作符</h2>
[${mouse!"No mouse."}]<br />
[${mouse!}]<br />
[${(mouse)!"No mouse."}]<br />
[${(mouse)!}]<br />
[${(product.color)!"red"}]<br />
[<#assign a=z! x=y />${a?c}]<br />
[<#assign a=(z!) b=y />${a}]
<h2>不存在值检测操作符</h2>
<#if price??>
price found
<#else>
No price found
</#if><br />
<#assign price = "Jerry">
<#if price??>
price found
<#else>
No price found
</#if><br />
<#if (banana.price)??>
banana.price found
<#else>
No banana.price found
</#if><br />
</body>
</html>
6. 运行项目