007_表达式

一. 字符串

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. 运行项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值