免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1052 | 回复: 0
打印 上一主题 下一主题

FreeMaker一篇通 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-06-05 18:42 |只看该作者 |倒序浏览


前言
Freemaker是一个强大的模板引擎,相比velocity而言,其强大的过程调用、递归和闭包回调功能让freemaker可以完成几乎所有我们所想的功能。从个人看法而言,freemaker完全有能力作为MDA的代码辅助生成工具。
本文试图越过传统的概念性介绍,通过一组例子直接把读者带入到Freemaker应用的较高层阶。
正文
大家看文章标题就应该知道,我想用一篇文章,把大家从对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提供的控制包括如下:
条件判断
遍历hash表或者collection(freemaker称作sequence)的成员
宏,无返回参数
函数,有返回参数
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
nest_body /@macro> 调用宏,并处理宏的嵌套
定义变量并初始化
在 macro 或者 function 中定义局部变量并初始化
定义全局变量并初始化
${var} 输出并替换为表达式的值
调用macro匹配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的 行(第5行)修改成如下:

#assign inner_types=pp.newWritableHash()> >

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

#macro "ns:field">

  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]
运行得到输出文件类似这样:


    class Type1


   

{

        public Float Field11;

        public String Field12;

        public Integer Field13;

        public Type2 Field14;

        public Float Field15;

    }



    class Type3


   

{

        public Type1 Field31;

    }


    class Type2


   

{

        public String Field21;

        public Integer Field22;

    }

[代码9]
大家比较一下,看看我们修改的地方出现了哪些效果?然后记得大家要做另外2件事情,
1。把第一行修改成为 ,然后把所有的  修改成,把所有的.node["ns:type"]修改成 .node.type,看看能不能运行?是不是觉得简单方便些了?记住,第一行的那个D表示是default namespace的意思哦
2。在第二行插入,在最后一行添加。再运行一下看看结果有什么不同?
一个例子下来,大家基本对freemaker有了一些感觉了,为了纠正大家认为freemaker就是一个xml处理工具的误解,我们再来做一个简单的实验。这次我们要做的是一个正常的编程题目,做一个100以内的Fibonacci数列的程序。程序如下:

迭代次数:

#list 1 .. 10 as n>

${n} = ${fibo(n)}

#list>


#compress>

#function fibo n>

#if n lte 1>

  #return 1>

#elseif n = 2>

  >

#else>

  #return fibo(n-1) + fibo(n-2)>

#if>

#function>

#compress>


[代码10]
这个例子里边有一些问题需要注意,大家看我的 #if n lte 1 这一行,为什么我这么写?因为常规的大于小于号和xml的节点有冲突,为了避免问题,所以用 gt(>) gte(>=) lt(

#assign str = "hello!$world!">

#assign mid = (str?length + 1)/2-1 >

#list mid .. 0 as cnt>

${str[(mid - cnt) .. (mid + cnt)]?left_pad(mid*2)}

#list>

[代码11]
最后,说一下非常有用的macro的nested指令,没有它,也许freemaker会失去大部分的魅力。我个人认为这也是freemaker全面超越velocity的地方。大家先看一下代码:

#assign msg = "hello">

@macro0 ; index >

${msg} ${index}

@macro0>


#macro macro0>

#list 0 .. 10 as number>

  #nested number>

#list>

#macro>


[代码12]
这段代码的作用就是一个闭包(closure)。我们用java的匿名类实现相同的功能就是这样:

interface ICallback



{

public void call(int index);

}


void Main()



{

String msg = "hello";

macro0(

  new ICallback()


  

{

   public void call(int index)


   

{

    System.out.println(msg + index.toString());

   }

  }

);

}


void macro0(ICallback callback)



{

for(int i = 0; i  10; ++i)




{

  callback.call(i);

}

}





本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/19228/showart_315276.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP