在net Framework中XMLTextReader和XmlTextWriter类提供了对xml数据的读和写操作在本文中作者讲述了XML阅读器(Reader)的体系结构及它们怎样与XMLDOM 和SAX 解释器结合作者也演示了怎么样运用阅读器分析和验证XML文档怎么样创建格式良好的XML文档以及怎么样用函数读/写基于Base和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类库能在win中被导入也能在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分析器的底层支持XmlReaderXmlWriter两个抽象类是所有NET Framework中XML类的基础类包括XMLDOM类ADONET驱动类及配置类所以在NET Framework中你有两种可选的方法去处理XML数据用XmlReader和XmlWriter类直接处理XML数据或者用XMLDOM模式处理更多的关于在NET Framework中读文档的介绍可以参见MSDN 年八月刊的Cutting Edge栏目文章
XmlReader类XML阅读器支持一个编程接口接口用于连接XML文档推出你要的数据如果你更深入去了解阅读器你会发现阅读器工作原理类似于我们的桌面应用程序从数据库中取出数据的原理数据库服务返回一个游标对象它包含所有查询结果集并返回指向目标数据集的开始地址的引用XML阅读器的客户端收到一个指向阅读器实例的引用该实例提取底层的数据流并把取出的数据呈现为一棵XML树阅读器类提供只读向前的游标你可以用阅读器类提供的方法滚动游标遍历结果集中的每一条数据
从阅读器中看XML文档不是一个标签文本文件而是一个序列化的节点集合它是NET Framework中的一种特殊的游标模式在NET Framework中你找不到其它的任何一个类似的API函数
阅读器和XMLDOM分析器有几点不同的地方XML阅读器是只进的它没有父子祖宗兄弟节点的概念而且是只读的在NET Framework中读写XML文档是分为两种完全不同的功能分别由XmlReader和XmlWriter类来完成要编辑XML文档你可以用XMLDOM分析器或者你自己设计一个类来实现这两种功能让我们开始分析阅读器的程序功能
XmlReader是一个抽象类你可以继承并扩展它的功能用户程序一般都基于下面的三种类XmlTextReaderXmlValidatingReader或者 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 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 (readerRead())
{
// 写开始标志如果节点类型为元素
if (readerNodeType == XmlNodeTypeElement)
{
//根据元素所处节点的深度加入readerDepth个tab符然后把元素名写入到<>中
tabPrefix = new string(\t readerDepth);
writerWriteLine({}<{}> tabPrefix readerName);
}
else
{
//写结束标志如果节点类型为元素
if (readerNodeType == XmlNodeTypeEndElement)
{
tabPrefix = new string(\t readerDepth);
writerWriteLine({} tabPrefix readerName);
}
}
}
// 输出到屏幕
string buf = writerToString();
writerClose();
// 关闭流
readerClose();
return buf;
}
图三演示了一个简单的用于输出一个给定的XML文档的节点元素的函数该函数先打开一个XML文档然后用循环处理XML文档中所有的内容每次调用Read方法阅读器的指针都会向下移一个节点大部分情况下用Read方法可以处理的元素节点但有时候当你从一个节点移动到下一个节点时可能是在两个不同类型的节点间移动但是Read方法不能在属性节点之间移动阅读器的MoveToContent方法可以让指针从头部节点位置跳到第一个内容节点位置在ProcessingInstruction DocumentType Comment Whitespace和SignificantWhitespace类型节点中也可以用Skip方法移动指针
每个节点的类型是XmlNodeType枚举中的一种在如图三所示的代码中我们只用了其中的两种类型Element 和 EndElement输出源码重新定制了原始的文档结构它丢弃或者说是忽略了XML元素的属性和节点内容只输出了元素节点名假设我们运用了下面的XML片断
<mags>
<mag name=MSDN Magazine>
MSDN Magazine
</mag>
<mag name=MSDN Voices>
MSDN Voices
</mag>
</mags>
用上面的程序输出的结果如下:
<mags>
<mag>
</mag>
<mag>
</mag>
</mags>
子节点的缩进量是根据阅读器的深度属性(Depth属性)设置的Depth属性返回一个整形的数据它表示当前节点的嵌套层次所有文本都放在StringWriter对象中(一个非常方便的基于流的封装了StrigBuilder类的类)
如前所述阅读器不会自动通过Read方法访问属性节点要访问当前元素的属性节点集合必须用一个简单的用MoveToNextAttribute方法的返回值控制的循环去遍历该集合下面的代码用于访问当前节点的所有属性并把属性的名称和它的值用逗号分开组合成一个字符串
if (readerHasAttributes)
while(readerMoveToNextAttribute())
buf += readerName + =\ + readerValue + \;
readerMoveToElement();
当你完成对属性集的处理时调用MoveToElement方法使指针返回到属性所属的元素节点准确的说MoveToElement方法并不是真正的移动指针因为在处理属性集时指针从来就没有从元素节点中移开MoveToElement方法只不过指向某个内部成员并依次取得成员的值例如用Name属性获得某个属性的属性名然后调用MoveToElement方法把指针移到其所属的元素节点处但是当你不需要继续处理别的节点时就不必再调用MoveToElement方法了