7.3.2 用 XML 表示文档

来源:互联网 发布:51单片机电源引脚功能 编辑:程序博客网 时间:2024/06/10 09:58

7.3.2 用 XML 表示文档

 

    XML 格式非常流行,完美地适合于存储分层数据,比如,我们上一节中的文档。处理 XML,对于许多现实世界的应用程序,是重要的。因此,在这一节中,我们将扩展应用程序,以支持从 XML 文件中加载文档。我们将使用 .NET 3.5 的 LINQ to XML API 来做大部分的困难工作——编写另外的 XML 解析器也没有任何意义。LINQ to XML 是函数概念如何用于主流框架的很好例子:虽然,它不是纯粹的函数式 API (类型一般是可变的),它允许对象以递归和声明的形式构造。这可以使代码的结构一目了然,所以,它比使用 DOM API 的典型代码更容易理解。
    在某种意义上,这是数据从一种表示到另一种表示的另一种转换。在这种情况下,源表示是 LINQ to XML 对象的结构, 目标是我们 7.3.1 节的文档数据类型。这次转换变得更容易,因为,两个数据结构是分层的。清单 7.11 演示了基于 XML 格式,我们将用它来表示我们的文档。

Listing 7.11 XML representation of a sample document (XML)

 

<titled title="Functional Programming for the Real World"
    font="Cambria" size="18" style="bold">
  <split orientation="vertical">
    <image filename="C:\Writing\Functional\Cover.jpg" />
    <text>In this book, we'll introduce you (...)</text>
  </split>
</titled>

 

    在看转换的核心部分之前,我们需要实现一些工具函数,解析在 XML 中显示的属性值。特别是,我们需要一个函数,解析字体名和 SplitPart 的方向。清单 7.12 显示了这些函数,引入了几个来自 LINQ to XML 库的对象。

 

Listing 7.12 Parsing font and orientation using LINQ to XML (F#)

 

open System.Xml.Linq

let attr(node:XElement, name, defaultValue) =
  let attr = node.Attribute(XName.Get(name))
  if (attr <> null) then attr.Value else defaultValue

let parseOrientation(node) =
  match attr(node, "orientation", "") with
  | "horizontal" –> Horizontal
  | "vertical" –> Vertical
  | _ -> failwith "Unknown orientation!"

let parseFont(node) =
  let style = attr(node, "style", "")
  let style =
    match style.Contains("bold"), style.Contains("italic") with

    | true, false -> FontStyle.Bold
    | false, true -> FontStyle.Italic
    | true, true -> FontStyle.Bold ||| FontStyle.Italic
    | 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#  中,可以使用 #r"..." 指令,指定程序集的路径作为参数值,如果程序集在全局程序集缓存(GAC )中,只要指定名称。

    这个清单以 attr 函数开始,用于读取属性,它取一个 XElement (LINQ to XML 类型表示 XML 元素)作为第一个参数值,后面是这个属性的名字,最后一个参数是默认值,当该属性缺失时使用。下一个函数使用 attr 来读取传入的 XML 节点 orientation 属性的值。如果该属性包含意外的值,函数将使用标准的 F# failwith 函数,引发异常。

    parseFont 用于把 XML 标记的属性,像清单 7.11 中的标题,转换成 .NET 中的 Font 对象。最有趣的部分是解析 style 属性的方式,它检查属性值是否包含两个字符串(“bold” 和 “italic”),作为子字符串,然后,使用模式匹配为四个可能的每一个指定样式。这个函数还将表示大小的字符串转换成数字,使用 float32 转换函数,然后创建 Font. 的实例。

    现在,我们已经有了我们需要的所有的工具函数,加载 XML 文档很容易。清单 7.13 显示了递归函数 loadPart,它执行完整的转换。

 

Listing 7.13 Loading document parts from XML (F#)

 

let rec loadPart(node:XElement) =
  match node.Name.LocalName with
  | "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("Unknown node: " + name)

 

    该函数取一个 XML 元素作为参数值,当我们以后使用 XML 文档时,我们给它根元素。函数主体是一个 match 结构,依据已知的选项检查元素的名称,如果它遇到一个未知的标记,将引发异常。

    加载图像和文本的部分很容易,因为,我们只需要使用工具函数,读取其属性,并创建相应的 DocumentPart 值。其余两个文档部件类型涉及递归,使它们变得更加有趣。

    要从一个 titled 元素创建 TitledPart,我们首先解析属性中的标题文本,然后,以递归方式处理部件中的第一个 XML 元素。要读取第一个子元素,我们调用 Elements() 方法,它返回所有子元素,作为一个 .NET 的 IEnumerable 集合。在 F# 中,IEnumerable 被简称为 seq<'a,我们可以使用来自 Seq 模块的函数来处理,它类似于处理列表的函数。在我们的示例中,我们使用 Seq.head,它返回集合中的第一个元素(头)。如果我们在 C# 中写这个代码,可以调用 Elements().First() 来达到相同的效果。

    要从 split 元素创建 SplitPart,我们需要解析所有的子结点,因此,我们再次调用 Elements() 方法,但这一次,我们将结果转换为XElement 值的函数式列表。我们以递归方式使用映射把每个转换成 DocumentPart 值,把 loadPart 函数作为参数值。

    这个函数是非常简单的,因为,它提供几行代码,为每个受支持的标记解析 XML 节点。异常的简单是由于这样的事实,XML 文档是分层次的,与目标表示的方式相同。当部件有嵌套的子部件时,我们就使用递归。

    最后,我们可以看到,应用程序如何显示更大的文档:在 XML 编辑器中设计的文档,比在 F# 中创新值更容易。清单 7.14 显示了用于组合到目前为止,我们已经开发的所有代码的管道,成为一个通常的 Windows 窗体应用程序。

 

Listing 7.14 Putting the parts of the application together (F#)

 

open System.Windows.Forms

[]
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 显示运行的结果。

 

image

图 7.4 完成的应用程序,显示更复杂的文档,有四种文档部件

 

    我们提到过,分层次表示对于操作文档,以及执行初始化结构,都是有用的。现在就让我们来看一看。

原创粉丝点击