在.NET Framework中轻松处理XML数据(一)

本文假设你已熟悉 XML和 .NET Framework

** 前言 ** 在 .NET Framework 中, XmlTextReader 和 XmlTextWriter 类提供了对 xml 数据的读和写操作。在本文中,作者讲述了 XML 阅读器 (Reader) 的体系结构及它们怎样与 XMLDOM 和 SAX 解释器结合。作者也演示了怎么样运用阅读器分析和验证 XML 文档,怎么样创建格式良好的 XML 文档,以及怎么样用函数读 / 写基于 Base64 和 BinHex 编码的大型的 XML 文档。最后,作者讲了怎么样实现一个基于流的读 / 写分析器,它把读写器都封装在一个单独的类里。

** 大 ** 概三年前,我参加了一个软件研讨会,主题是“没有 XML ,就没有编程的未来”。 XML 确实也在一步一步的发展,它已经嵌入到 . NET Framework 中了。在本文中,我将讲解 . NET Framework 中用于处理 XML 文档的 API 的角色和它的内部特性,然后我将演示一些常用的功能。

** 从 MSXML ** ** 到 .net ** ** 的 XML **

在 . NET Framework 出现之前,你习惯使用 MSXML 服务 ---- 一个基于 COM 的类库 --- 写 windows 的 XML 的驱动程序。不像 . NET Framework 中的类, MSXML 类库的部分代码比 API 更深,它完全的嵌在操作系统的底层。 MSXML 的确能够与你的应用程序通信,但是它不能真正的与外部环境结合。

MSXML 类库能在 win32 中被导入,也能在 CLR 中运用,但它只能作为一个外部服务器组件使用。但是基于 .NET Framework 的应用程序能直接的用 XML 类与 .NET Framework 的其它命名空间整合使用,并且写出来的代码易于阅读。

作为一个独立的组件, MSXML 分析器提供了一些高级的特性如异步分析。这个特性在 .NET Framework 中的 XML 类及 .NET Framework 的其它类都没有提供,但是, NET Framework 中的 XML 类与其它的类整合可以很轻易的获得相同的功能,在这个基础上你可以增加更多的功能。

.NET Framework 中的 XML 类提供了基本的分析、查询、转换 XML 数据的功能。在 .NET Framework 中,你可以找到支持 Xpath 查询和 XSLT 转换的类,及读 / 写 XML 文档的类。另外, .NET Framework 也包含了其它处理 XML 的类,例如对象的序列化( XmlSerializer 和 the SoapFormatter 类),应用程序配置( AppSettingsReader 类),数据存储( DataSet 类)。在本文中,我只讨论实现基本 XML I/O 操作的类。

** XML ** ** 分析模式 **

既然 XML 是一种标记语言,就应该有一种工具按一定的语法来分析和理解存储在文档中信息。这个工具就是 XML 分析器 --- 一个组件用于读标记文本并返回指定平台的对象。

所有的 XML 分析器,不管它属于哪个操作平台,不外乎都分以下的两类:基于树或者基于事件的处理器。这两类通常都是用 XMLDOM ( the Microsoft XML Document Object Model )和 SAX(Simple API for XML) 来实现。 XMLDOM 分析器是一个普通的基于树的 API--- 它把 XML 文档当成一个内存结构树呈现。 SAX 分析器是基于事件的 API---- 它处理每个在 XML 数据流中的元素 ( 它把 XML 数据放进流中再进行处理 ) 。通常, DOM 能被一个 SAX 流载入并执行,因此,这两类的处理不是相互排斥的。

总的来说, SAX 分析器与 XMLDOM 分析器正好相反,它们的分析模式存在着极大的差别。 XMLDOM 被很好的定义在它的 functionalition 集合里面,你不能扩展它。当它在处理一个大型的文档时,它要占用很大内存空间来处理 functionalition 这个巨大的集合。

SAX 分析器利用客户端应用程序通过现存的指定平台的对象的实例去处理分析事件。 SAX 分析器控制整个处理过程,把数据“推出”到处理程序,该处理程序依次接受或拒绝处理数据。这种模式的优点是只需很少的内存空间。

.NET Framework 完全支持 XMLDOM 模式,但它不支持 SAX 模式。为什么呢?因为 .NET Framework 支持两种不同的分析模式: XMLDOM 分析器和 XML 阅读器。它显然不支持 SAX 分析器,但这并不意味它没有提供类似 SAX 分析器的功能。通过 XML 阅读器 SAX 的所有的功能都能很容易的实现及更有效的运用。不像 SAX 分析器, .NET Framework 的阅读器整个都运作在客户端应用程序下面。这样,应用程序本身就可以只把真正需要的数据“推出”,然后从 XML 数据流中跳出来。而 SAX 分析模式要处理所有的对应用程序有用和无用的信息。

阅读器是基于 .NET Framework 流模式工作的,它的工作方式类似于数据库的游标。有趣的是,实现类似游标分析模式的类提供对 .NET Framework 中的 XMLDOM 分析器的底层支持。 XmlReader 、 XmlWriter 两个抽象类是所有 .NET Framework 中 XML 类的基础类,包括 XMLDOM 类、 ADO.NET 驱动类及配置类。所以在 .NET Framework 中你有两种可选的方法去处理 XML 数据。用 XmlReader 和 XmlWriter 类直接处理 XML 数据,或者用 XMLDOM 模式处理。更多的关于在 .NET Framework 中读文档的介绍可以参见 MSDN 2002 年八月刊的 Cutting Edge 栏目文章。

** XmlReader ** ** 类 ** ** **

XML 阅读器支持一个编程接口,接口用于连接 XML 文档,“推出”你要的数据。如果你更深入去了解阅读器,你会发现阅读器工作原理类似于我们的桌面应用程序从数据库中取出数据的原理。数据库服务返回一个游标对象,它包含所有查询结果集,并返回指向目标数据集的开始地址的引用。 XML 阅读器的客户端收到一个指向阅读器实例的引用。该实例提取底层的数据流并把取出的数据呈现为一棵 XML 树。阅读器类提供只读、向前的游标 , 你可以用阅读器类提供的方法滚动游标遍历结果集中的每一条数据。

从阅读器中看 XML 文档不是一个标签文本文件,而是一个序列化的节点集合。它是 .NET Framework 中的一种特殊的游标模式 ; 在 .NET Framework 中 , 你找不到其它的任何一个类似的 API 函数。

阅读器和 XMLDOM 分析器有几点不同的地方。 XML 阅读器是只进的,它没有父、子、祖宗、兄弟节点的概念,而且是只读的。在 .NET Framework 中,读写 XML 文档是分为两种完全不同的功能,分别由 XmlReader 和 XmlWriter 类来完成。要编辑 XML 文档,你可以用 XMLDOM 分析器,或者你自己设计一个类来实现这两种功能。让我们开始分析阅读器的程序功能。

XmlReader 是一个抽象类,你可以继承并扩展它的功能。用户程序一般都基于下面的三种类: XmlTextReader 、 XmlValidatingReader 或者 XmlNodeReader 类。所有的这些类都有如图一的属性和图二的方法。要注意的是,某些属性的值实际上依赖于实际的某个阅读器类,不同的类与基类可能不同。因此,在图一中每个属性的说明都是以基类为准的。例如, CanResolveEntity 属性在 XmlValidatingReader 类中只返回 true ;而在其它的阅读器类中它却可以设为 false 。同样的,在图二中的某些方法的实际返回值对不同的类可能不同。例如,如果节点类型不是元素节点( element node ) , 所有包含 Atrributes 的方法的返回值类型都是 void 。

XmlTextReader 类用只进,只读的方式快速访问 XML 数据流。阅读器先验证 XML 文档是否是格式良好的,如果不是则抛出一个异常。 XmlTextReader 检查 DTD 的格式是否良好,但不使用 DTD 对文档进行验证。 XmlTextReader 通过 XML 文档的文件名,或它的 URL ,或者从文件流中载入 XML 文档,然后快速的处理 XML 文档数据。如果你需要对文档的数据进行验证,你可以用 XmlValidatingReader 类。

可以用多种方法创建 XmlTextReader 类的实例,从硬盘中加载文件,或从 URL 地址中加载,流( streams )中加载,还有就是从文本中读入 XML 文档数据:

XmlTextReader reader = new XmlTextReader(file);

注意,所有 XmlTextReader 类的公共 (public) 构造函数都要求你指定数据源,数据源可以是 stream 、文件或者其它。 XmlTextReader 默认的构造函数是受保护的( protected ),所以不能直接使用。像 .NET Framework 中所有的阅读器类一样 ( 如 SqlDataReader 类 ) ,一旦阅读器对象连接并打开,你就可以用 Read 方法去访问数据了。开始的时候只能用 Read 方法把指针移到第一个元素;然后我们可以用 Read 方法或其它方法(如 Skip, MoveToContent 和 ReadInnerXml )移动指针到下一个节点元素。要处理整个 XML 文档的内容,可以根据 Read 方法的返回值用一个循环遍历文档内容,因为 Read 方法返回一个布尔值,当读到文档的尾节点时, Read 方法返回 false ,否则它返回 true 。

** Figure 3 ** ** Outputting an XML Document Node Layout **

string GetXmlFileNodeLayout(string file)


{


    // 创建一个XmlTextReader类使它指向目标XML文档


    XmlTextReader reader = new XmlTextReader(file);


 


// 循环取出节点的文本并放入到StringWriter对象实例中


StringWriter writer = new StringWriter();


    string tabPrefix = "";


 


    while (reader.Read())


    {


        // 写开始标志,如果节点类型为元素


        if (reader.NodeType == XmlNodeType.Element)


        {


            //根据元素所处节点的深度,加入reader.Depth个tab符,然后把元素名写入到<>中。


                  tabPrefix = new string('\t', reader.Depth);


            writer.WriteLine("{0}<{1}>", tabPrefix, reader.Name);


        }


        else


        {


            //写结束标志,如果节点类型为元素


            if (reader.NodeType == XmlNodeType.EndElement)


            {


                tabPrefix = new string('\t', reader.Depth);


                writer.WriteLine("{0}

", tabPrefix, reader.Name);

            }


        }


    }


 


    // 输出到屏幕


    string buf = writer.ToString();


    writer.Close();


 


    // 关闭流


    reader.Close();


 


   return buf;


}

图三演示了一个简单的用于输出一个给定的 XML 文档的节点元素的函数。该函数先打开一个 XML 文档,然后用循环处理 XML 文档中所有的内容。每次调用 Read 方法,阅读器的指针都会向下移一个节点。大部分情况下,用 Read 方法可以处理的元素节点,但有时候,当你从一个节点移动到下一个节点时,可能是在两个不同类型的节点间移动。但是 Read 方法不能在属性节点之间移动。阅读器的 MoveToContent 方法可以让指针从头部节点位置跳到第一个内容节点位置。在 ProcessingInstruction, DocumentType, Comment, Whitespace 和 SignificantWhitespace 类型节点中也可以用 Skip 方法移动指针。
每个节点的类型是 XmlNodeType 枚举值中的一种,在如图三所示的代码中,我们只用了其中的两种类型: Element 和 EndElement 。输出源码重新定制了原始的文档结构,它丢弃或者说是忽略了 XML 元素的属性和节点内容,只输出了元素节点名。假设我们运用了下面的 XML 片断:

 1<mags>
 2    
 3    
 4       <mag name="MSDN Magazine">
 5    
 6    
 7       MSDN Magazine
 8    
 9    
10       </mag>
11    
12    
13       <mag name="MSDN Voices">
14    
15    
16       MSDN Voices
17    
18    
19       </mag>
20</mags>

用上面的程序输出的结果如下 :

 1<mags>
 2    
 3    
 4       <mag>
 5    
 6    
 7       </mag>
 8    
 9    
10       <mag>
11    
12    
13       </mag>
14</mags>

子节点的缩进量是根据阅读器的深度属性(Depth属性)设置的,Depth属性返回一个整形的数据,它表示当前节点的嵌套层次。所有文本都放在StringWriter对象中(一个非常方便的基于流的封装了StrigBuilder类的类)。

如前所述,阅读器不会自动通过Read方法访问属性节点。要访问当前元素的属性节点集合,必须用一个简单的用MoveToNextAttribute方法的返回值控制的循环去遍历该集合。下面的代码用于访问当前节点的所有属性,并把属性的名称和它的值用逗号分开组合成一个字符串:


if (reader.HasAttributes)


while(reader.MoveToNextAttribute())


   buf += reader.Name + "=\"" + reader.Value + "\",";


reader.MoveToElement();

当你完成对属性集的处理时,调用 MoveToElement 方法使指针返回到属性所属的元素节点。准确的说, MoveToElement 方法并不是真正的移动指针,因为在处理属性集时指针从来就没有从元素节点中移开。 MoveToElement 方法只不过指向某个内部成员,并依次取得成员的值。例如,用 Name 属性获得某个属性的属性名,然后调用 MoveToElement 方法把指针移到其所属的元素节点处。但是当你不需要继续处理别的节点时,就不必再调用 <SPAN lang=EN-US style="FON

Published At
Categories with Web编程
Tagged with
comments powered by Disqus