首页 > 代码库 > 7.3.2 用 XML 表示文档
7.3.2 用 XML 表示文档
7.3.2 用 XML 表示文档
XML 格式非常流行,非常适合于保存分层次的数据,比如,上一节的文档。如何处理 XML,对于许多实际应用非常重要,因此,在这一节,我们要扩展我们的应用程序,以支持从 XML 文件加载文档。我们将使用.NET 3.5 的 LINQ to XMLAPI 完成大部分的困难工作,自己再写另外的 XML 解析器没有任何意义。LINQto XML 是函数概念应用于主流框架的很好示例:虽然它不是纯粹的函数式 API(类型一般是可变的),但是对象能够以递归和声明的形式构造,这就使代码的结构一目了然,因此,比使用 DOM API 的传统代码更容易理解。
在某种意义上,这是数据从一种表示到另一种表示的再次转换。这次,源表示是 LINQ to XML 对象的结构,目标是我们 7.3.1 节的文档数据类型。这次的转换更容易,因为两种数据结构都是分层次的。清单 7.11 是用 XML 格式表示的文档。
清单 7.11 XML 表示的样本文档 (XML)
<titled title="FunctionalProgramming for the Real World"
font="Cambria"size="18" style="bold">
<splitorientation="vertical">
<imagefilename="C:\Writing\Functional\Cover.jpg" /> | 把子部分保存为
<text>In thisbook, we‘ll introduce you (...)</text> | 嵌套的 XML 元素
</split>
</titled>
在看到转换的核心之前,我们需要实现一些工具函数,解析在 XML 中显示的特性值;特别是,我们需要解析字体名和 SplitPart方向的函数。清单 7.12 显示了这些函数,并引入了几个LINQ to XML 库的对象。
清单 7.12 使用 LINQ to XML 解析字体和方向 (F#)
open System.Xml.Linq
let attr(node:XElement, name, defaultValue)= [1]
let attr =node.Attribute(XName.Get(name))
if (attr <> null) thenattr.Value else defaultValue <-- 如果没有,就返回默认值
let parseOrientation(node) = [2]
match attr(node,"orientation", "") with
| "horizontal" –>Horizontal
| "vertical" –>Vertical
| _ -> failwith "Unknownorientation!" <-- 抛出异常
let parseFont(node) = [3]
let style = attr(node,"style", "")
let style =
matchstyle.Contains("bold"), style.Contains("italic") with
| true, false ->FontStyle.Bold
| false, true ->FontStyle.Italic
| true, true ->FontStyle.Bold ||| FontStyle.Italic <-- 组合两个 .NET 枚举值
| false, false ->FontStyle.Regular
let name = attr(node,"font", "Calibri")
new Font(name, float32(attr(node,"size", "12")), style)
这段代码将只引用了System.Xml.dll 和System.Xml.Linq.dll 程序集。在 Visual Studio 中,通常可以从解决方案资源管理器中,用添加引用命令实现;在 F# Interactive 中,可以使用 #r"..." 指令,参数值指定程序集的路径,如果程序集在全局程序集缓存(GAC)中,只要指定名称就行了。
清单开头的 attr 函数[1],用于读特性,第一个参数是 XElement(LINQ to XML 类型,表示 XML 元素),后面特性的名字,最后一个参数是默认值,当没有这个特性时使用。第二个函数[2]使用 attr 读取传入的 XML 节点中 orientation 特性的值。如果特性包含的不是希望的值,函数将使用标准的 F#failwith 函数,抛出异常。
parseFont [3]用于把 XML 标记的特性,像清单 7.11中的标题,转换成 .NET 中的 Font 对象。最有意义的部分是解析 style 特性,检查特性值是否包含两个字符串(bold 和 italic),作为子字符串,然后,使用模式匹配为四种可能性中的每一个指定样式。这个函数还使用 float32 转换函数,把表示大小的字符串转换成数字,然后创建 Font 实例。
我们已经有了需要的所有工具函数,因此,加载 XML 文档就很容易了。清单7.13 显示了递归函数loadPart,实现全部转换。
清单 7.13从 XML 中加载文档 (F#)
let rec loadPart(node:XElement) =
match node.Name.LocalName with [1]
| "titled" –>
let tx = { Text =attr(node, "title", ""); Font = parseFont node}
let body =loadPart(Seq.head(node.Elements())) <-- 递归加载第一个子元素
TitledPart(tx,body)
| "split" ->
let orient =parseOrientation node
let nodes =node.Elements() |> List.ofSeq |> List.map loadPart <-- 递归加载所有子元素
SplitPart(orient,nodes)
| "text" ->
TextPart({Text =node.Value; Font = parseFont node})
| "image" ->
ImagePart(attr(node,"filename", ""))
| name -> failwith("Unknownnode: " + name) [2]
函数的参数为 XML 元素,我们在以后使用时,我们把 XML 文档的根元素给它。函数主体是一个 match构造[1],依据已知的选项检查元素的名称,如果遇到未知的标记[2],就抛出异常。
加载图像和文本部分很容易,因为我们只要使用工具函数,读取特性,并创建相应的 DocumentPart 值。其余两个文档部分类型涉及递归,因此更重要。
要从 titled 元素创建 TitledPart,首先要解析标题文本的特性,然后,递归处理这部分中的第一个 XML 元素。读取第一个子元素,我们调用 Elements() 方法,它以 .NET IEnumerable 集合的形式返回所有子元素。在 F# 中,IEnumerable<T> 简称为 seq<‘a>,因此,可以使用 Seq 模块中的函数来处理,它类似于处理列表的函数。在我们的示例中,我们使用Seq.head,返回集合中的第一个元素(头)。如果我们用 C# 写代码,可以调用Elements().First() 来达到同样的效果。
从 split 元素创建 SplitPart,需要解析所有子元素,因此,我们再次调用 Elements() 方法,但这一次,我们把结果转换为XElement 值的函数式列表。我们递归地把每个元素转换成 DocumentPart 值,使用映射,把 loadPart 函数作为参数值。
这个函数非常简单,因为它只用几行代码,就能为每个受支持的标记解析 XML 节点。之所以这样简单,是因为 XML 文档的分层次,与目标表示形式的方式相同,这样,一个部分有嵌套的子部分时,能够使用递归。
最后,我们会看到应用程序如何显示大文档:在 XML 编辑器中设计文档,比在 F#中创建值更容易。清单7.14 显示了最后的组装,把目前为止我们已经开发的所有代码组合成通常的 Windows 窗体应用程序。
清单 7.14 把应用程序的所有部分组织到一起 (F#)
open System.Windows.Forms
[<System.STAThread>]
do
let doc =loadPart(XDocument.Load(@"..\..\document.xml").Root)
let bounds = { Left = 0.0f; Top =0.0f; Width = 520.0f; Height = 630.0f }
let parts = documentToScreen(doc,bounds)
let img = drawImage (570, 680) 25.0f(drawElements parts)
let main = new Form(Text ="Document", BackgroundImage = img,
ClientSize = Size(570, 680))
Application.Run(main)
代码首先使用XDocument 类,从 XML 文件加载文档。我们把文档的根元素传递给 loadPart 函数,进行文档分层次表示的转换;接下来,我们使用documentToScreen,将它转换成平面表示,然后,使用我们在清单 7.8 中看到的代码,绘制并显示文档。我们还添加了 STAThread 特性,这是独立的 Windows 窗体应用程序所必需的。最后一行,用 Application.Run 方法,启动应用的程序。图 7.4 显示运行的结果。
图 7.4 最终完成的应用程序,显示更复杂的文档,有四种文档部分
我们提到过,文档的分层次表示对于文档的初始构造和操作,都是有用的。现在,我们就来看一看。
7.3.2 用 XML 表示文档