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;@Overrideprotected 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();}}@Overrideprotected 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. 運行項目
總結
- 上一篇: 006_基本指令
- 下一篇: 009_字符串内建函数