熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> ASP編程 >> 正文

詳談.NET Framework處理XML操作技巧

2013-11-15 12:50:58  來源: ASP編程 

  在Net Framework中XMLTextReader和XmlTextWriter類提供了對xml數據的讀和寫操作在本文中作者講述了XML閱讀器(Reader)的體系結構及它們怎樣與XMLDOM和SAX解釋器結合作者也演示了怎麼樣運用閱讀器分析和驗證XML文檔怎麼樣創建格式良好的XML文檔以及怎麼樣用函數讀/寫基於Base和BinHex編碼的大型的XML文檔最後作者講了怎麼樣實現一個基於流的讀/寫分析器它把讀寫器都封裝在一個單獨的類裡

  大概三年前我參加了一個軟件研討會主題是沒有XML就沒有編程的未來XML確實也在一步一步的發展它已經嵌入到NET Framework中了在本文中我將講解NET Framework中用於處理XML文檔的API的角色和它的內部特性然後我將演示一些常用的功能

  從的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方法或其它方法(如SkipMoveToContent和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方法了

  

  分析屬性值

  大部分情況下屬性值都是一個簡單的文本字符串然而這並不意味著實際應用中的屬性值都是字符型的有時候屬性值是由許多種類型的數據組合而成的例如Date或Boolean這時你就要用XMLConvert或SystemConvevt類的方法把這些類型轉換成原來的類型XmlConvert和SystemConvevt類都能實現數據類型的轉換但是XmlConvert類依據XSD中指定的數據類型進行轉換而不管它現在是什麼類型

  假設你有以下的XML數據片斷 

  讓我們先確認birthdaay屬性值是February 如果你用SystemConvert類把該字符串轉換 Framework中的DateTime類型這樣我們就可以把它當成date類型使用了相比下如果你用XmlConvert類來轉換字符串你將看到一個分析錯誤因為XmlConvert類不能正確解釋這個字符串中的日期因為在XML中日期型數據的格式必須是YYYYMMDD形式的XmlConvert類擔任CLR類型與XSD類型之間的相互轉換工作當轉換工作發生時轉換結果是局部的

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

  ReadAttributeValue方法分析屬性值然後把各個組成的要素分隔開(如把純文本和實體分開)你可以用ReadAttributeValue方法的返回值作為循環條件遍歷整個屬性值中的要素既然XmlTextReader類不能處理實體那麼你可以自己寫一個用於處理實體的類下面的代碼片斷演示了怎麼調用一個自定義的處理類

  

  while(readerReadAttributeValue())
{
if (readerNodeType == XmlNodeTypeEntityReference)
// Resolve the readerName reference and add
// the result to a buffer
buf += YourResolverCode(readerName);
else
// Just append the value to the buffer
buf += readerValue;
}

  當屬性值全部被分析後ReadAtributeValue方法返回False 從而結束循環屬性值的最終結果就是全局變量buffer的值了

  處理XML文本(Text)

  當我們在處理XML標簽文本時如果不能正確的處理它的錯誤原因能很快地確定例如一個字符轉換錯誤它必然是傳輸了非XML文本到一個XML數據流中不是所有在給定的平台中有效的字符都是有效的XML字符只有在XML規范(/TR//l)中規定的有效的字符才能安全的用作元素和屬性名

  XmlConvert類提供了把非XML標准的命名轉換成標准的XML命名的功能當標簽名中包含有無效的XML字符時EncodeName 和 DecodeName方法能把它們調整成符合Schema的XML命名包括SQL Server? 和Microsoft Office這些應用程序允許及支持Unicode文檔然而這些文檔中的字符有些也不是有效的XML命名典型的情況是在你處理數據庫中包含空格的列名時雖然SQL Server允許長列名但這對XML流來說可能就不是有效的命名空格會被十六進制代碼Invoice_x_Details替代下面的代碼演示了怎麼樣在程序中獲得該字符串

  

  XmlConvertEncodeName(Invoice Details);

  與此相反的方法是DecodeName該方法把XML文本轉換成其原始的格式要注意的是它只能轉換完整的十六進制代碼只有_x_才被當成一個空格而_x_就不是了

  

  XmlConvertDecodeName(Invoice_x_Details);

  在XML文檔中的空格即重要也不重要說它重要是當它出現在元素的內容中或者它在注釋語句中時它能表示實際意義例如下面的情況

  

  <MyNode XML:space=preserve
<! any space here must be preserved
???
</MyNode>

  在xml中空格不只是代表空格(空白)也代表回車換行和縮進

  通過XmlTextReader類的WhiteSpaceHandling屬性你可以處理空格這個屬性接受及返回一個WhiteSpaceHandling枚舉值(該枚舉類有三種可選值)默認值是All它表示有意義和無意義的空格都會作為節點返回 分別為SignificantWhitespace和Whitespace節點 另一個枚舉值是None它表示對任何空格都不作為節點返回最後就是Signficant枚舉值它表示忽略沒有意義的空格而只返回節點類型為SignficantWhitespace的節點注意WhiteSpaceHandling屬性是少數閱讀器屬性中的一個它能被改變在任何時候和給Read操作帶來影響而Normalization及 XmlResolver屬性是Sensitive

  String和Fragment

  程序員把在MSXML的程序剪切下來會發現在 Framework XML API 之間的差別很大NET Framework類本身沒有提供方法去分析存儲在字符串中XML數據不像MSXML分析器對象XmlTestReader類沒有提供任何一種LoadXML方法從一個格式良好的字符中創建閱讀器沒有提供類似LoadXML的方法因為你可以用特殊的text readerStringReader類來獲得同樣的功能

  XmlTextReader其中一個構造函數接受一個TextReader派生對象和一個XML reader作參數(該閱讀器以text reader的內容為基礎創建)一個text reader類是一個流這個流是輸入的字符經優化生成的StringReader類繼承TextReader類並用一個內存中字符串作為其輸入流下面的代碼片斷演示了怎樣初始化一個XML reader用一個格式良好的XML 字符串作為其輸入

  

  string xmlText = ;
StringReader strReader = new StringReader(xmlText);
XmlTextReader reader = new XmlTextReader(strReader);

  另外用StringWriter類代替TextWrite類你可以從內存字符中創建一個XML文檔

  一個指定類型的XML字符串是一個XML片斷(fragment) XML片斷由XML文本構成但沒有根節點的XML文檔不是格式良好的XML文檔所以不能被應用一個XML片斷是原始的文檔的一部分所以它可能缺少根節點例如下面的XML文本是一個有效的XML 片斷但不是一個有效的XML文檔因為它沒有根節點

  

  Dino
Esposito

  NET Framework XML API允許程序員把XML片斷與一個分析器內容結合使用分析器內容由類似encoding字符集DTD文檔命名空間語言和空格處理程序構成

  

  public XmlTextReader(
string xmlFragment
XmlNodeType fragType
XmlParserContext context
);

            xmlFragment參數包括了XML字符串分析FragType參數表示fragment的類型它給出了fragment根節點的類型只有elementattibute和document類型的節點才能作為fragment的根節點分析器的內容才能被XmlParserContext類解釋

  帶驗證的閱讀器

  XMLValidatingReader類實現了XmlReader類它提供了支持多種類型的XML驗證DTDXMLData Reduced(XDR)架構以及XSDDTD和XSD都是WC官方推薦的而XDR是Microsoft早期用於處理XML構架的一種格式

  你可以用XmlVlidatingReader類去驗證XML文檔和XML片斷XmlValidatingReader類工作在XML閱讀器上面是一個典型的XMLTextReader類實例XMLTextReade用於讀取文檔的節點但是XmlVlidatingReader依據需要的驗證類型去驗證每一個XML塊

  XmlVlidatingReader類只實現了非常小的XML閱讀器必備的一個功能子集該類總是工作在一個已存在的XML閱讀器上面它監視方法和屬性如果你深入該類的構造函數你會發現它很明顯的依靠一個已存在的文本閱讀器帶驗證的XML閱讀器不能直接的從一個文件或一個URL序列化該類的構造函數列表如下

  

  public XmlValidatingReader(XmlReader);
public XmlValidatingReader(Stream XmlNodeType XmlParserContext);
public XmlValidatingReader(string XmlNodeType XmlParserContext);

  帶驗證的XML閱讀器能分析任何的XML片斷XML片斷通過一個string或者一個stream提供也可以分析任何閱讀器提供的XML文檔

  XmlVlidatingReader類中有重大改變的方法非常少(相對其它reader類來說)另外對 Read它有Skip和ReadTypedValue方法Skip方法跳過當前節點所有的子節點(你不能跳過不良格式的XML文本它是相當有用的算法)Skip方法也驗證被跳過的內容ReadTypedValue方法返回指定 XML 架構 (XSD) 類型對應的CLR類型如果該方法找到了XSD類型對應的CLR類型則返回CLR的類型名如果找不到則把該節點的值作為一個字符串值返回

  帶驗證的XML閱讀器正如其名它是一個基於節點的閱讀器它驗證當前節點的結構是否符合當前的schema驗證是增量式的它沒有方法返回表示文檔是否有效的布爾值通常你都是用Read方法去讀輸入的XML文檔實際上你也可以用帶驗證的閱讀器去讀XML文檔在每一步中當前被訪問的節點的結構是否與指定的schema符合如果不符合拋出一個異常圖四是一個控制台應用程序它有一個要輸入文件名的命令行最後輸出驗證結果

  

  Figure Console App

  using System;
using SystemXml;
using SystemXmlSchema;
class MyXmlValidApp
{
public MyXmlValidApp(String fileName)
{
try {
Validate(fileName);
}
catch (Exception e) {
ConsoleWriteLine(Error:\t{} eMessage);
ConsoleWriteLine(Exception raised: {}
eGetType()ToString());
}
}
private void Validate(String fileName)
{
XmlTextReader xtr = new XmlTextReader(fileName);
XmlValidatingReader vreader = new XmlValidatingReader(xtr);
vreaderValidationType = ValidationTypeAuto;
vreaderValidationEventHandler += new
ValidationEventHandler(thisValidationEventHandle);
vreaderRead();
vreaderMoveToContent();
while (vreaderRead()) {}
xtrClose();

  vreaderClose();
}
public void ValidationEventHandle(Object sender
ValidationEventArgs args)
{
ConsoleWrite(Validation error: + argsMessage + \r\n);
}
public static void Main(String[] args)
{
MyXMLValidApp o = new MyXmlValidApp(args[]);
return;
}
}

  ValidationType屬性設置驗證的類型它可以是:DTDXSDXDR或者none如果沒有指定驗證的類型(用ValidationTypeAuto選項)閱讀器將自動的根據文檔用最適合的驗證類型在驗證過程中出現任何錯誤都會觸發ValidationEventHandler事件如果未提供事件ValidationEventHandler事件處理程序則拋出一個XML異常定義ValidationEventHandler事件處理程序是用於捕捉任何在XML源文件中存在錯誤而引發XML異常的一種方法要注意的是閱讀器的原理是檢查一個文檔是否是格式良好的以及檢查文檔是否與架構吻合如果帶驗證的閱讀器發現一個有嚴重的格式錯誤的XML文檔只會觸發XmlException異常它不會觸發其它的事件 

  驗證發生在用戶用Read方法向前移動指針時一旦節點被分析和讀取它獲得傳送過來的處理驗證的內部的對象驗證操作是基於節點類型及被要求的驗證類型它確認節點所有的屬性和節點包含的子節點是否符合驗證條件 

  驗證對象在內部調用兩個不同風格的對象DTD分析器和架構生成器(schema builder)DTD分析器處理當前節點的內容和不符合DTD的子樹架構生成器根據XDR或者XSD架構對當前的節點構建一個SOM(schema object model)架構生成器類實際上是所有指定為XDR和XSD架構生成器的基類為什麼呢雖然XDR和XSD架構的許多相同的方法被加工處理過但是它們在執行時的性能沒有區別 

  如果節點有子節點用另一個臨時的閱讀器收集子節點信息因此節點的架構信息能被完全地驗證你可以看圖五

  在</p>
<p>  <CODE> 
</CODE></p><CODE>
<p>  498)this.width=498;

      注意盡管XmlValidatingReader類的構造函數可以接受一個XmlReader類作為其閱讀器但是該閱讀器只能是XmlTextReader類的一個實例或者是它的一個派生類的實例這意味著你不能用其它從XmlReader派生的類(例如一個自定義的XML閱讀器)在XmlValidatingReader類的內部它假設閱讀器是一個子XmlTextReader對象及把傳入的閱讀器顯式的轉換成XmlTextReader類如果你用XmlNodeReader或者自定義的閱讀器器程序在編譯時會出錯運行時拋出一個異常

  節點閱讀器

  XML閱讀器提供一種增量式的方法(一個一個節點的讀)來處理文檔的內容到目前為止我們假設源文件是一個基於硬盤的流或者是一個字符串流然而我們不能保證在實際中會提供一個源文件的XMLDOM對象給我們在這種情況下我們需要一個帶有特別的讀方法的特別的類對這種情況 Framework提供了XmlNodeReader類

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

  

  // xmldomNode is the XML DOM node
XmlNodeReader nodeReader = new XmlNodeReader(xmldomNode);
while (nodeReaderRead())
{
// Do something here
}

  當你要在配置文件(例如fig文件)中引用自定義的數據時先把這些數據填充到XMLDOM樹中然後用XmlNodeReader類與XMLDOM類結合處理這些數據這也是高效的

  XMLTextWriter類

  用在本節中的方法創建XML文檔顯然並不困難多年以來開發者都是通過在緩存在連接一些字符串連接好以後再把緩存中字符串輸出到文件的方式來創建XML文檔但是以這種方式創建XML文檔的方法只有在你保證字符串中不存在任何細小的錯誤的時候才有效 Framework通過用XMLwriter提供了更好的創建XML文檔的方法

  XML Writer類以只前(forwardonly)的方式輸出XML數據到流或者文件中更重要的是XML Writer在設計時就保證所有的XML數據都符合WC XML 推薦規范你甚至不用擔心忘記寫閉標簽因為XML Writer會幫你寫XmlWriter是所有 XML writer的抽象基類NET Framework只提供唯一的一個writer 類XmlTextWriter類

  我們先來看看XML writers和舊的writers的不同點下面的代碼保存了一個string型的數組

  

  StringBuilder sb = new StringBuilder();
sbAppend();
foreach(string s in theArray) {
sbAppend(
sbAppend(s);
sbAppend(\\\\\\\\/>);
}
sbAppend();

  
代碼通過循環取出數據中的元素寫好標簽文本並把它們累加到一個string中代碼保證輸出的內容是格式良好的並且注意了新行的縮進及支持命名空間當創建的文檔結構比較簡單時這種方法可能不會有錯誤然而當你要支持處理指令命名空間縮進格式化以及實體的時候代碼的數量就成指數級增長出錯的可能性也隨之增長

  XML writer寫方法功能對應每個可能的XML節點類型它使創建xml文檔的過程更符合邏輯更少的信賴於繁瑣的標記語言圖六演示了怎麼樣用XmlTextWriter類的方法來連接一個string數據代碼很簡潔用XML writer的代碼更容易讀結構更好

  

  Figure Serializing a String Array
void CreateXmlFileUsingWriters(String[] theArray string filename)
{
// Open the XML writer (用默認的字符集)
XmlTextWriter xmlw = new XmlTextWriter(filename null);
xmlwFormatting = FormattingIndented;
xmlwWriteStartDocument();
xmlwWriteStartElement(array);
foreach(string s in theArray)
{
xmlwWriteStartElement(element);
xmlwWriteAttributeString(value s);
xmlwWriteEndElement();
}
xmlwWriteEndDocument();
// Close the writer
xmlwClose();
}

  然而XML writer並不是魔術師它不能修復輸入的錯誤XML writer不會檢查元素名和屬性名是否有效也不保證被用的任何的Unicode字符集適合當前架構的編碼集如上所述為了避免輸出錯誤必須要杜絕非XML字符但是writer沒有提供這種方法

  

  另外當創建一個屬性節點時Writer不會檢驗屬性節點的名稱是否與已存在的元素節點的名稱相同最後XmlWriter類不是一個帶驗證的Writer類也不保證輸出是否符合schema或者DTDNET Framework中帶驗證的writer類目前來說還沒有提供但是在我寫的《Applied XML Programming for Microsoft NET (Microsoft Press? )》書中我自己寫了一個帶驗證的Writer組件你可以到下面的網址去下載源碼

  圖七列出了XML writer的一些狀態值(state)這些值都源於WriteState枚舉類當你創建一個Writer它的初始狀態為Start表示你將要配置該對象實際上writer沒有開始下一個狀態是Prolog該狀態是當你調用WriteStartDocument方法開始工作的時候設置的然後狀態的轉換就取決於你的寫的文檔及文檔的內容了Prolog狀態一直保留到當你增加一個非元素節點時例如注釋元素處理指令及文檔類型當第一個節點也就是根節點寫完後狀態就變為Element當你調用WriterStartAtribute方法時狀態轉換為Attribute而不是當你調用WriteAtributeString方法寫屬性時轉換為該狀態如果那樣的話狀態應該是Element當你寫一個閉標簽(>)時狀態會轉換成Content當你寫完文檔後調用WriteEndDocument方法狀態就會返回為Start直到你開始寫另一個文檔或者把Writer關掉

  

  Figure 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 wellformed XML document
Start
The writer is in an initial state awaiting for a write call to be issued

  Writer 把輸出文本存在內部的一個緩沖區內一般情況下緩沖區會被刷新或者被清除當Writer被關閉前XML文本應該要寫出在任何時你都可以通過調用Flush方法清空緩沖區把當前的內容寫到流中(通過BaseStream屬性暴露流)然後釋放部分占用的內存Writer仍保持為打開狀態(open state)可以繼續操作注意雖然寫了部分的文檔內容但是在Writer沒有關閉前其它的程序是不能處理該文檔的

  可以用兩種方法來寫屬性節點第一種方法是用WriteStartAtribute方法去創建一個新的屬性節點更新Writer的狀態接著用WriteString方法設置屬性值寫完後用WriteEndElement方法結束該節點另外你也可以用WriteAttributeString方法去創建新的屬性節點當writerr的狀態為Element時WriterAttributeString開始工作它單獨創建一個屬性同樣的WriteStartElement方法寫節點的開始標簽(<)然後你可以隨意的設置節點的屬性和文本內容元素節點的閉標簽都帶/ >如果想寫閉標簽可以用WriteFullEndElement方法來寫

  應該避免傳送給寫方法的文本中包含敏感的標記字符例如小於號(<)用WriteRaw方法寫入流的字符串不會被解析我們可以用它來對xml文檔寫入特殊的字符串下面的兩行代碼第一行輸出的是<第二行輸出<:

  

  writerWriteString(<);
writerWriteRaw(<);


  讀寫流
有趣的是reader(閱讀器)和writer類提供了基於Base 和BinHex編碼的讀寫數據流的方法WriteBase 和 WriteBinHex方法的功能與其它的寫方法的功能存在著細微的差別它們都是基於流的這兩個方法的功能像一個byte數組而不是一個string下面的代碼首先把一個string轉換成一個byte數組然後把它們寫成一個Base 編碼流Encoding類的GetBytes靜態方法完成轉換的任務

  

  writerWriteBase(
EncodingUnicodeGetBytes(buf)
bufLength*);

  圖八中代碼演示了把一個string數據轉換為Base 編碼的XML流圖九是輸出的結果

  

  Figure Persisting a String Array as Base
using System;
using SystemText;
using SystemIO;
using SystemXml;
class MyBaseArray
{
public static void Main(String[] args)
{
string outputFileName = testxml;
if (argsLength >)
outputFileName = args[]; // file name
// 把數組轉換成XML
String[] theArray = {Rome New York Sydney Stockholm
Paris};
CreateOutput(theArray outputFileName);
return;
}
private static void CreateOutput(string[] theArray string filename)
{
// 打開XML writer
XmlTextWriter xmlw = new XmlTextWriter(filename null);
//使子元素根據 Indentation 和 IndentChar 設置縮進此選項只對元素內容進行縮進
xmlwFormatting = FormattingIndented;
//書寫版本為的 XML 聲明
xmlwWriteStartDocument();
//寫出包含指定文本的注釋
xmlwWriteComment(Array to Base XML);
//開始寫出array節點
xmlwWriteStartElement(array);
//寫出具有指定的前綴本地名稱命名空間 URI 和值的屬性
xmlwWriteAttributeString(xmlns x null dinoe:msdnmag);
// 循環的寫入array的子節點
foreach(string s in theArray)
{
//寫出指定的開始標記並將其與給定的命名空間和前綴關聯起來
xmlwWriteStartElement(x element null);
//把S轉換成byte[]數組 並把byte[]數組編碼為 Base 並寫出結果文本

  要寫入的字節數為s總長度的一個string占的字節數是字節
xmlwWriteBase(EncodingUnicodeGetBytes(s) sLength*);
//關閉子節點
xmlwWriteEndElement();
}
//關閉根節點只有兩級
xmlwWriteEndDocument();
// 關閉writer
xmlwClose();
// 讀出寫入的內容
XmlTextReader reader = new XmlTextReader(filname);
while(readerRead())
{
//獲取節點名為element的節點
if (readerLocalName == element)
{
byte[] bytes = new byte[];
int n = readerReadBase(bytes );
string buf = EncodingUnicodeGetString(bytes);
ConsoleWriteLine(bufSubstring(n));
}
}
readerClose();
}
}

  

  Figure String Array in Internet Explorer

   

  Reader類有專門的解釋Base和BinHex編碼流的方法下面的代碼片斷演示了怎麼樣用XmlTextReader類的ReadBase方法解析用Base和BinHex編碼集創建的文檔

  

  XmlTextReader reader = new XmlTextReader(filename);
while(readerRead()) {
if (readerLocalName == element) {
byte[] bytes = new byte[];
int n = readerReadBase(bytes );
string buf = EncodingUnicodeGetString(bytes);
ConsoleWriteLine(bufSubstring(n));
}
}
readerClose();


從byte型轉換成string型是通過Encoding類的GetString方法實現的盡管我只介紹了基於Base編碼集的代碼但是可以簡單的用BinHex替換方法名就可以實現讀基於BinHex編碼的節點內容(用ReadBinHex方法)這個技巧也可以用於讀任何用byte數據形式表示的二進制數據尤其是image類型的數據

  設計XMLReadWriter類

  如前面所說XML reader和Writer是各自獨立工作的reader只讀writer只寫假設你的應用程序要管理冗長的XML文檔且該文檔有不確定的數據Reader提供了一個很好的方法去讀該文檔的內容另一方面Writer是一個非常有用的用於創建XML文檔片斷工具但是如果你想要它即能讀又能寫那麼你就要用XMLDOM了如果實際的XML文檔非常龐大又會出現了一個問題什麼問題呢?是不是把這個XML文檔全部加載到內存中然後進行讀和寫呢?讓我們先看一下怎麼樣建立一個混合的流分析器用於分析大型的XMLDOM

  像一般的只讀操作一樣用普通的XML reader去順序的訪問節點不同的是在讀的同時你可以用XML writer改變屬性值以及節點的內容你用reader去讀源文件中的每個節點後台的writer創建該節點的一個拷貝在這個拷貝中你可以增加一些新的節點忽略或者編輯其它的一些節點還可以編輯屬性的值當你完成修改後你就用新的文檔替換舊的文檔

  一個簡單有效的辦法是從只讀流中拷貝節點對象到write流中這種方法可以用XmlTextWriter類中的兩個方法WriteAttributes方法和WriteNode方法 WriteAttributes方法讀取當前reader中選中的節點的所有有效的屬性然後把屬性當作一個單獨的string拷貝到當前的輸出流中同樣的WriteNode方法用類似的方法處理除屬性節點外的其它類型的節點圖十所示的代碼片斷演示了怎麼用上述的兩個方法創建一個源XML文檔的拷貝有選擇的修改某些節點XML樹從樹根開始被訪問但只輸出了除屬性節點類型以外的其它類型的節點你可以把Reader和Writer整合在一個新的類中設計一個新的接口使它能讀寫流及訪問屬性和節點

  

  Figure Using the WriteNode Method
XmlTextReader reader = new XmlTextReader(inputFile);
XmlTextWriter writer = new XmlTextWriter(outputFile);
// 配置 reader 和 writer
writerFormatting = FormattingIndented;
readerMoveToContent();
// Write根節點
writerWriteStartElement(readerLocalName);
// Read and output every other node
int i=;
while(readerRead())
{
if (i % )
writerWriteNode(reader false);
i++;
}
// Close the root
writerWriteEndElement();
// Close reader and writer
writerClose();
readerClose();

  我的XmlTextReadWriter類並沒有從XmlReader或者XmlWriter類中繼承取而代之的是另外兩個類一個是基於只讀流(stream)的操作類另一個是基於只寫流的操作類XmlTextReadWriter類的方法用Reader對象讀數據寫入到Writer對象為了適應不同的需求內部的Reader和Writer 對象分別通過只讀的Reader和Writer屬性公開圖十一列出了該類的一些方法

  

  Figure 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 readers Read method
WriteAttributes
Specialized version of the writers 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

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

  圖十二所示的代碼演示了客戶端用XmlTextReadWriter類在讀操作的同時修改屬性值的優勢在本期的msdn中提供了XmlTextReadWriter類的C#和VB源代碼下載(見本文開頭提供的鏈接)

  

  Figure Changing Attribute Values
private void ApplyChanges(string nodeName string attribName
string oldVal string newVal)
{
XmlTextReadWriter rw = new XmlTextReadWriter(InputFileNameText
OutputFileNameText);
rwWriteStartDocument(true CommentTextText);
// 手工修改根節點
rwWriterWriteStartElement(rwReaderLocalName);
// 開始修改屬性
// (可以修改更多節點的屬性)
rwAddAttributeChange(nodeName attribName oldVal newVal);
// 循環處理文檔
while(rwRead())
{
switch(rwNodeType)
{
case XmlNodeTypeElement:
rwWriterWriteStartElement(rwReaderLocalName);
if (nodeName == rwReaderLocalName)
// 修改屬性
rwWriteAttributes(nodeName);
else
// deep copy
rwWriterWriteAttributes(rwReader false);
if (rwReaderIsEmptyElement)
rwWriterWriteEndElement();
break;
}
}
// Close the root tag
rwWriterWriteEndElement();
// Close the document and any internal resources
rwWriteEndDocument();
}


XmlTextReadWriter類不僅可以讀XML文檔也可以寫XML文檔你可以它來讀XML文檔的內容如果需要你還可以用它來做一些基本的更新操作基本的更新操作在這裡是指修改某個已存在的屬性的值或者某個節點的內容又或者是增加一個新的屬性或節點對於更復雜的操作最好還是用XMLDOM分析器

  總結

  Reader和 Framework中處理XML數據的根本它們提供了對所有XML數據訪問功能的原始的APIReader像一個新的分析器類它即有XMLDOM的強大又有SAX的快速簡單Writer為簡單的創建XML文檔而設計雖然Reader和Writer都是NET Framework中的一小塊但是它們是相互獨立的API在本文中我們只討論了怎麼樣用Reader和Writer完成一些主要的工作 介紹了驗證分析器的原理機制並把Reader和writer整合在一個單獨的類中上述所有的這些類都是輕量級的類似於游標式的XMLDOM分析器


From:http://tw.wingwit.com/Article/program/ASP/201311/21740.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.