7.3.2 用 XML 表示文档

728 篇文章 1 订阅
349 篇文章 0 订阅

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 最终完成的应用程序,显示更复杂的文档,有四种文档部分

 

我们提到过,文档的分层次表示对于文档的初始构造和操作,都是有用的。现在,我们就来看一看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值