7、XML处理
尽管FreeMarker最初被设计用作Web页面的模板引擎,对于2.3版本来说,它的另外一个应用领域目标是:转换XML到任意的文本输出(比如HTML)。因此,在很多情况下,FreeMarker也是一个可选的XSLT。
从技术上来说,在转换XML文档上没有什么特别之处。它和你使用FreeMarker做其他事情都是一样的:你将XML文档丢到数据模型中(和其他可能的变量),然后你将FTL模板和数据模型合并来生成输出文本。对于更好的XML处理的额外特性是节点FTL变量类型(在通用的树形结构中象征一个节点,不仅仅是对XML有用)和用内建函数,指令处理它们,你使用的XML包装器会暴露XML文档,并将作为模板的FTL变量。 使用FreeMarker和XSLT有什么不同?FTL语言有常规的命令式/过程式的逻辑。另一方面,XSLT是声明式的语言,由很聪明的人设计出来,所以它并不能轻易吸收它的逻辑,也不会在很多情况下使用。而且它的语法也非常繁琐。然而,当你处理XML文档时,XSLT的“应用模板”方法可以非常方便,因此FreeMarker支持称作“访问者模式”的相似事情。所以在很多应用程序中,写FTL的样式表要比写XSLT的样式表容易很多。另外一个根本的不同是FTL转换节点树到文本,而XSLT转换一课树到另一棵树。所以你不能经常在使用XSLT的地方使用FreeMarker。 1、XML文档 节点树 如下示例的XML文档 <book> <title>Test Book</title> <chapter> <title>Ch1</title> <para>p1.1</para> <para>p1.2</para> <para>p1.3</para> </chapter> <chapter> <title>Ch2</title> <para>p2.1</para> <para>p2.2</para> </chapter> </book>W3C的DOM定义XML文档模型为节点树。上面XML的节点树可以被视为: 要注意,烦扰的“n”是行的中断(这里用n指示,在FTL字符串中使用转义序列)和标记直接的缩进空格。 注意和DOM相关的术语: FTL中的DOM节点和node variable节点变量对应。这是变量类型,和字符串,数字,哈希表等类型相似。节点变量类型使得FreeMarker来获取一个节点的父节点和子节点成为可能。这是技术上需要允许模板开发人员在节点间操作,也就是,使用节点内建函数或者visit和recurse指令; 2、将XML放到数据模型中 创建一个简单的程序来运行下面的示例是非常容易的。仅仅用下面这个例子来替换程序开发指南中快速入门示例中的“Create a data-model”部分: /* Create a data-model */ Map root = new HashMap(); root.put("doc",freemarker.ext.dom.NodeModel.parse(new File("the/path/of/the.xml")));然后你可以在基本的输出(通常是终端屏幕)中得到一个程序可以输出XML转换的结果。 注意: ●parse方法默认移除注释和处理指令节点。 ●NodeModel也允许你直接包装org.w3c.dom.Node。首先你也许想用静态的实用方法清空DOM树,比如NodeModel.simplify或你自定义的清空规则。 3、必要的XML处理 假设程序员在数据模型中放置了一个XML文档,就是名为doc的变量。这个变量和DOM树的根节点“document”对应。真实的变量doc之后结构是非常复杂的,大约类似DOM树。 3.1、通过名称来访问元素 这个FTL打印book的title:<h1>${doc.book.title}</h1> 输出是:<h1>Test Book</h1> 正如你所看到的,doc和book都可以当作哈希表来使用。你可以按照子变量的形式来获得它们的子节点。基本上,你用描述路径的方法来访问在DOM树中的目标(元素title)。你也许注意到了上面有一些是假象:使用${doc.book.title},就好像我们指示FreeMarker打印title元素本身,但是我们应该打印它的子元素文本(看看DOM树)。那也可以办到,因为元素不仅仅是哈希表变量,也是字符串变量。元素节点的标量是从它的文本子节点级联中获取的字符串结果。然而,如果元素有子元素,尝试使用一个元素作为标量会引起错误。比如${doc.book}将会以错误而终止。 <h2>${doc.book.chapter[0].title}</h2> 这里,book有两个chapter子元素,doc.book.chapter是存储两个元素节点的序列。因此,我们可以概括上面的FTL,所以它以任意chapter的数量起作用: <#list doc.book.chapter as ch> 但是如果只有一个chapter会怎么样呢?实际上,当你访问一个作为哈希表子变量的元素时,通常也可以是序列(不仅仅是哈希表和字符串),但如果序列只包含一个项,那么变量也作为项目自身。所以,回到第一个示例中,它也会打印book的title: <h1>${doc.book[0].title[0]}</h1> 但是你知道那里就只有一个book元素,而且book也就只有一个title,所以你可以忽略那些[0]。如果book恰好有一个chapter(否则它就是模糊的:它怎么知道你想要的是哪个chapter的title?所以它就会以错误而停止),${doc.book.chapter.title}也可以正常进行。但是因为一个book可以有很多chapter,你不能使用这种形式。如果元素book没有子元素chapter,那么doc.book.chapter将是一个长度为零的序列,所以用FTL<#list ...>也可以进行。 现在我们完成了打印每个chapter所有的para示例: <h1>${doc.book.title}</h1> <#list doc.book.chapter as ch> <h2>${ch.title}</h2> <#list ch.para as p> <p>${p} </#list> </#list>这将打印出: <h1>Test</h1> 3.2、访问属性 <!-- THIS XML IS USED FOR THE "Accessing attributes" CHAPTER ONLY! --> <!-- Outside this chapter examples use the XML from earlier. --> <book title="Test"> <chapter title="Ch1"> <para>p1.1</para> <para>p1.2</para> <para>p1.3</para> </chapter> <chapter title="Ch2"> <para>p2.1</para> <para>p2.2</para> </chapter> </book>这个XML和原来的那个是相同的,除了它使用title属性,而不是元素 一个元素的属性可以通过和元素的子元素一样的方式来访问,除了你在属性名的前面放置一个@符号: <#assign book = doc.book> <h1>${book.@title}</h1> <#list book.chapter as ch> <h2>${ch.@title}</h2> <#list ch.para as p> <p>${p} </#list> </#list>这会打印出和前面示例相同的结果。 按照和获取子节点一样的逻辑来获得属性,所以上面的ch.@title结果就是大小为1的序列。如果没有title属性,那么结果就是一个大小为0的序列。所以要注意,这里使用内建函数也是有问题的:如果你很好奇是否foo含有属性bar,那么你不得不写foo.@bar[0]??来验证。(foo.@bar??是不对的,因为它总是返回true)。类似地,如果你想要一个bar属性的默认值,那么你就不得不写foo.@bar[0]!"theDefaultValue"。 正如子元素那样,你可以选择多节点的属性。例如,这个模板将打印所有chapter的title属性。 <#list doc.book.chapter.@title as t> ${t} </#list> 3.3、探索DOM树 这个FTL将会枚举所有book元素的子节点: <#list doc.book?children as c> - ${c?node_type} <#if c?node_type = 'element'>${c?node_name}</#if> </#list>会打印出: - text 关于?node_type的意思,有一些在DOM树中存在的节点类型,比如"element","text","comment","pi"等。 然后运行这个FTL: <#list doc.book.@@ as attr> 然后得到这个输出(或者其他相似的结果) - baaz = Baaz 要返回子节点的列表,有一个方便的子变量来仅仅列出元素的子元素: - title 可以使用内建函数parent来获得元素的父节点: <#assign e = doc.book.chapter[0].para[0]> 将会打印: para 在最后一行你访问到了DOM树的根节点,文档节点。它不是一个元素,这就是为什么得到了一个奇怪的名字; 你可以使用内建函数root来快速返回到文档节点: <#assign e = doc.book.chapter[0].para[0]> 会输出: @document 3.4、XML命名空间 默认来说,当你编写如doc.book这样的东西时,那么它会选择属于任何XML命名空间名字为book的元素。如果你想在XML命名空间中选择一个元素,你必须注册一个前缀,然后使用它。比如,如果元素book是命名空间http://example.com/ebook,那么你不得不关联一个前缀,要在模板的顶部使用ftl指令的the ns_prefixes参数: <#ftl ns_prefixes={"e":"http://example.com/ebook"}> 现在你可以编写如doc["e:book"]的表达式。(因为冒号会混淆FreeMarker,方括号语法的使用是需要的) <#ftl ns_prefixes={ ns_prefixes参数影响整个FTL命名空间。这就意味着实际中,你在主页面模板中注册的前缀必须在所有的<#include ...>模板中可见,而不是<#imported ...>模板(经常用来引用FTL库)。从另外一种观点来说,一个FTL库可以注册XML命名空间前缀来为自己使用,而前缀注册不会干扰主模板和其他库。 现在表达式doc.book选择属于XML命名空间http://example.com/ebook的book元素。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |