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:WritingFunctionalCover.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 最终完成的应用程序,显示更复杂的文档,有四种文档部分 我们提到过,文档的分层次表示对于文档的初始构造和操作,都是有用的。现在,我们就来看一看。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |