|
在.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欄目文章。 分析屬性值 大部分情況下,屬性值都是一個(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”的。
?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; } } 驗(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è)異常。
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ù)。這也是高效的。
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("\"/>"); } 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)掉。 Figure 7 States for XML Writer State Attribute Closed Content Element Prolog Start
可以用兩種方法來(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); 圖八中代碼演示了把一個(gè)string數(shù)據(jù)轉(zhuǎn)換為Base64 編碼的XML流。圖九是輸出的結(jié)果。
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ù)。 設(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 AddAttributeChange Read WriteAttributes WriteEndDocument WriteStartDocument
圖十二所示的代碼演示了客戶端用XmlTextReadWriter類在讀操作的同時(shí)修改屬性值的優(yōu)勢(shì)。在本期的msdn中提供了XmlTextReadWriter類的C#和VB源代碼下載(見(jiàn)本文開(kāi)頭提供的鏈接)。 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) |
|
|
來(lái)自: 遙遠(yuǎn)的橋zz > 《數(shù)據(jù)處理》