小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

在.NET Framework中輕松處理XML數(shù)據(jù)

 遙遠(yuǎn)的橋zz 2011-04-11
在.NET Framework中,XmlTextReader和XmlTextWriter類提供了對(duì)xml數(shù)據(jù)的讀和寫(xiě)操作。在本文中,作者講述了XML閱讀器(Reader)的體系結(jié)構(gòu)及它們?cè)鯓优cXMLDOM 和SAX 解釋器結(jié)合。作者也演示了怎么樣運(yùn)用閱讀器分析和驗(yàn)證XML文檔,怎么樣創(chuàng)建格式良好的XML文檔,以及怎么樣用函數(shù)讀/寫(xiě)基于Base64和BinHex編碼的大型的XML文檔。最后,作者講了怎么樣實(shí)現(xiàn)一個(gè)基于流的讀/寫(xiě)分析器,它把讀寫(xiě)器都封裝在一個(gè)單獨(dú)的類里。

大概三年前,我參加了一個(gè)軟件研討會(huì),主題是“沒(méi)有XML,就沒(méi)有編程的未來(lái)”。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的類庫(kù)---寫(xiě)windows的XML的驅(qū)動(dòng)程序。不像. NET Framework中的類,MSXML類庫(kù)的部分代碼比API更深,它完全的嵌在操作系統(tǒng)的底層。MSXML的確能夠與你的應(yīng)用程序通信,但是它不能真正的與外部環(huán)境結(jié)合。

MSXML類庫(kù)能在win32中被導(dǎo)入,也能在CLR中運(yùn)用,但它只能作為一個(gè)外部服務(wù)器組件使用。但是基于.NET Framework的應(yīng)用程序能直接的用XML類與.NET Framework 的其它命名空間整合使用,并且寫(xiě)出來(lái)的代碼易于閱讀。

作為一個(gè)獨(dú)立的組件,MSXML分析器提供了一些高級(jí)的特性如異步分析。這個(gè)特性在.NET Framework中的XML類及.NET Framework的其它類都沒(méi)有提供,但是,NET Framework中的XML類與其它的類整合可以很輕易的獲得相同的功能,在這個(gè)基礎(chǔ)上你可以增加更多的功能。

.NET Framework中的XML類提供了基本的分析、查詢、轉(zhuǎn)換XML數(shù)據(jù)的功能。在.NET Framework中,你可以找到支持Xpath查詢和XSLT轉(zhuǎn)換的類,及讀/寫(xiě)XML文檔的類。另外,.NET Framework也包含了其它處理XML的類,例如對(duì)象的序列化(XmlSerializer和the SoapFormatter類),應(yīng)用程序配置(AppSettingsReader類),數(shù)據(jù)存儲(chǔ)(DataSet類)。在本文中,我只討論實(shí)現(xiàn)基本XML I/O操作的類。

XML分析模式

既然XML是一種標(biāo)記語(yǔ)言,就應(yīng)該有一種工具按一定的語(yǔ)法來(lái)分析和理解存儲(chǔ)在文檔中信息。這個(gè)工具就是XML分析器---一個(gè)組件用于讀標(biāo)記文本并返回指定平臺(tái)的對(duì)象。

所有的XML分析器,不管它屬于哪個(gè)操作平臺(tái),不外乎都分以下的兩類:基于樹(shù)或者基于事件的處理器。這兩類通常都是用XMLDOM(the Microsoft XML Document Object Model)和SAX(Simple API for XML)來(lái)實(shí)現(xiàn)。XMLDOM分析器是一個(gè)普通的基于樹(shù)的API---它把XML文檔當(dāng)成一個(gè)內(nèi)存結(jié)構(gòu)樹(shù)呈現(xiàn)。SAX分析器是基于事件的API----它處理每個(gè)在XML數(shù)據(jù)流中的元素(它把XML數(shù)據(jù)放進(jìn)流中再進(jìn)行處理)。通常,DOM能被一個(gè)SAX流載入并執(zhí)行,因此,這兩類的處理不是相互排斥的。

總的來(lái)說(shuō),SAX分析器與XMLDOM分析器正好相反,它們的分析模式存在著極大的差別。XMLDOM被很好的定義在它的functionalition集合里面,你不能擴(kuò)展它。當(dāng)它在處理一個(gè)大型的文檔時(shí),它要占用很大內(nèi)存空間來(lái)處理functionalition這個(gè)巨大的集合。

SAX分析器利用客戶端應(yīng)用程序通過(guò)現(xiàn)存的指定平臺(tái)的對(duì)象的實(shí)例去處理分析事件。SAX分析器控制整個(gè)處理過(guò)程,把數(shù)據(jù)“推出”到處理程序,該處理程序依次接受或拒絕處理數(shù)據(jù)。這種模式的優(yōu)點(diǎn)是只需很少的內(nèi)存空間。

.NET Framework完全支持XMLDOM模式,但它不支持SAX模式。為什么呢?因?yàn)?NET Framework支持兩種不同的分析模式:XMLDOM分析器和XML閱讀器。它顯然不支持SAX分析器,但這并不意味它沒(méi)有提供類似SAX分析器的功能。通過(guò)XML閱讀器SAX的所有的功能都能很容易的實(shí)現(xiàn)及更有效的運(yùn)用。不像SAX分析器,.NET Framework的閱讀器整個(gè)都運(yùn)作在客戶端應(yīng)用程序下面。這樣,應(yīng)用程序本身就可以只把真正需要的數(shù)據(jù)“推出”,然后從XML數(shù)據(jù)流中跳出來(lái)。而SAX分析模式要處理所有的對(duì)應(yīng)用程序有用和無(wú)用的信息。

閱讀器是基于.NET Framework流模式工作的,它的工作方式類似于數(shù)據(jù)庫(kù)的游標(biāo)。有趣的是,實(shí)現(xiàn)類似游標(biāo)分析模式的類提供對(duì).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中讀文檔的介紹可以參見(jiàn)MSDN 2002 年八月刊的Cutting Edge欄目文章。

[下一頁(yè)]

分析屬性值

大部分情況下,屬性值都是一個(gè)簡(jiǎn)單的文本字符串。然而,這并不意味著實(shí)際應(yīng)用中的屬性值都是字符型的。有時(shí)候,屬性值是由許多種類型的數(shù)據(jù)組合而成的,例如Date或Boolean,這時(shí),你就要用XmlConvert或System.Convevt類的方法把這些類型轉(zhuǎn)換成原來(lái)的類型。XmlConvert和System.Convevt類都能實(shí)現(xiàn)數(shù)據(jù)類型的轉(zhuǎn)換,但是XmlConvert類依據(jù)XSD中指定的數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換,而不管它現(xiàn)在是什么類型。

假設(shè)你有以下的XML數(shù)據(jù)片斷:



讓我們先確認(rèn),birthdaay屬性值是February 8, 2001,如果你用System.Convert類把該字符串轉(zhuǎn)換成.NET Framework中的DateTime類型,這樣,我們就可以把它當(dāng)成date類型使用了。相比下,如果你用XmlConvert類來(lái)轉(zhuǎn)換字符串,你將看到一個(gè)分析錯(cuò)誤,因?yàn)閄mlConvert類不能正確解釋這個(gè)字符串中的日期。因?yàn)樵赬ML中,日期型數(shù)據(jù)的格式必須是YYYY-MM-DD形式的。XmlConvert類擔(dān)任CLR類型與XSD類型之間的相互轉(zhuǎn)換工作。當(dāng)轉(zhuǎn)換工作發(fā)生時(shí),轉(zhuǎn)換結(jié)果是局部的。

在某些解決方案中,屬性值是由純文本和實(shí)體共同組成的。在所有的閱讀器類中,只有XmlValidatingReader類能處理實(shí)體。XmlTextReader雖然不能處理實(shí)體,但它們同時(shí)出現(xiàn)在屬性值中的時(shí)候,它只能把文本值取出來(lái)。出現(xiàn)這種情況,你必須用ReadAttributeValue方法替代簡(jiǎn)單的讀方法來(lái)分析屬性值的內(nèi)容。

ReadAttributeValue方法分析屬性值,然后把各個(gè)組成的要素分隔開(kāi)(如把純文本和實(shí)體分開(kāi))。你可以用ReadAttributeValue方法的返回值作為循環(huán)條件,遍歷整個(gè)屬性值中的要素。既然XmlTextReader類不能處理實(shí)體,那么你可以自己寫(xiě)一個(gè)用于處理實(shí)體的類。下面的代碼片斷演示了怎么調(diào)用一個(gè)自定義的處理類:

while(reader.ReadAttributeValue())

{

if (reader.NodeType == XmlNodeType.EntityReference)

// Resolve the "reader.Name" reference and add

// the result to a buffer

buf += YourResolverCode(reader.Name);

else

// Just append the value to the buffer

buf += reader.Value;

}

當(dāng)屬性值全部被分析后,ReadAtributeValue方法返回False, 從而結(jié)束循環(huán)。屬性值的最終結(jié)果就是全局變量buffer的值了。

處理XML文本(Text)

當(dāng)我們?cè)谔幚鞽ML標(biāo)簽文本時(shí),如果不能正確的處理,它的錯(cuò)誤原因能很快地確定。例如一個(gè)字符轉(zhuǎn)換錯(cuò)誤,它必然是傳輸了非XML文本到一個(gè)XML數(shù)據(jù)流中。不是所有在給定的平臺(tái)中有效的字符都是有效的XML字符。只有在XML規(guī)范(www./TR/2000/REC-xml-20001006.html)中規(guī)定的有效的字符才能安全的用作元素和屬性名。

XmlConvert類提供了把非XML標(biāo)準(zhǔn)的命名轉(zhuǎn)換成標(biāo)準(zhǔn)的XML命名的功能。當(dāng)標(biāo)簽名中包含有無(wú)效的XML字符時(shí),EncodeName 和 DecodeName方法能把它們調(diào)整成符合Schema的XML命名。包括SQL Server™ 和Microsoft Office,這些應(yīng)用程序允許及支持Unicode文檔,然而,這些文檔中的字符有些也不是有效的XML命名。典型的情況是在你處理數(shù)據(jù)庫(kù)中包含空格的列名時(shí)。雖然SQL Server允許長(zhǎng)列名,但這對(duì)XML流來(lái)說(shuō)可能就不是有效的命名??崭駮?huì)被十六進(jìn)制代碼Invoice_0x0020_Details替代。下面的代碼演示了怎么樣在程序中獲得該字符串:

XmlConvert.EncodeName("Invoice Details");

與此相反的方法是DecodeName。該方法把XML文本轉(zhuǎn)換成其原始的格式。要注意的是它只能轉(zhuǎn)換完整的十六進(jìn)制代碼,只有_0x0020_才被當(dāng)成一個(gè)空格,而_0x20_就不是了:

XmlConvert.DecodeName("Invoice_0x0020_Details");

在XML文檔中的空格即重要也不重要。說(shuō)它重要,是當(dāng)它出現(xiàn)在元素的內(nèi)容中或者它在注釋語(yǔ)句中時(shí),它能表示實(shí)際意義。例如下面的情況:

 

<MyNode xml:space="preserve">

<!-- any space here must be preserved -->

•••

</MyNode>

在xml中,空格不只是代表空格(空白),也代表回車(chē)、換行和縮進(jìn)。

通過(guò)XmlTextReader類的WhiteSpaceHandling屬性你可以處理空格。這個(gè)屬性接受及返回一個(gè)WhiteSpaceHandling枚舉值(該枚舉類有三種可選值)。默認(rèn)值是All,它表示有意義和無(wú)意義的空格都會(huì)作為節(jié)點(diǎn)返回---- 分別為SignificantWhitespace和Whitespace節(jié)點(diǎn)。 另一個(gè)枚舉值是None,它表示對(duì)任何空格都不作為節(jié)點(diǎn)返回。最后,就是Signficant枚舉值,它表示忽略沒(méi)有意義的空格,而只返回節(jié)點(diǎn)類型為SignficantWhitespace的節(jié)點(diǎn)。注意WhiteSpaceHandling屬性是少數(shù)閱讀器屬性中的一個(gè)。它能被改變?cè)谌魏螘r(shí)候和給Read操作帶來(lái)影響。而Normalization及 XmlResolver屬性是“Sensitive”的。

[下一頁(yè)]


 

?zhēng)?yàn)證的閱讀器

XmlValidatingReader類實(shí)現(xiàn)了XmlReader類,它提供了支持多種類型的XML驗(yàn)證:DTD,XML-Data Reduced(XDR)架構(gòu),以及XSD,DTD和XSD都是W3C官方推薦的。而XDR是Microsoft早期用于處理XML構(gòu)架的一種格式。

你可以用XmlVlidatingReader類去驗(yàn)證XML文檔和XML片斷。XmlValidatingReader類工作在XML閱讀器上面---是一個(gè)典型的XMLTextReader類實(shí)例。XMLTextReade用于讀取文檔的節(jié)點(diǎn),但是XmlVlidatingReader依據(jù)需要的驗(yàn)證類型去驗(yàn)證每一個(gè)XML塊。

XmlVlidatingReader類只實(shí)現(xiàn)了非常小的XML閱讀器必備的一個(gè)功能子集。該類總是工作在一個(gè)已存在的XML閱讀器上面,它監(jiān)視方法和屬性。如果你深入該類的構(gòu)造函數(shù),你會(huì)發(fā)現(xiàn)它很明顯的依靠一個(gè)已存在的文本閱讀器。帶驗(yàn)證的XML閱讀器不能直接的從一個(gè)文件或一個(gè)URL序列化。該類的構(gòu)造函數(shù)列表如下:

public XmlValidatingReader(XmlReader);

public XmlValidatingReader(Stream, XmlNodeType, XmlParserContext);

public XmlValidatingReader(string, XmlNodeType, XmlParserContext);

?zhēng)?yàn)證的XML閱讀器能分析任何的XML片斷,XML片斷通過(guò)一個(gè)string或者一個(gè)stream提供,也可以分析任何閱讀器提供的XML文檔。

XmlVlidatingReader類中有重大改變的方法非常少(相對(duì)其它reader類來(lái)說(shuō)),另外對(duì) Read,它有Skip和ReadTypedValue方法。Skip方法跳過(guò)當(dāng)前節(jié)點(diǎn)所有的子節(jié)點(diǎn)(你不能跳過(guò)不良格式的XML文本,它是相當(dāng)有用的算法),Skip方法也驗(yàn)證被跳過(guò)的內(nèi)容。ReadTypedValue方法返回指定 XML 架構(gòu) (XSD) 類型對(duì)應(yīng)的CLR類型。如果該方法找到了XSD類型對(duì)應(yīng)的CLR類型,則返回CLR的類型名。如果找不到,則把該節(jié)點(diǎn)的值作為一個(gè)字符串值返回。

?zhēng)?yàn)證的XML閱讀器正如其名,它是一個(gè)基于節(jié)點(diǎn)的閱讀器,它驗(yàn)證當(dāng)前節(jié)點(diǎn)的結(jié)構(gòu)是否符合當(dāng)前的schema。驗(yàn)證是增量式的;它沒(méi)有方法返回表示文檔是否有效的布爾值。通常你都是用Read方法去讀輸入的XML文檔。實(shí)際上,你也可以用帶驗(yàn)證的閱讀器去讀XML文檔。在每一步中,當(dāng)前被訪問(wèn)的節(jié)點(diǎn)的結(jié)構(gòu)是否與指定的schema符合,如果不符合,拋出一個(gè)異常。圖四是一個(gè)控制臺(tái)應(yīng)用程序,它有一個(gè)要輸入文件名的命令行,最后輸出驗(yàn)證結(jié)果。

Figure 4 Console App

using System;

using System.Xml;

using System.Xml.Schema;

 

class MyXmlValidApp

{

public MyXmlValidApp(String fileName)

{

try {

Validate(fileName);

}

catch (Exception e) {

Console.WriteLine("Error:\t{0}", e.Message);

Console.WriteLine("Exception raised: {0}",

e.GetType().ToString());

}

}

 

private void Validate(String fileName)

{

XmlTextReader xtr = new XmlTextReader(fileName);

XmlValidatingReader vreader = new XmlValidatingReader(xtr);

vreader.ValidationType = ValidationType.Auto;

vreader.ValidationEventHandler += new

ValidationEventHandler(this.ValidationEventHandle);

 

vreader.Read();

vreader.MoveToContent();

 

while (vreader.Read()) {}

 

xtr.Close();

vreader.Close();

}

 

public void ValidationEventHandle(Object sender,

ValidationEventArgs args)

{

Console.Write("Validation error: " + args.Message + "\r\n");

}

 

public static void Main(String[] args)

{

MyXmlValidApp o = new MyXmlValidApp(args[0]);

return;

}

}
ValidationType屬性設(shè)置驗(yàn)證的類型,它可以是:DTD, XSD, XDR或者none。如果沒(méi)有指定驗(yàn)證的類型(用ValidationType.Auto選項(xiàng)),閱讀器將自動(dòng)的根據(jù)文檔用最適合的驗(yàn)證類型。在驗(yàn)證過(guò)程中出現(xiàn)任何錯(cuò)誤,都會(huì)觸發(fā)ValidationEventHandler事件。如果未提供事件ValidationEventHandler事件處理程序,則拋出一個(gè)XML異常。定義ValidationEventHandler事件處理程序是用于捕捉任何在XML源文件中存在錯(cuò)誤而引發(fā)XML異常的一種方法。要注意的是閱讀器的原理是檢查一個(gè)文檔是否是格式良好的,以及檢查文檔是否與架構(gòu)吻合。如果帶驗(yàn)證的閱讀器發(fā)現(xiàn)一個(gè)有嚴(yán)重的格式錯(cuò)誤的XML文檔,只會(huì)觸發(fā)XmlException異常,它不會(huì)觸發(fā)其它的事件。

驗(yàn)證發(fā)生在用戶用Read方法向前移動(dòng)指針時(shí),一旦節(jié)點(diǎn)被分析和讀取,它獲得傳送過(guò)來(lái)的處理驗(yàn)證的內(nèi)部的對(duì)象。驗(yàn)證操作是基于節(jié)點(diǎn)類型及被要求的驗(yàn)證類型。它確認(rèn)節(jié)點(diǎn)所有的屬性和節(jié)點(diǎn)包含的子節(jié)點(diǎn)是否符合驗(yàn)證條件。

驗(yàn)證對(duì)象在內(nèi)部調(diào)用兩個(gè)不同風(fēng)格的對(duì)象:DTD分析器和架構(gòu)生成器(schema builder)。DTD分析器處理當(dāng)前節(jié)點(diǎn)的內(nèi)容和不符合DTD的子樹(shù)。架構(gòu)生成器根據(jù)XDR或者XSD架構(gòu)對(duì)當(dāng)前的節(jié)點(diǎn)構(gòu)建一個(gè)SOM(schema object model)。架構(gòu)生成器類實(shí)際上是所有指定為XDR和XSD架構(gòu)生成器的基類。為什么呢,雖然XDR和XSD架構(gòu)的許多相同的方法被加工處理過(guò),但是它們?cè)趫?zhí)行時(shí)的性能沒(méi)有區(qū)別。

如果節(jié)點(diǎn)有子節(jié)點(diǎn),用另一個(gè)臨時(shí)的閱讀器收集子節(jié)點(diǎn)信息,因此節(jié)點(diǎn)的架構(gòu)信息能被完全地驗(yàn)證。你可以看圖五:

注意,盡管XmlValidatingReader類的構(gòu)造函數(shù)可以接受一個(gè)XmlReader類作為其閱讀器,但是該閱讀器只能是XmlTextReader類的一個(gè)實(shí)例或者是它的一個(gè)派生類的實(shí)例。這意味著你不能用其它從XmlReader派生的類(例如一個(gè)自定義的XML閱讀器)。在XmlValidatingReader類的內(nèi)部,它假設(shè)閱讀器是一個(gè)子X(jué)mlTextReader對(duì)象及把傳入的閱讀器顯式的轉(zhuǎn)換成XmlTextReader類。如果你用XmlNodeReader或者自定義的閱讀器器,程序在編譯時(shí)會(huì)出錯(cuò),運(yùn)行時(shí)拋出一個(gè)異常。


節(jié)點(diǎn)閱讀器

XML閱讀器提供一種增量式的方法(一個(gè)一個(gè)節(jié)點(diǎn)的讀)來(lái)處理文檔的內(nèi)容。到目前為止,我們假設(shè)源文件是一個(gè)基于硬盤(pán)的流或者是一個(gè)字符串流,然而,我們不能保證在實(shí)際中會(huì)提供一個(gè)源文件的XMLDOM對(duì)象給我們。在這種情況下,我們需要一個(gè)帶有特別的讀方法的特別的類。對(duì)這種情況,.NET Framework提供了XmlNodeReader類。

就像XmlTextReader訪問(wèn)指定XML流中所有節(jié)點(diǎn)一樣,XmlNodeReader類訪問(wèn)XMLDOM子樹(shù)的所有節(jié)點(diǎn)。XMLDOM類(在.NET Framework中的XmlDocument類)支持基于Xpath的方法,例如SelectNodes方法和SelectSingleNode方法。這些方法的作用是把匹配的節(jié)點(diǎn)放在內(nèi)存中。如果你需要處理子樹(shù)中的所有節(jié)點(diǎn),節(jié)點(diǎn)閱讀器比用增量式方法處理節(jié)點(diǎn)的閱讀器具有更高的效率:

// xmldomNode is the XML DOM node

XmlNodeReader nodeReader = new XmlNodeReader(xmldomNode);

while (nodeReader.Read())

{

// Do something here

}

當(dāng)你要在配置文件(例如web.cofig文件)中引用自定義的數(shù)據(jù)時(shí),先把這些數(shù)據(jù)填充到XMLDOM樹(shù)中,然后用XmlNodeReader類與XMLDOM類結(jié)合處理這些數(shù)據(jù)。這也是高效的。

[下一頁(yè)]


 

XmlTextWriter類

用在本節(jié)中的方法創(chuàng)建XML文檔顯然并不困難。多年以來(lái),開(kāi)發(fā)者都是通過(guò)在緩存在連接一些字符串,連接好以后再把緩存中字符串輸出到文件的方式來(lái)創(chuàng)建XML文檔。但是以這種方式創(chuàng)建XML文檔的方法只有在你保證字符串中不存在任何細(xì)小的錯(cuò)誤的時(shí)候才有效。.NET Framework通過(guò)用XMLwriter提供了更好的創(chuàng)建XML文檔的方法。

XML Writer類以只前(forward-only)的方式輸出XML數(shù)據(jù)到流或者文件中。更重要的是,XML Writer在設(shè)計(jì)時(shí)就保證所有的XML數(shù)據(jù)都符合W3C XML 1.0推薦規(guī)范,你甚至不用擔(dān)心忘記寫(xiě)閉標(biāo)簽,因?yàn)閄ML Writer會(huì)幫你寫(xiě)。XmlWriter是所有 XML writer的抽象基類。.NET Framework只提供唯一的一個(gè)writer 類----XmlTextWriter類。

我們先來(lái)看看XML writers和舊的writers的不同點(diǎn),下面的代碼保存了一個(gè)string型的數(shù)組:

StringBuilder sb = new StringBuilder("");

sb.Append("");

foreach(string s in theArray) {

sb.Append("
sb.Append(s);

sb.Append("\"/>");

}

sb.Append("");

代碼通過(guò)循環(huán)取出數(shù)據(jù)中的元素,寫(xiě)好標(biāo)簽文本并把它們累加到一個(gè)string中。代碼保證輸出的內(nèi)容是格式良好的并且注意了新行的縮進(jìn),及支持命名空間。當(dāng)創(chuàng)建的文檔結(jié)構(gòu)比較簡(jiǎn)單時(shí),這種方法可能不會(huì)有錯(cuò)誤。然而,當(dāng)你要支持處理指令,命名空間,縮進(jìn),格式化以及實(shí)體的時(shí)候,代碼的數(shù)量就成指數(shù)級(jí)增長(zhǎng),出錯(cuò)的可能性也隨之增長(zhǎng)。

XML writer寫(xiě)方法功能對(duì)應(yīng)每個(gè)可能的XML節(jié)點(diǎn)類型,它使創(chuàng)建xml文檔的過(guò)程更符合邏輯、更少的信賴于繁瑣的標(biāo)記語(yǔ)言。圖六演示了怎么樣用XmlTextWriter類的方法來(lái)連接一個(gè)string數(shù)據(jù)。代碼很簡(jiǎn)潔,用XML writer的代碼更容易讀、結(jié)構(gòu)更好。

Figure 6 Serializing a String Array

void CreateXmlFileUsingWriters(String[] theArray, string filename)

{

// Open the XML writer (用默認(rèn)的字符集)

XmlTextWriter xmlw = new XmlTextWriter(filename, null);

xmlw.Formatting = Formatting.Indented;

 

xmlw.WriteStartDocument();

xmlw.WriteStartElement("array");

foreach(string s in theArray)

{

xmlw.WriteStartElement("element");

xmlw.WriteAttributeString("value", s);

xmlw.WriteEndElement();

}

xmlw.WriteEndDocument();

 

// Close the writer

xmlw.Close();

}

然而XML writer并不是魔術(shù)師----它不能修復(fù)輸入的錯(cuò)誤。XML writer不會(huì)檢查元素名和屬性名是否有效,也不保證被用的任何的Unicode字符集適合當(dāng)前架構(gòu)的編碼集。如上所述,為了避免輸出錯(cuò)誤,必須要杜絕非XML字符。但是writer沒(méi)有提供這種方法。

另外,當(dāng)創(chuàng)建一個(gè)屬性節(jié)點(diǎn)時(shí),Writer不會(huì)檢驗(yàn)屬性節(jié)點(diǎn)的名稱是否與已存在的元素節(jié)點(diǎn)的名稱相同。最后,XmlWriter類不是一個(gè)帶驗(yàn)證的Writer類,也不保證輸出是否符合schema或者DTD。在.NET Framework中帶驗(yàn)證的writer類目前來(lái)說(shuō)還沒(méi)有提供。但是在我寫(xiě)的《Applied XML Programming for Microsoft .NET (Microsoft Press®, 2002)》書(shū)中,我自己寫(xiě)了一個(gè)帶驗(yàn)證的Writer組件。你可以到下面的網(wǎng)址去下載源碼:http://www.microsoft.com/MSPress/books/6235.asp.

圖七列出了XML writer的一些狀態(tài)值(state)。這些值都源于WriteState枚舉類。當(dāng)你創(chuàng)建一個(gè)Writer,它的初始狀態(tài)為Start,表示你將要配置該對(duì)象,實(shí)際上writer沒(méi)有開(kāi)始。下一個(gè)狀態(tài)是Prolog,該狀態(tài)是當(dāng)你調(diào)用WriteStartDocument方法開(kāi)始工作的時(shí)候設(shè)置的。然后,狀態(tài)的轉(zhuǎn)換就取決于你的寫(xiě)的文檔及文檔的內(nèi)容了。Prolog狀態(tài)一直保留到當(dāng)你增加一個(gè)非元素節(jié)點(diǎn)時(shí),例如注釋元素,處理指令及文檔類型。當(dāng)?shù)谝粋€(gè)節(jié)點(diǎn)也就是根節(jié)點(diǎn)寫(xiě)完后,狀態(tài)就變?yōu)镋lement。當(dāng)你調(diào)用WriterStartAtribute方法時(shí)狀態(tài)轉(zhuǎn)換為Attribute,而不是當(dāng)你調(diào)用WriteAtributeString方法寫(xiě)屬性時(shí)轉(zhuǎn)換為該狀態(tài)。如果那樣的話,狀態(tài)應(yīng)該是Element。當(dāng)你寫(xiě)一個(gè)閉標(biāo)簽(>)時(shí),狀態(tài)會(huì)轉(zhuǎn)換成Content。當(dāng)你寫(xiě)完文檔后,調(diào)用WriteEndDocument方法,狀態(tài)就會(huì)返回為Start,直到你開(kāi)始寫(xiě)另一個(gè)文檔或者把Writer關(guān)掉。

[下一頁(yè)]

Figure 7 States for XML Writer

State
Description

Attribute
The writer enters this state when an attribute is being written

Closed
The Close method has been called and the writer is no longer available for writing operations

Content
The writer enters this state when the content of a node is being written

Element
The writer enters this state when an element start tag is being written

Prolog
The writer is writing the prolog of a well-formed XML 1.0 document

Start
The writer is in an initial state, awaiting for a write call to be issued


Writer 把輸出文本存在內(nèi)部的一個(gè)緩沖區(qū)內(nèi)。一般情況下,緩沖區(qū)會(huì)被刷新或者被清除,當(dāng)Writer被關(guān)閉前XML文本應(yīng)該要寫(xiě)出。在任何時(shí)你都可以通過(guò)調(diào)用Flush方法清空緩沖區(qū),把當(dāng)前的內(nèi)容寫(xiě)到流中(通過(guò)BaseStream屬性暴露流),然后釋放部分占用的內(nèi)存,Writer仍保持為打開(kāi)狀態(tài)(open state),可以繼續(xù)操作。注意,雖然寫(xiě)了部分的文檔內(nèi)容,但是在Writer沒(méi)有關(guān)閉前其它的程序是不能處理該文檔的。

可以用兩種方法來(lái)寫(xiě)屬性節(jié)點(diǎn)。第一種方法是用WriteStartAtribute方法去創(chuàng)建一個(gè)新的屬性節(jié)點(diǎn),更新Writer的狀態(tài)。接著用WriteString方法設(shè)置屬性值。寫(xiě)完后,用WriteEndElement方法結(jié)束該節(jié)點(diǎn)。另外,你也可以用WriteAttributeString方法去創(chuàng)建新的屬性節(jié)點(diǎn),當(dāng)writerr的狀態(tài)為Element時(shí),WriterAttributeString開(kāi)始工作,它單獨(dú)創(chuàng)建一個(gè)屬性。同樣的,WriteStartElement方法寫(xiě)節(jié)點(diǎn)的開(kāi)始標(biāo)簽(<),然后你可以隨意的設(shè)置節(jié)點(diǎn)的屬性和文本內(nèi)容。元素節(jié)點(diǎn)的閉標(biāo)簽都帶”/ >”。如果想寫(xiě)閉標(biāo)簽可以用WriteFullEndElement方法來(lái)寫(xiě)。

?wèi)?yīng)該避免傳送給寫(xiě)方法的文本中包含敏感的標(biāo)記字符,例如小于號(hào)(<)。用WriteRaw方法寫(xiě)入流的字符串不會(huì)被解析,我們可以用它來(lái)對(duì)xml文檔寫(xiě)入特殊的字符串。下面的兩行代碼,第一行輸出的是”<”,第二行輸出”<”:

writer.WriteString("<");

writer.WriteRaw("<");

讀寫(xiě)流

有趣的是,reader(閱讀器)和writer類提供了基于Base64 和BinHex編碼的讀寫(xiě)數(shù)據(jù)流的方法。WriteBase64 和 WriteBinHex方法的功能與其它的寫(xiě)方法的功能存在著細(xì)微的差別。它們都是基于流的,這兩個(gè)方法的功能像一個(gè)byte數(shù)組而不是一個(gè)string。下面的代碼首先把一個(gè)string轉(zhuǎn)換成一個(gè)byte數(shù)組,然后把它們寫(xiě)成一個(gè)Base64 編碼流。Encoding類的GetBytes靜態(tài)方法完成轉(zhuǎn)換的任務(wù):

writer.WriteBase64(

Encoding.Unicode.GetBytes(buf),

0, buf.Length*2);

[下一頁(yè)]

圖八中代碼演示了把一個(gè)string數(shù)據(jù)轉(zhuǎn)換為Base64 編碼的XML流。圖九是輸出的結(jié)果。


Figure 8 Persisting a String Array as Base64

using System;

using System.Text;

using System.IO;

using System.Xml;

 

class MyBase64Array

{

public static void Main(String[] args)

{

string outputFileName = "test64.xml";

if (args.Length > 0)

outputFileName = args[0]; // file name

 

// 把數(shù)組轉(zhuǎn)換成XML

String[] theArray = {"Rome", "New York", "Sydney", "Stockholm",

"Paris"};

 

CreateOutput(theArray, outputFileName);

return;

}

 

private static void CreateOutput(string[] theArray, string filename)

{

// 打開(kāi)XML writer

XmlTextWriter xmlw = new XmlTextWriter(filename, null);

//使子元素根據(jù) Indentation 和 IndentChar 設(shè)置縮進(jìn)。此選項(xiàng)只對(duì)元素內(nèi)容進(jìn)行縮進(jìn)

xmlw.Formatting = Formatting.Indented;

//書(shū)寫(xiě)版本為“1.0”的 XML 聲明

xmlw.WriteStartDocument();

//寫(xiě)出包含指定文本的注釋 。

xmlw.WriteComment("Array to Base64 XML");

//開(kāi)始寫(xiě)出array節(jié)點(diǎn)

xmlw.WriteStartElement("array");

//寫(xiě)出具有指定的前綴、本地名稱、命名空間 URI 和值的屬性

xmlw.WriteAttributeString("xmlns", "x", null, "dinoe:msdn-mag");

// 循環(huán)的寫(xiě)入array的子節(jié)點(diǎn)

foreach(string s in theArray)

{

//寫(xiě)出指定的開(kāi)始標(biāo)記并將其與給定的命名空間和前綴關(guān)聯(lián)起來(lái)

xmlw.WriteStartElement("x", "element", null);

//把S轉(zhuǎn)換成byte[]數(shù)組, 并把byte[]數(shù)組編碼為 Base64 并寫(xiě)出結(jié)果文本,要寫(xiě)入的字節(jié)數(shù)為s總長(zhǎng)度的2倍,一個(gè)string占的字節(jié)數(shù)是2字節(jié)。

xmlw.WriteBase64(Encoding.Unicode.GetBytes(s), 0, s.Length*2);

//關(guān)閉子節(jié)點(diǎn)

xmlw.WriteEndElement();

}

//關(guān)閉根節(jié)點(diǎn),只有兩級(jí)

xmlw.WriteEndDocument();

 

// 關(guān)閉writer

xmlw.Close();

 

// 讀出寫(xiě)入的內(nèi)容

XmlTextReader reader = new XmlTextReader(filename);

while(reader.Read())

{

//獲取節(jié)點(diǎn)名為element的節(jié)點(diǎn)

if (reader.LocalName == "element")

{

 

byte[] bytes = new byte[1000];

int n = reader.ReadBase64(bytes, 0, 1000);

string buf = Encoding.Unicode.GetString(bytes);

 

Console.WriteLine(buf.Substring(0,n));

}

}

reader.Close();

 

}

}

Figure 9 String Array in Internet Explorer

Reader類有專門(mén)的解釋Base64和BinHex編碼流的方法。下面的代碼片斷演示了怎么樣用XmlTextReader類的ReadBase64方法解析用Base64和BinHex編碼集創(chuàng)建的文檔。

XmlTextReader reader = new XmlTextReader(filename);

while(reader.Read()) {

if (reader.LocalName == "element") {

byte[] bytes = new byte[1000];

int n = reader.ReadBase64(bytes, 0, 1000);

string buf = Encoding.Unicode.GetString(bytes);

Console.WriteLine(buf.Substring(0,n));

}

}

reader.Close();

從byte型轉(zhuǎn)換成string型是通過(guò)Encoding類的GetString方法實(shí)現(xiàn)的。盡管我只介紹了基于Base64編碼集的代碼,但是可以簡(jiǎn)單的用BinHex替換方法名就可以實(shí)現(xiàn)讀基于BinHex編碼的節(jié)點(diǎn)內(nèi)容(用ReadBinHex方法)。這個(gè)技巧也可以用于讀任何用byte數(shù)據(jù)形式表示的二進(jìn)制數(shù)據(jù),尤其是image類型的數(shù)據(jù)。

[下一頁(yè)]

設(shè)計(jì)XmlReadWriter類

如前面所說(shuō),XML reader和Writer是各自獨(dú)立工作的:reader只讀,writer只寫(xiě)。假設(shè)你的應(yīng)用程序要管理冗長(zhǎng)的XML文檔,且該文檔有不確定的數(shù)據(jù)。Reader提供了一個(gè)很好的方法去讀該文檔的內(nèi)容。另一方面,Writer是一個(gè)非常有用的用于創(chuàng)建XML文檔片斷工具,但是如果你想要它即能讀,又能寫(xiě),那么你就要用XMLDOM了。如果實(shí)際的XML文檔非常龐大,又會(huì)出現(xiàn)了一個(gè)問(wèn)題,什么問(wèn)題呢?是不是把這個(gè)XML文檔全部加載到內(nèi)存中,然后進(jìn)行讀和寫(xiě)呢?讓我們先看一下怎么樣建立一個(gè)混合的流分析器用于分析大型的XMLDOM。

像一般的只讀操作一樣,用普通的XML reader去順序的訪問(wèn)節(jié)點(diǎn)。不同的是,在讀的同時(shí)你可以用XML writer改變屬性值以及節(jié)點(diǎn)的內(nèi)容。你用reader去讀源文件中的每個(gè)節(jié)點(diǎn),后臺(tái)的writer創(chuàng)建該節(jié)點(diǎn)的一個(gè)拷貝。在這個(gè)拷貝中,你可以增加一些新的節(jié)點(diǎn),忽略或者編輯其它的一些節(jié)點(diǎn),還可以編輯屬性的值。當(dāng)你完成修改后,你就用新的文檔替換舊的文檔。

一個(gè)簡(jiǎn)單有效的辦法是從只讀流中拷貝節(jié)點(diǎn)對(duì)象到write流中,這種方法可以用XmlTextWriter類中的兩個(gè)方法:WriteAttributes方法和WriteNode方法。 WriteAttributes方法讀取當(dāng)前reader中選中的節(jié)點(diǎn)的所有有效的屬性,然后把屬性當(dāng)作一個(gè)單獨(dú)的string拷貝到當(dāng)前的輸出流中。同樣的,WriteNode方法用類似的方法處理除屬性節(jié)點(diǎn)外的其它類型的節(jié)點(diǎn)。圖十所示的代碼片斷演示了怎么用上述的兩個(gè)方法創(chuàng)建一個(gè)源XML文檔的拷貝,有選擇的修改某些節(jié)點(diǎn)。XML樹(shù)從樹(shù)根開(kāi)始被訪問(wèn),但只輸出了除屬性節(jié)點(diǎn)類型以外的其它類型的節(jié)點(diǎn)。你可以把Reader和Writer整合在一個(gè)新的類中,設(shè)計(jì)一個(gè)新的接口,使它能讀寫(xiě)流及訪問(wèn)屬性和節(jié)點(diǎn)。

Figure 10 Using the WriteNode Method

XmlTextReader reader = new XmlTextReader(inputFile);

XmlTextWriter writer = new XmlTextWriter(outputFile);

 

// 配置 reader 和 writer

writer.Formatting = Formatting.Indented;

reader.MoveToContent();

 

// Write根節(jié)點(diǎn)

writer.WriteStartElement(reader.LocalName);

 

// Read and output every other node

int i=0;

while(reader.Read())

{

if (i % 2)

writer.WriteNode(reader, false);

i++;

}

 

// Close the root

writer.WriteEndElement();

 

// Close reader and writer

writer.Close();

reader.Close();

我的XmlTextReadWriter類并沒(méi)有從XmlReader或者XmlWriter類中繼承。取而代之的是另外兩個(gè)類,一個(gè)是基于只讀流(stream)的操作類,另一個(gè)是基于只寫(xiě)流的操作類。XmlTextReadWriter類的方法用Reader對(duì)象讀數(shù)據(jù),寫(xiě)入到Writer對(duì)象。為了適應(yīng)不同的需求,內(nèi)部的Reader和Writer 對(duì)象分別通過(guò)只讀的Reader和Writer屬性公開(kāi)。圖十一列出了該類的一些方法:

Figure 11 XmlTextReadWriter Class Methods

Method
Description

AddAttributeChange
Caches all the information needed to perform a change on a node attribute. All the changes cached through this method are processed during a successive call to WriteAttributes.

Read
Simple wrapper around the internal reader''s Read method.

WriteAttributes
Specialized version of the writer''s WriteAttributes method, writes out all the attributes for the given node, taking into account all the changes cached through the AddAttributeChange method.

WriteEndDocument
Terminates the current document in the writer and closes both the reader and the writer.

WriteStartDocument
Prepares the internal writer to output the document and add a default comment text and the standard XML prolog.


這個(gè)新類有一個(gè)Read方法,它是對(duì)Reader的read方法的一個(gè)簡(jiǎn)單的封裝。另外,它提供了WriterStartDocument和WriteEndDocument方法。它們分別初始化/釋放(finalize)了內(nèi)部Reader和writer對(duì)象,還處理所有I/O操作。在循環(huán)讀節(jié)點(diǎn)的同時(shí),我們就可以直接的修改節(jié)點(diǎn)。出于性能的原因,要修改屬性必須先用AddAttributeChange方法聲明。對(duì)一個(gè)節(jié)點(diǎn)的屬性所作的所有修改都會(huì)存放在一個(gè)臨時(shí)的表中,最后,通過(guò)調(diào)用WriteAttribute方法提交修改,清除臨時(shí)表。

[下一頁(yè)]

圖十二所示的代碼演示了客戶端用XmlTextReadWriter類在讀操作的同時(shí)修改屬性值的優(yōu)勢(shì)。在本期的msdn中提供了XmlTextReadWriter類的C#和VB源代碼下載(見(jiàn)本文開(kāi)頭提供的鏈接)。
Figure 12 Changing Attribute Values

private void ApplyChanges(string nodeName, string attribName,

string oldVal, string newVal)

{

XmlTextReadWriter rw = new XmlTextReadWriter(InputFileName.Text,

OutputFileName.Text);

rw.WriteStartDocument(true, CommentText.Text);

 

// 手工修改根節(jié)點(diǎn)

rw.Writer.WriteStartElement(rw.Reader.LocalName);

 

// 開(kāi)始修改屬性

// (可以修改更多節(jié)點(diǎn)的屬性)

rw.AddAttributeChange(nodeName, attribName, oldVal, newVal);

 

// 循環(huán)處理文檔

while(rw.Read())

{

switch(rw.NodeType)

{

case XmlNodeType.Element:

rw.Writer.WriteStartElement(rw.Reader.LocalName);

if (nodeName == rw.Reader.LocalName)

// 修改屬性

rw.WriteAttributes(nodeName);

else

// deep copy

rw.Writer.WriteAttributes(rw.Reader, false);

 

if (rw.Reader.IsEmptyElement)

rw.Writer.WriteEndElement();

break;

}

}

 

// Close the root tag

rw.Writer.WriteEndElement();

 

// Close the document and any internal resources

rw.WriteEndDocument();

}

 

XmlTextReadWriter類不僅可以讀XML文檔,也可以寫(xiě)XML文檔。你可以它來(lái)讀XML文檔的內(nèi)容,如果需要,你還可以用它來(lái)做一些基本的更新操作?;镜母虏僮髟谶@里是指修改某個(gè)已存在的屬性的值或者某個(gè)節(jié)點(diǎn)的內(nèi)容,又或者是增加一個(gè)新的屬性或節(jié)點(diǎn)。對(duì)于更復(fù)雜的操作,最好還是用XMLDOM分析器。

總結(jié)

Reader和Writer是.NET Framework中處理XML數(shù)據(jù)的根本。它們提供了對(duì)所有XML數(shù)據(jù)訪問(wèn)功能的原始的API。Reader像一個(gè)新的分析器類,它即有XMLDOM的強(qiáng)大,又有SAX的快速簡(jiǎn)單。Writer為簡(jiǎn)單的創(chuàng)建XML文檔而設(shè)計(jì)。雖然Reader和Writer都是.NET Framework中的一小塊,但是它們是相互獨(dú)立的API。在本文中,我們只討論了怎么樣用Reader和Writer完成一些主要的工作, 介紹了驗(yàn)證分析器的原理機(jī)制,并把Reader和writer整合在一個(gè)單獨(dú)的類中。上述所有的這些類都是輕量級(jí)的,類似于游標(biāo)式的XMLDOM分析器。 (chyich翻譯/ASPCool)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多