13.3.1 实现 XML 辅助函数
LINQ to XML 主要用于 C# 和 VB,在 F# 中使用可能有些麻烦。例如,F# 不支持隐式类型转换(因为这会使类型推断复杂),所以,每次我们指定元素名,必须使用 XName.Get,而不是只使用一个字符串。或者,我们可以写一个简单的工具函数或自定义运算符,来完成这些。
我们可以很容易地实现几个 F# 函数,把 LINQ to XML 最常用的部分打包起来,提供一个"F#-友好"的处理数据的方式。你可以看到在清单 13.8 中,大多数函数非常简单。这个是用 F# Interactive 创建的,所以,可以使用推导出的类型签名,来了解函数做了什么。一个值得注意的方面是,每个函数取输入的元素作为其最后一个参数,这意味着,我们可以使用流运算符,来组合这些函数。
Listing 13.8 Helper functions for reading XML (F# Interactive)
> #r "System.Xml.dll"
#r "System.Xml.Linq.dll"
open System.Xml.Linq;;
> let wb = "http://www.worldbank.org";;
val wb : string = http://www.worldbank.org
> let xattr s (el:XElement) =
el.Attribute(XName.Get(s)).Value
let xelem s (el:XContainer) =
el.Element(XName.Get(s, wb))
let xvalue (el:XElement) =
el.Value
let xelems s (el:XContainer) =
el.Elements(XName.Get(s, wb));;
val xattr : string -> XElement -> string
val xelem : string -> XContainer -> XElement
val xvalue : XElement -> string
val xelems : string -> XContainer -> seq<XElement>
> let xnested path (el:XContainer) =
let res = path |> Seq.fold (fun xn s –>
let child = xelem s xn
child :> XContainer) el
res :?> XElement
;;
val xnested : seq<string> -> XContainer –> XElement
大多数辅助函数是很简单的。xattr 返回指定属性的值;xelem 返回指定名字的子元素;xvalue 读取元素内的文本;xelems 返回指定名字的所有子元素;xnested 更有趣,返回由给定路径作为元素的名字的序列所指定一个子节点。当访问元素时,我们指定的 XML 命名空间,用在从世界银行返回的文档中。当我们在本章的后面,将使用这些辅助函数时,只需要提供元素的本地名称。
清单 13.8 首先引用了 LINQ to XML 所需的程序集,并打开包含 XElement 这样的类的命名空间。第一组的函数用于访问任何给定的元素的子节点、属性或值。注意,xelem 函数取 XContainer 作为参数值,这意味着,既可以于普通的元素,也可以用于表示整个文档的对象。这是可能的,因为,当将实例作为输入参数值传递给函数或方法时,F# 可以隐式转换为基类或实现的接口。在其他地方,没有隐式转换,比如,从 lambda 函数返回结果。这会使 xnested 函数稍微有点复杂,就需要增加几个新的显式强制转换。
xnested 函数取一个名称序列作为参数值,并沿着这个路径,去查找深层嵌套的元素。它用 Seq.fold 实现,并使用输入的元素作为初始状态,路径中的每个名称都执行这个 Lambda 函数。它查找具有指定名称的当前元素的子元素,并把它作为一个新的子元素返回。我们想要输入类型成为 XContainer,所以,折叠操作使用这个类型来表示当前状态。因此,我们需要在 lambda 函数内部,把返回的元素向上转换成 XContainer,把最终结果向下转换成 XElement。
有了这此辅助函数,就可以轻松地从下载的 XML 文档提取我们想要的所有信息了。如果你对这些新函数做什么,还不确定,也不必担心: 一旦我们开始使用它们来处理实际数据,一切都会更加清晰。