http://publishblog.blogchina.com/blog/tb.b?diaryID=4137438
大家看文章標題就應該知道,我想用一篇文章,把大家從對freemaker的陌生直接帶入到比較深入的境界,所以不想說一些基礎性的東西,如果大家不習慣我的表達方法,大可通過google去找習慣於自己閱讀方式的相關文章。
我 用過velocity,最近才用freemaker,才知道我以前的選擇是錯了,因為velocity不支持過程的調用,所以我為velocity增加了 很多的東西,寫了很多代碼,而且腳本也累贅得要命。freemaker首先吸引我的是它強大的過程調用和遞歸處理能力,其次則是xml風格的語法結構有着 明顯的邊界,不象velocity要注意段落之間要留空格。所以我建議大家直接使用Freemaker,雖然freemaker沒有.net版本,我想不 嵌入程序中使用的話,freemaker是絕對的首選。(題外話,誰有興趣移植一個NFreeMaker?)
在使用之前我們先要設置運行環境,在使用Freemaker的時候,我們需要下載相關的程序:
freemaker: http://freemarker.sourceforge.net/
fmpp: http://fmpp.sourceforge.net/
其中fmpp是一個freemaker的輔助工具,有了它,我們可以實現更多的功能。以下例子必須fmpp輔助。
這裡我們首先提出問題。大家看如下的一個xml文件,雖然freemaker的能力不僅在於處理xml文件,但是用xml作為例子更直觀一些:
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:DruleForm-Lite.xsd">
<type name="Type1" >
<labels>
<label lang="zh-CN" value="投保單"/>
labels>
<field name="Field11" type="Float" lbound="1" ubound="1" >
<labels>
<label lang="zh-CN" value="投保單ID"/>
labels>
field>
<field name="Field12" type="String" lbound="1" ubound="*"/>
<field name="Field13" type="Integer" lbound="1" ubound="*"/>
<field name="Field14" type="Type2" lbound="1" ubound="*">
<type name="Type2">
<field name="Field21" type="String" lbound="1" ubound="*"/>
<field name="Field22" type="Integer" lbound="1" ubound="*"/>
type>
field>
<field name="Field15" type="InsuranceProduct" lbound="1" ubound="*"/>
<type>
<type name="Type3">
<field name="Field31" type="Type1" lbound="1" ubound="*" />
type>
types>
[代碼1]
我們的任務是把這個文件轉化為相應的C#代碼。大家先看轉換模板的代碼:
2<#-- 定義xml namespace,以便在以下代碼中使用,注意,ftl指令必須使用單獨的行 -->
3<@pp.setOutputEncoding encoding="gb2312" /> <#-- 使用fmpp提供的函數來設置輸出編碼 -->
4 5<#recurse doc> <#-- 根入口,代碼1部分的xml存放在變量doc中,doc變量的填充由fmpp根據config.fmpp中的配置進行 -->
6 7<#macro "ns:types"> <#-- xslt風格的匹配處理入口 -->
8<#recurse> <#-- 直接進行types節點內的匹配 -->
9#macro>
1011<#macro "ns:type"> <#-- 匹配type節點 -->
12 class ${.node.@name} <#-- 其中.node是保留字,表示當前節點,引用的@name是xslt風格 -->
13 {
14 <#recurse> <#-- 繼續匹配 -->
15 }
16#macro>
1718<#macro "ns:field">
19 public ${.node.@type} ${.node.@name};
20#macro>
2122<#macro @element> <#-- 所有沒有定義匹配的節點到這裡處理 -->
23#macro>
2425
[代碼2]
我們使用的配置文件設置如下:
outputRoot: out
logFile: log.fmpp
modes: [
copy(common/**/*.*, resource/*.*)
execute(*.ftl)
ignore(templates/*.*, .project, **/*.xml, xml/*.*, *.js)
]
removeExtensions: ftl
sourceEncoding: gb2312
data: {
doc: xml(freemaker.xml)
}
[代碼3]
然後我們在dos模式下運行指令:
E:/work/blogs/freemaker>f:/download/freemaker/fmpp/bin/fmpp
最後的輸出結果是這樣的,存放在文件out/freemaker.中:
{
public Float Field11;
public String Field12;
public Integer Field13;
public Type2 Field14;
public Float Field15;
}
class Type3
{
public Type1 Field31;
}
[代碼4]
先來解釋一下freemaker的基本語法了,
<# ... > 中存放所有freemaker的內容,之外的內容全部原樣輸出。
<@ ... /> 是函數調用
兩個定界符內的內容中,第一個符號表示指令或者函數名,其後的跟隨參數。freemaker提供的控制包括如下:
<#if condition><#elseif condition><#else> 條件判斷
<#list hash_or_seq as var> 遍歷hash表或者collection(freemaker稱作sequence)的成員
<#macro name param1 param2 ... ><#nested param> 宏,無返回參數
<#function name param1 param2><#return val>函數,有返回參數
var?member_function(...) 用函數對var進行轉換,freemaker稱為build-ins。實際內部實現類似member_function(var, ...)
stringA[M .. N] 取子字符串,類似substring(stringA, M, N)
{key:value, key2:value2 ...} 直接定義一個hash表
[item0, item1, item2 ...] 直接定義一個序列
hash0[key0] 存取hash表中key對應的元素
seq0[5] 存取序列指定下標的元素
<@function1 param0 param1 ... /> 調用函數function1
<@macro0 param0 param1 ; nest_param0 nest_param1 ...> nest_body </@macro> 調用宏,並處理宏的嵌套
<#assign var = value > 定義變量並初始化
<#local var = value> 在 macro 或者 function 中定義局部變量並初始化
<#global var = value > 定義全局變量並初始化
${var} 輸出並替換為表達式的值
<#visit xmlnode> 調用macro匹配xmlnode本身及其子節點
<#recurse xmlnode> 調用macro匹配xmlnode的子節點
[表1]
大家仔細對比xml文件,發現少了什麼嗎?對了,少了一個Type2定義,我們把代碼2中的ns:type匹配(第11行)修改一下:
public ${.node.@type} ${.node.@name};
<#recurse > <#-- 深入處理子節點 -->
#macro>
[代碼5]
結果輸出文件中的內容就變為如下:
{
public Float Field11;
public String Field12;
public Integer Field13;
public Type2 Field14;
class Type2
{
public String Field21;
public Integer Field22;
}
public Float Field15;
}
class Type3
{
public Type1 Field31;
}
[代碼6]
如果各位有意向把Type2提到跟Type1和Type3同一級別的位置,那麼我們要繼續修改代碼了。把代碼2的 <#recurse doc>行(第5行)修改成如下:
<#recurse doc> <#-- 根入口,代碼1部分的xml存放在變量doc中,doc變量的填充由fmpp根據config.fmpp中的配置進行 -->
<#if inner_types?size gt 0 > <#-- 如果存放有類型 -->
<#list inner_types?values as node> <#-- 遍歷哈西表的值 -->
<#visit node> <#-- 激活相應的macro處理,類似於xslt的apply-template。大家把visit改成recurse看一下不同的效果 -->
#list>
#if>
[代碼7]
同時把macro ns:field(第18行)修改成如下:
public ${.node.@type} ${.node.@name};
<#if .node["ns:type"]?has_content > <#-- 如果當前節點下存在type節點 -->
<#local t = .node["ns:type"] >
<@pp.set hash=inner_types key="${t.@name}" value=t /> <#-- 哈西表中增加內容,key為嵌套類型的name屬性,value為該類型節點 -->
#if>
#macro>
[代碼8]
運行得到輸出文件類似這樣:
{
public Float Field11;
public String Field12;
public Integer Field13;
public Type2 Field14;
public Float Field15;
}
class Type3