在.NET Framework中簡單處理XML數(shù)據(jù)(一)
發(fā)表時(shí)間:2023-08-05 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]本文假設(shè)你已熟悉XML和.NET Framework 前言 在.NET Framework中,XmlTextReader和XmlTextWriter類提供了對xml數(shù)據(jù)的讀和寫操作。...
本文假設(shè)你已熟悉XML和.NET Framework
前言 在.NET Framework中,XmlTextReader和XmlTextWriter類提供了對xml數(shù)據(jù)的讀和寫操作。在本文中,作者講述了XML閱讀器(Reader)的體系結(jié)構(gòu)及它們怎樣與XMLDOM 和SAX 解釋器結(jié)合。作者也演示了怎么樣運(yùn)用閱讀器分析和驗(yàn)證XML文檔,怎么樣創(chuàng)建格式良好的XML文檔,以及怎么樣用函數(shù)讀/寫基于Base64和BinHex編碼的大型的XML文檔。最后,作者講了怎么樣實(shí)現(xiàn)一個(gè)基于流的讀/寫分析器,它把讀寫器都封裝在一個(gè)單獨(dú)的類里。
大概三年前,我參加了一個(gè)軟件研討會(huì),主題是“沒有XML,就沒有編程的未來”。XML確實(shí)也在一步一步的發(fā)展,它已經(jīng)嵌入到. NET Framework中了。在本文中,我將講解. NET Framework中用于處理XML文檔的API的角色和它的內(nèi)部特性,然后我將演示一些常用的功能。
從MSXML到.net的XML
在. NET Framework出現(xiàn)之前,你習(xí)慣使用MSXML服務(wù)----一個(gè)基于COM的類庫---寫windows的XML的驅(qū)動(dòng)程序。不像. NET Framework中的類,MSXML類庫的部分代碼比API更深,它完全的嵌在操作系統(tǒng)的底層。MSXML的確能夠與你的應(yīng)用程序通信,但是它不能真正的與外部環(huán)境結(jié)合。
MSXML類庫能在win32中被導(dǎo)入,也能在CLR中運(yùn)用,但它只能作為一個(gè)外部服務(wù)器組件使用。但是基于.NET Framework的應(yīng)用程序能直接的用XML類與.NET Framework 的其它命名空間整合使用,并且寫出來的代碼易于閱讀。
作為一個(gè)獨(dú)立的組件,MSXML分析器提供了一些高級(jí)的特性如異步分析。這個(gè)特性在.NET Framework中的XML類及.NET Framework的其它類都沒有提供,但是,NET Framework中的XML類與其它的類整合可以很輕易的獲得相同的功能,在這個(gè)基礎(chǔ)上你可以增加更多的功能。
.NET Framework中的XML類提供了基本的分析、查詢、轉(zhuǎn)換XML數(shù)據(jù)的功能。在.NET Framework中,你可以找到支持Xpath查詢和XSLT轉(zhuǎn)換的類,及讀/寫XML文檔的類。另外,.NET Framework也包含了其它處理XML的類,例如對象的序列化(XmlSerializer和the SoapFormatter類),應(yīng)用程序配置(AppSettingsReader類),數(shù)據(jù)存儲(chǔ)(DataSet類)。在本文中,我只討論實(shí)現(xiàn)基本XML I/O操作的類。
XML分析模式
既然XML是一種標(biāo)記語言,就應(yīng)該有一種工具按一定的語法來分析和理解存儲(chǔ)在文檔中信息。這個(gè)工具就是XML分析器---一個(gè)組件用于讀標(biāo)記文本并返回指定平臺(tái)的對象。
所有的XML分析器,不管它屬于哪個(gè)操作平臺(tái),不外乎都分以下的兩類:基于樹或者基于事件的處理器。這兩類通常都是用XMLDOM(the Microsoft XML Document Object Model)和SAX(Simple API for XML)來實(shí)現(xiàn)。XMLDOM分析器是一個(gè)普通的基于樹的API---它把XML文檔當(dāng)成一個(gè)內(nèi)存結(jié)構(gòu)樹呈現(xiàn)。SAX分析器是基于事件的API----它處理每個(gè)在XML數(shù)據(jù)流中的元素(它把XML數(shù)據(jù)放進(jìn)流中再進(jìn)行處理)。通常,DOM能被一個(gè)SAX流載入并執(zhí)行,因此,這兩類的處理不是相互排斥的。
總的來說,SAX分析器與XMLDOM分析器正好相反,它們的分析模式存在著極大的差別。XMLDOM被很好的定義在它的functionalition集合里面,你不能擴(kuò)展它。當(dāng)它在處理一個(gè)大型的文檔時(shí),它要占用很大內(nèi)存空間來處理functionalition這個(gè)巨大的集合。
SAX分析器利用客戶端應(yīng)用程序通過現(xiàn)存的指定平臺(tái)的對象的實(shí)例去處理分析事件。SAX分析器控制整個(gè)處理過程,把數(shù)據(jù)“推出”到處理程序,該處理程序依次接受或拒絕處理數(shù)據(jù)。這種模式的優(yōu)點(diǎn)是只需很少的內(nèi)存空間。
.NET Framework完全支持XMLDOM模式,但它不支持SAX模式。為什么呢?因?yàn)?NET Framework支持兩種不同的分析模式:XMLDOM分析器和XML閱讀器。它顯然不支持SAX分析器,但這并不意味它沒有提供類似SAX分析器的功能。通過XML閱讀器SAX的所有的功能都能很容易的實(shí)現(xiàn)及更有效的運(yùn)用。不像SAX分析器,.NET Framework的閱讀器整個(gè)都運(yùn)作在客戶端應(yīng)用程序下面。這樣,應(yīng)用程序本身就可以只把真正需要的數(shù)據(jù)“推出”,然后從XML數(shù)據(jù)流中跳出來。而SAX分析模式要處理所有的對應(yīng)用程序有用和無用的信息。
閱讀器是基于.NET Framework流模式工作的,它的工作方式類似于數(shù)據(jù)庫的游標(biāo)。有趣的是,實(shí)現(xiàn)類似游標(biāo)分析模式的類提供對.NET Framework中的XMLDOM分析器的底層支持。XmlReader、XmlWriter兩個(gè)抽象類是所有.NET Framework中XML類的基礎(chǔ)類,包括XMLDOM類、ADO.NET驅(qū)動(dòng)類及配置類。所以在.NET Framework中你有兩種可選的方法去處理XML數(shù)據(jù)。用XmlReader和XmlWriter類直接處理XML數(shù)據(jù),或者用XMLDOM模式處理。更多的關(guān)于在.NET Framework中讀文檔的介紹可以參見MSDN 2002 年八月刊的Cutting Edge欄目文章。
XmlReader類
XML閱讀器支持一個(gè)編程接口,接口用于連接XML文檔,“推出”你要的數(shù)據(jù)。如果你更深入去了解閱讀器,你會(huì)發(fā)現(xiàn)閱讀器工作原理類似于我們的桌面應(yīng)用程序從數(shù)據(jù)庫中取出數(shù)據(jù)的原理。數(shù)據(jù)庫服務(wù)返回一個(gè)游標(biāo)對象,它包含所有查詢結(jié)果集,并返回指向目標(biāo)數(shù)據(jù)集的開始地址的引用。XML閱讀器的客戶端收到一個(gè)指向閱讀器實(shí)例的引用。該實(shí)例提取底層的數(shù)據(jù)流并把取出的數(shù)據(jù)呈現(xiàn)為一棵XML樹。閱讀器類提供只讀、向前的游標(biāo),你可以用閱讀器類提供的方法滾動(dòng)游標(biāo)遍歷結(jié)果集中的每一條數(shù)據(jù)。
從閱讀器中看XML文檔不是一個(gè)標(biāo)簽文本文件,而是一個(gè)序列化的節(jié)點(diǎn)集合。它是.NET Framework中的一種特殊的游標(biāo)模式;在.NET Framework中,你找不到其它的任何一個(gè)類似的API函數(shù)。
閱讀器和XMLDOM分析器有幾點(diǎn)不同的地方。XML閱讀器是只進(jìn)的,它沒有父、子、祖宗、兄弟節(jié)點(diǎn)的概念,而且是只讀的。在.NET Framework中,讀寫XML文檔是分為兩種完全不同的功能,分別由XmlReader和XmlWriter類來完成。要編輯XML文檔,你可以用XMLDOM分析器,或者你自己設(shè)計(jì)一個(gè)類來實(shí)現(xiàn)這兩種功能。讓我們開始分析閱讀器的程序功能。
XmlReader是一個(gè)抽象類,你可以繼承并擴(kuò)展它的功能。用戶程序一般都基于下面的三種類:XmlTextReader、XmlValidatingReader或者 XmlNodeReader類。所有的這些類都有如圖一的屬性和圖二的方法。要注意的是,某些屬性的值實(shí)際上依賴于實(shí)際的某個(gè)閱讀器類,不同的類與基類可能不同。因此,在圖一中每個(gè)屬性的說明都是以基類為準(zhǔn)的。例如,CanResolveEntity屬性在XmlValidatingReader類中只返回true;而在其它的閱讀器類中它卻可以設(shè)為false。同樣的,在圖二中的某些方法的實(shí)際返回值對不同的類可能不同。例如,如果節(jié)點(diǎn)類型不是元素節(jié)點(diǎn)(element node),所有包含Atrributes的方法的返回值類型都是void。
XmlTextReader類用只進(jìn),只讀的方式快速訪問XML數(shù)據(jù)流。閱讀器先驗(yàn)證XML文檔是否是格式良好的,如果不是則拋出一個(gè)異常。XmlTextReader 檢查 DTD 的格式是否良好,但不使用 DTD 對文檔進(jìn)行驗(yàn)證。XmlTextReader通過XML文檔的文件名,或它的URL,或者從文件流中載入XML文檔,然后快速的處理XML文檔數(shù)據(jù)。如果你需要對文檔的數(shù)據(jù)進(jìn)行驗(yàn)證,你可以用XmlValidatingReader類。
可以用多種方法創(chuàng)建XmlTextReader類的實(shí)例,從硬盤中加載文件,或從URL地址中加載,流(streams)中加載,還有就是從文本中讀入XML文檔數(shù)據(jù):
XmlTextReader reader = new XmlTextReader(file);
注意,所有XmlTextReader類的公共(public)構(gòu)造函數(shù)都要求你指定數(shù)據(jù)源,數(shù)據(jù)源可以是stream、文件或者其它。XmlTextReader默認(rèn)的構(gòu)造函數(shù)是受保護(hù)的(protected),所以不能直接使用。像.NET Framework中所有的閱讀器類一樣(如SqlDataReader類),一旦閱讀器對象連接并打開,你就可以用Read方法去訪問數(shù)據(jù)了。開始的時(shí)候只能用Read方法把指針移到第一個(gè)元素;然后我們可以用Read方法或其它方法(如Skip, MoveToContent和ReadInnerXml)移動(dòng)指針到下一個(gè)節(jié)點(diǎn)元素。要處理整個(gè)XML文檔的內(nèi)容,可以根據(jù)Read方法的返回值用一個(gè)循環(huán)遍歷文檔內(nèi)容,因?yàn)镽ead方法返回一個(gè)布爾值,當(dāng)讀到文檔的尾節(jié)點(diǎn)時(shí),Read方法返回false,否則它返回true。
Figure 3 Outputting an XML Document Node Layout
string GetXmlFileNodeLayout(string file)
{
// 創(chuàng)建一個(gè)XmlTextReader類使它指向目標(biāo)XML文檔
XmlTextReader reader = new XmlTextReader(file);
// 循環(huán)取出節(jié)點(diǎn)的文本并放入到StringWriter對象實(shí)例中
StringWriter writer = new StringWriter();
string tabPrefix = "";
while (reader.Read())
{
// 寫開始標(biāo)志,如果節(jié)點(diǎn)類型為元素
if (reader.NodeType == XmlNodeType.Element)
{
//根據(jù)元素所處節(jié)點(diǎn)的深度,加入reader.Depth個(gè)tab符,然后把元素名寫入到<>中。
tabPrefix = new string('\t', reader.Depth);
writer.WriteLine("{0}<{1}>", tabPrefix, reader.Name);
}
else
{
//寫結(jié)束標(biāo)志,如果節(jié)點(diǎn)類型為元素
if (reader.NodeType == XmlNodeType.EndElement)
{
tabPrefix = new string('\t', reader.Depth);
writer.WriteLine("{0}</{1}>", tabPrefix, reader.Name);
}
}
}
// 輸出到屏幕
string buf = writer.ToString();
writer.Close();
// 關(guān)閉流
reader.Close();
return buf;
}
圖三演示了一個(gè)簡單的用于輸出一個(gè)給定的XML文檔的節(jié)點(diǎn)元素的函數(shù)。該函數(shù)先打開一個(gè)XML文檔,然后用循環(huán)處理XML文檔中所有的內(nèi)容。每次調(diào)用Read方法,閱讀器的指針都會(huì)向下移一個(gè)節(jié)點(diǎn)。大部分情況下,用Read方法可以處理的元素節(jié)點(diǎn),但有時(shí)候,當(dāng)你從一個(gè)節(jié)點(diǎn)移動(dòng)到下一個(gè)節(jié)點(diǎn)時(shí),可能是在兩個(gè)不同類型的節(jié)點(diǎn)間移動(dòng)。但是Read方法不能在屬性節(jié)點(diǎn)之間移動(dòng)。閱讀器的MoveToContent方法可以讓指針從頭部節(jié)點(diǎn)位置跳到第一個(gè)內(nèi)容節(jié)點(diǎn)位置。在ProcessingInstruction, DocumentType, Comment, Whitespace和SignificantWhitespace類型節(jié)點(diǎn)中也可以用Skip方法移動(dòng)指針。
每個(gè)節(jié)點(diǎn)的類型是XmlNodeType枚舉值中的一種,在如圖三所示的代碼中,我們只用了其中的兩種類型:Element 和 EndElement。輸出源碼重新定制了原始的文檔結(jié)構(gòu),它丟棄或者說是忽略了XML元素的屬性和節(jié)點(diǎn)內(nèi)容,只輸出了元素節(jié)點(diǎn)名。假設(shè)我們運(yùn)用了下面的XML片斷:
<mags>
<mag name="MSDN Magazine">
MSDN Magazine
</mag>
<mag name="MSDN Voices">
MSDN Voices
</mag>
</mags>
用上面的程序輸出的結(jié)果如下:
<mags>
<mag>
</mag>
<mag>
</mag>
</mags>
子節(jié)點(diǎn)的縮進(jìn)量是根據(jù)閱讀器的深度屬性(Depth屬性)設(shè)置的,Depth屬性返回一個(gè)整形的數(shù)據(jù),它表示當(dāng)前節(jié)點(diǎn)的嵌套層次。所有文本都放在StringWriter對象中(一個(gè)非常方便的基于流的封裝了StrigBuilder類的類)。
如前所述,閱讀器不會(huì)自動(dòng)通過Read方法訪問屬性節(jié)點(diǎn)。要訪問當(dāng)前元素的屬性節(jié)點(diǎn)集合,必須用一個(gè)簡單的用MoveToNextAttribute方法的返回值控制的循環(huán)去遍歷該集合。下面的代碼用于訪問當(dāng)前節(jié)點(diǎn)的所有屬性,并把屬性的名稱和它的值用逗號(hào)分開組合成一個(gè)字符串:
if (reader.HasAttributes)
while(reader.MoveToNextAttribute())
buf += reader.Name + "=\"" + reader.Value + "\",";
reader.MoveToElement();
當(dāng)你完成對屬性集的處理時(shí),調(diào)用MoveToElement方法使指針返回到屬性所屬的元素節(jié)點(diǎn)。準(zhǔn)確的說,MoveToElement方法并不是真正的移動(dòng)指針,因?yàn)樵谔幚韺傩约瘯r(shí)指針從來就沒有從元素節(jié)點(diǎn)中移開。MoveToElement方法只不過指向某個(gè)內(nèi)部成員,并依次取得成員的值。例如,用Name屬性獲得某個(gè)屬性的屬性名,然后調(diào)用MoveToElement方法把指針移到其所屬的元素節(jié)點(diǎn)處。但是當(dāng)你不需要繼續(xù)處理別的節(jié)點(diǎn)時(shí),就不必再調(diào)用MoveToElement方法了。