遗留的XML包装实现

Note:

遗留的XML包装已经废弃了。 FreeMarker 2.3 已经引入了对新的XML处理模型的支持。要支持它, 新的XML包装包已经引入了,就是 freemarker.ext.dom。 对于新用法,我们鼓励你使用。它会在XML处理指南中来说明。

freemarker.ext.xml.NodeListModel 类提供了来包装XML文档展示为结点树模板模型。 每个结点列表可以包含零个或多个XML结点 (文档类型,元素类型,文本类型,处理指令,注释,实体引用,CDATA段等)。 结点列表实现了下面模板的语义模型接口:

模板标量模型(TemplateScalarModel)

当使用一个标量时,结点列表将会呈现XML片段,表示其包含的结点。 这使得使用XML到XML转换模板很方便。

模板集合模型(TemplateCollectionModel)

当用 list 指令来使用一个集合时, 它会简单枚举它的结点。每个结点将会被当作一个新的单一结点组的结点列表返回。

模板序列模型(TemplateSequenceModel)

当被用作是序列时,它会返回第i个结点作为一个新的结点列表, 包含单独的被请求的结点。也就是说,要返回 <book> 元素的第三个 <chapter> 元素,你可以使用下面的代码 (结点索引是从零开始的):

<#assign thirdChapter = xmldoc.book.chapter[2]>

模板哈希表模型(TemplateHashModel)

当被用作是哈希表时,它基本上是用来遍历子结点。也就是说, 如果你有个名为 book 的结点列表, 并包装了一个有很多chapter的元素结点,那么 book.chapter 将会产生一个book元素的所有chapter元素的结点列表。@符号常被用来指代属性: book.@title 产生一个有单独属性的结点列表, 也就是book元素的title属性。

意识到下面这样的结果是很重要的,比如,如果 book 没有 chapter-s,那么 book.chapter 就是一个空序列,所以 xmldoc.book.chapter??不会false,它会一直是 true!相似地, xmldoc.book.somethingTotallyNonsense?? 也不会是false。为了检查是否发现子结点,可以使用 xmldoc.book.chapter?size == 0

哈希表定义了一些"魔力键"。所有的这些键以下划线开头。 最值得注意的是_text,可以得到结点的文本内容: ${book.@title._text} 将会给模板交出属性的值。 相似地,_name将会取得元素或属性的名字。 *_allChildren 返回所有结点列表元素中的直接子元素,而 @*_allAttributes 返回结点列表中元素的所有属性。 还有很多这样的键;下面给出哈希表键的详细总结:

键名 结果为
*_children 所有当前结点(非递归)的直接子元素。适用于元素和文档结点。
@*_attributes 当前结点的所有属性。仅适用于元素。
@attributeName 当前结点的命名属性。适用于元素,声明和处理指令。 在声明中它支持属性 publicIdsystemIdelementName。在处理指令中,它支持属性 targetdata,还有数据中以 name="value" 对出现的其他属性名。 对于声明和处理指令的属性结点是合成的,因此它们没有父结点。 要注意,@* 不能在声明或处理指令上进行操作。
_ancestor 当前结点的所有祖先,直到根元素(递归)。 适用于类型和 _parent 相同的结点类型。
_ancestorOrSelf 当前结点和它的所有祖先结点。 适用于和 _parent 相同的结点类型。
_cname 当前结点(命名空间URI+本地名称)的标准名称, 每个结点(非递归)一个字符串值。适用于元素和属性。
_content 当前结点的全部内容,包括子元素,文本, 实体引用和处理指令(非递归)。适用于元素和文档。
_descendant 当前结点的所有递归的子孙元素。适用于文档和元素结点。
_descendantOrSelf 当前结点和它的所有递归的子孙元素。适用于文档和元素结点。
_document 当前结点所属的所有文档类型。适用于所有除文本的结点。
_doctype 当前结点的声明。仅仅适用于文档类型结点。
_filterType 这是一种按类型过滤的模板方法模型。当被调用时, 它会产生一个结点列表,仅仅包含它们当前结点, 这些结点的类型和传递给它们参数的一种类型相匹配。 你应该传递任意数量的字符串给这个方法, 其中包含来保持类型的名字。合法的类型名称是:"attribute", "cdata","comment","document","documentType", "element","entity","entityReference", "processingInstruction","text"。
_name 当前结点的名称。每个结点(非递归)一个字符串值。 适用于元素和属性(返回它们的本地名称),实体, 处理指令(返回它的目标),声明(返回它的public ID)。
_nsprefix 当前结点的命名空间前缀,每个结点(非递归)一个字符串值。 适用于元素和属性。
_nsuri 当前结点的命名空间URI,每个结点(非递归)一个字符串值。 适用于元素和属性。
_parent 当前结点的父结点。适用于元素,属性,注释,实体,处理指令。
_qname 当前结点在 [namespacePrefix:]localName 形式的限定名,每个结点(非递归)一个字符串值。适用于元素和属性。
_registerNamespace(prefix, uri) 注册一个对当前结点列表和从当前结点列表派生出的所有结点列表有指定前缀和URI的XML命名空间。 注册之后,你可以使用nodelist["prefix:localname"]nodelist["@prefix:localname"] 语法来访问元素和属性, 它们的名字是命名空间范围内的。 注意命名空间的前缀需要不能和当前XML文档它自己使用的前缀相匹配, 因为命名空间纯粹是由URI来比较的。
_text 当前结点的文本内容,每个结点(非递归)一个字符串值。 适用于元素,属性,注释,处理指令(返回它的数据)和CDATA段。 保留的XML字符('<'和'&')不能被转义。
_type 返回描述结点类型的结点列表,每个结点包含一个字符串值。 可能的结点名称是:合法的结点名称是:"attribute","cdata","comment", "document","documentType","element","entity","entityReference", "processingInstruction","text"。如果结点类型是未知的,就返回"unknown"。
_unique 当前结点的一个拷贝,仅仅保留每个结点第一次的出现,消除重复。 重复可以通过应用对树的向上遍历出现在结点列表中,如_parent_ancestor_ancestorOrSelf_document,也就是说,foo._children._parent 会返回一个结点列表,它包含foo中重复的结点,每个结点会包含出现的次数, 和它子结点数目相等。这些情况下,使用 foo._children._parent._unique 来消除重复。适用于所有结点类型。
其它键 当前结点的子元素的名称和键相匹配。这允许以 book.chapter.title 这种风格语法进行方便的子元素遍历。 请注意,在技术上 nodeset.childnamenodeset("childname") 相同,但是两者写法都很短, 处理也很迅速。适用于文档和元素结点。

模板方法模型(TemplateMethodModel)

当被用作方法模型,它返回一个结点列表, 这个列表是处理结点列表中当前内容的XPath表达式的结果。 为了使这种特性能够工作,你必须将 Jaxen 类库放到类路径下。比如:

<#assign firstChapter=xmldoc("//chapter[first()]")>

命名空间处理

为了遍历有命名空间范围内名称的子元素这个目的, 你可以使用结点列表注册命名空间前缀。 你可以在Java代码中来做,调用:

public void registerNamespace(String prefix, String uri);

方法,或者在模板中使用

${nodelist._registerNamespace(prefix, uri)}

语法。从那里开始, 你可以在命名空间通过特定的URI来标记引用子元素,用这种语法

nodelist["prefix:localName"]

nodelist["@prefix:localName"]

和在 XPath 表达式中使用这些命名空间前缀一样。 命名空间使用一个结点列表来注册并传播到所有结点列表, 这些结点列表来自于原来的结点列表。要注意命名空间只可使用URI来进行匹配, 所以你可以在你的模板中安全地使用命名空间的前缀,这和在实际XML中的不同, 在模板和XML文档中,一个前缀只是一个对URI的本地别名。