在.NET Framework中,XmlTextReader和XmlTextWriter类提供了对xml数据的读和写操作。在本文中,作者讲述了
XML阅读器(Reader)的体系结构及它们怎样与
XML和SAX 解释器结合。作者也演示了怎么样运用阅读器分析和验证
XML文档,怎么样创建格式良好的
XML文档,以及怎么样用
函数读/写基于Base64和BinHex编码的大型的
XML文档。最后,作者讲了怎么样实现一个基于流的读/写分析器,它把读写器都封装在一个单独的类里。 DOM
大概三年前,我参加了一个软件研讨会,主题是
“
没有
XML
,就没有编程的未来
”
。
XML
确实也在一步一步的发展,它已经嵌入到
. NET Framework
中了。在本文中,我将讲解
. NET Framework
中用于处理
XML
文档的
API
的角色和它的内部特性,然后我将演示一些常用的功能。
从MSXML到.net的XML
在 . NET Framework 出现之前,你习惯使用 MSXML 服务 ---- 一个基于 COM 的类库 --- 写 windows 的 XML 的驱动程序。不像 . NET Framework 中的类, MSXML 类库的部分代码比 API 更深,它完全的嵌在 操作系统 的底层。 MSXML 的确能够与你的应用程序通信,但是它不能真正的与外部环境结合。
MS XML 类库能在 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栏目文章。
从MSXML到.net的XML
在 . NET Framework 出现之前,你习惯使用 MSXML 服务 ---- 一个基于 COM 的类库 --- 写 windows 的 XML 的驱动程序。不像 . NET Framework 中的类, MSXML 类库的部分代码比 API 更深,它完全的嵌在 操作系统 的底层。 MSXML 的确能够与你的应用程序通信,但是它不能真正的与外部环境结合。
MS XML 类库能在 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方法移动 指针。
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
片断:
<mags>
<mag name="MSDN Magazine">
MSDN Magazine
</mag>
<mag name="MSDN Voices">
MSDN Voices
</mag>
</mags>
用上面的程序输出的结果如下 :
<mag name="MSDN Magazine">
MSDN Magazine
</mag>
<mag name="MSDN Voices">
MSDN Voices
</mag>
</mags>
用上面的程序输出的结果如下 :
<mags>
<mag>
</mag>
<mag>
</mag>
</mags>
<mag>
</mag>
<mag>
</mag>
</mags>
子节点的缩进量是根据阅读器的深度
属性
(
Depth属性
)设置的,
Depth属性
返回一个整形的数据,它表示当前节点的嵌套层次。所有文本都放在
StringWriter
对象中(一个非常方便的基于流的封装了
StrigBuilder
类的类)。