轉 FreeMarker一篇通

  

大家看文章標題就應該知道,我想用一篇文章,把大家從對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作爲例子更直觀一些:

xml version='1.0' encoding="gb2312" ?>
<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#代碼。大家先看轉換模板的代碼:

 1<#ftl ns_prefixes={"ns": "urn:DruleForm-Lite.xsd"}> 
 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>
10
11<#macro "ns:type"> <#-- 匹配type節點 -->
12 class ${.node.@name} <#-- 其中.node是保留字,表示當前節點,引用的@name是xslt風格 -->
13 {
14  <#recurse> <#-- 繼續匹配 -->
15 }
16</#macro>
17
18<#macro "ns:field">
19  public ${.node.@type} ${.node.@name};
20</#macro>
21
22<#macro @element> <#-- 所有沒有定義匹配的節點到這裏處理 -->
23
</#macro>
24
25

[代碼2]

我們使用的配置文件設置如下:

sourceRoot: src
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.中:

    class Type1 
    
{
        
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行)修改一下:

<#macro "ns:field">
  public ${.node.@type} ${.node.@name};
  
<#recurse > <#-- 深入處理子節點 -->
#macro>

[代碼5]

結果輸出文件中的內容就變爲如下:

    class Type1 
    
{
        
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行)修改成如下:

<#assign inner_types=pp.newWritableHash()> <#-- 調用fmpp功能函數,生成一個可寫的hash -->
<#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看一下不同的效果 -->
</<
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章