freemarker模闆解析過程
例如:一個freemarker表達式<body> ${hello} </body>,會被解析成三個部分,分别是
<body>
${hello}
</body>
前面和後面的body标簽,在freemarker中被定義為TextBlock,中間的變量定義為DollarVariable。那麼目前的結構也就是RootExpression = TextBlock DollarVariable TextBlock。解釋器一進來将會對RootExpression進行解析,RootExpression将會依次調用TextBlock DollarVariable TextBlock進行解析。不同類型将會做不同操作,根據傳進來的Context參數進行相應指派并輸出等。
當Template啟動解釋時,由Environment進入調用根元素的通路動作,根元素會依次通路所包含的TemplateElement,直到所有葉子節點通路完成,這些通路動作是通過調用Environment的visit方法控制,Environment做些相關必要操作,再根據通路的節點類型調用相應節點的通路操作。當通路到包含需要解釋器的元素節點時,則會啟動解釋器做解釋操作,根據Expression類型,調用getStringValue,并傳入參數Environment,相應類型的表達式根據Environment解釋得到輸入字元串的值,傳回并寫到響應流,即解釋完成。
freemarker内建函數介紹
Sequence的内置函數
1.sequence?first 傳回sequence的第一個值。
2.sequence?last 傳回sequence的最後一個值。
3.sequence?reverse 将sequence的現有順序反轉,即倒序排序
4.sequence?size 傳回sequence的大小
5.sequence?sort 将sequence中的對象轉化為字元串後順序排序
6.sequence?sort_by(value) 按sequence中對象的屬性value進行排序
注意:Sequence不能為null
Hash的内置函數
1.hash?keys 傳回hash裡的所有key,傳回結果為sequence
2.hash?values 傳回hash裡的所有value,傳回結果為sequence
操作字元串内置函數
1.substring(start,end)從一個字元串中截取子串
start:截取子串開始的索引,start必須大于等于0,小于等于end
end: 截取子串的長度,end必須大于等于0,小于等于字元串長度,如果省略該參數,預設為字元串長度。
2.cap_first 将字元串中的第一個單詞的首字母變為大寫。
3.uncap_first将字元串中的第一個單詞的首字母變為小寫。
4.capitalize将字元串中的所有單詞的首字母變為大寫
5.date,time,datetime将字元串轉換為日期
注意:如果指定的字元串格式不正确将引發錯誤
6.ends_with 判斷某個字元串是否由某個子串結尾,傳回布爾值
注意:布爾值必須轉換為字元串才能輸出
7.html 用于将字元串中的<、>、&和"替換為對應得<>&quot:&amp
8.index_of(substring,start)在字元串中查找某個子串,傳回找到子串的第一個字元的索引,如果沒有找到子串,則傳回-1。
Start參數用于指定從字元串的那個索引處開始搜尋,start為數字值。
如果start大于字元串長度,則start取值等于字元串長度,如果start小于0,則start取值為0。
9.length傳回字元串的長度
10.lower_case将字元串轉為小寫
11.upper_case将字元串轉為大寫
12.contains 判斷字元中是否包含某個子串。傳回布爾值
13.number将字元串轉換為數字
14.replace用于将字元串中的一部分從左到右替換為另外的字元串。
15.split使用指定的分隔符将一個字元串拆分為一組字元串
16.trim 删除字元串首尾空格
操作數字内置函數
1.c 用于将數字轉換為字元串
2.string用于将數字轉換為字元串
Freemarker中預訂義了三種數字格式:number,currency(貨币)和percent(百分比)其中number為預設的數字格式轉換
操作布爾值内置函數
string用于将布爾值轉換為字元串輸出
true轉為"true",false轉換為"false"
foo?string("yes","no")如果布爾值是true,那麼傳回"yes",否則傳回no
freemarker日志實作過程分析
freemarker有自己的log類,這是一個抽象類,具體的日志列印委托給classpath裡面合适的日志jar包來執行,尋找合适日志jar的查找順序是:Apache Log4J, Apache Avalon LogKit, JDK log。如果一個合适的日志實作類都沒有找到,日志功能将被抑制,并會使用System.err列印出錯誤提示資訊。
如果我們想自己指定使用的日志類型,那麼可以通過:
Loger.selectLoggerLibrary(int library);
注意:一定要在freemarker初始化階段進行設定,在調用任何freemarker api之前進行設定,否則freemarker将會與預設的日志實作進行綁定,進而自己指定的日志修改将不會起到作用。
Freemarker中大于号>的使用
在Freemarker中,比較資料的大小時候,要注意大于号(>)的使用。如果不注意,程式就會發生異常資訊,如下面的例子:
1
2
3
4
<#assign x = 4>
<#if x>5 >
x >5
</#if>
以上的方式進行比較,就會發生異常,原因是Freemarker内部的解析處理原因,x>5中的大于号将會跟<#if中的小于号進行配對,導緻解析出現問題。針對這種情況,有兩種方式解決:
方法一:加上括号。
<#if (x>5) >
x > 5
方法二:使用gt符号。
<#if x gt 5 >
使用>=和>的時候有一點小問題。FreeMarker解釋>的時候可以把它當作FTL标簽的結束符。為了避免這種問題,不得不将表達式放到括号内:<#if (x > y) >,另外,可以使用lt代替<,lte代替<=,gt代替>,gte代替>=。由于曆史遺留的原因,FTL也支援\lt,\lte,\gt和\gte,使用他們和使用不帶反斜杠的效果一樣。
序列的重點知識小結
<#if (winnersList![])?size gt 0>
<table class="winner_table" border="0" cellspacing="0" cellpadding="0">
<tr>
<th class="bdr_gray">中獎賬号</th>
<th>猜測內插補點</th>
</tr>
<#list winnersList as list>
<td class="bdr_gray">${list.accountId!""}</td>
<td>${list.deviation!""}</td>
</#list>
</table>
說明:在上面例子中,winnersList預設為[],它的内建函數為size
可以将兩個序列連接配接成一個新的序列,連接配接序列的運算符是'+',見下面的例子:
<#list ["一","二","三"] + ["四","五","六"] as x>
${x}
</#list>
輸出結果如下:
一二三四五六
舉個例子看序列的切分應用場景:有的時候我們在頁面中不需要顯示那麼長的字元串,比如新聞标題,這樣用下面的例子就可以自定義顯示的長度
<#if title.content?length lt 8>
<a href>${title.content?default("")}</a>
<#else>
<a href title="${title.content}">${title.content[0..3]?default("")}</a>
上面例子的作用是:如果這個字元串的長度小于8,那麼就正常顯示,反之則取4位。
序列的切分要注意下面兩點:
從FreeMarker 2.3.3版本以後lastindex才能省略。
如果試圖通路一個序列首變量之前的項或末變量之後的項将會引起錯誤,模闆的執行也會中斷。
序列中的項是表達式,那麼也可以這樣做:[2 + 2, [1, 2, 3, 4], "what"],其中第一個子變量是數字4,第二個子變量是一個序列,第三個子變量是字元串"what"。
第一種定義序列的方式:
<#assign nums=[1,2,3,4,5,77,8,99]/>
使用list指令将序列輸出,
<#list nums as num>
${num}
第二種定義序列的方式
定義了一個連續的序列,
<#assign nums=12..99/>
這種方式定義的序列的内容是12到99
說明:
從上面的例子可以看出,序列也可以用start..end定義存儲數字範圍的序列,這裡的start和end是處理數字值表達式,比如2..5和[2, 3, 4, 5]是相同的,但是使用前者會更有效率(記憶體占用少而且速度快)。可以看出前者也沒有使用方括号,這樣也可以用來定義遞減的數字範圍,比如5..2。(此外,還可以省略end,隻需5..即可,但這樣序列預設包含5,6,7,8等遞增量直到無窮大)。
如果要判斷序列中是否包含某個指定的元素,可以使用序列的内建函數seq_contains。
注:seq_contains這個内建函數從FreeMarker 2.3.1 版本開始可用。而在2.3 版本中不存在。
<#--聲明一個序列,包含若幹個元素-->
<#assign x = ["red", 16, "blue", "cyan"]>
<#--使用seq_contains判斷序列中的元素是否存在-->
"blue": ${x?seq_contains("blue")?string("yes", "no")}
"yellow": ${x?seq_contains("yellow")?string("yes", "no")}
16: ${x?seq_contains(16)?string("yes", "no")}
"16": ${x?seq_contains("16")?string("yes", "no")}
輸出結果:
"blue": yes
"yellow": no
16: yes
"16": no
附:seq_字首在這個内建函數中是需要的,用來和contains 區分開。contains函數用來在字元串中查找子串(因為變量可以同時當作字元串和序列)。
StringTemplateLoader的用法
作為一個模闆架構,freemarker的功能還是很強大的。在模闆處理方面,freemarker有多種形式,最常見的方式是将模闆檔案放在一個統一的檔案夾下面,如下形式:
Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File("templates"));
如果我想把模闆存放到資料庫中,可以實作嗎?答案是肯定的。在這裡可以使用StringTemplateLoader來加載模闆内容。主要的代碼實作如下所示:
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
String templateContent="hello ${name}!";
stringLoader.putTemplate("myTemplate",templateContent);
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("myTemplate","utf-8");
freemarker報錯的處理方案
freemarker檔案如果出錯,網站的前台頁面會報出很明顯的錯誤-焦黃的背景,血紅的文字,很不利于使用者體驗的。如何修改這個問題呢?
首先需要在struts.xml配置檔案裡添加下面一行代碼:
<constant name="struts.freemarker.manager.classname" value="net.swiftlet.freemarker.MyFreemarkerManager" />
接着建立MyFreemarkerManager類,如下所示:
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyFreemarkerManager extends org.apache.struts2.views.freemarker.FreemarkerManager
{
private static final Logger LOG = LoggerFactory.getLogger(MyFreemarkerManager.class);
public void init(ServletContext servletContext) throws TemplateException
{
config = createConfiguration(servletContext);
config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
contentType = DEFAULT_CONTENT_TYPE;
wrapper = createObjectWrapper(servletContext);
if (LOG.isDebugEnabled())
{
LOG.debug("Using object wrapper of class " + wrapper.getClass().getName());
}
config.setObjectWrapper(wrapper);
templatePath = servletContext.getInitParameter(INITPARAM_TEMPLATE_PATH);
if (templatePath == null)
templatePath = servletContext.getInitParameter("templatePath");
configureTemplateLoader(createTemplateLoader(servletContext, templatePath));
loadSettings(servletContext);
}
}