在.NET Framework中輕鬆處理XML資料(四)
XmlTextWriter
類 用在本節中的方法建立XML文檔顯然並不困難。多年以來,開發人員都是通過在緩衝在串連一些字串,串連好以後再把緩衝中字串輸出到檔案的方式來建立XML文檔。但是以這種方式建立XML文檔的方法只有在你保證字串中不存在任何細小的錯誤的時候才有效。.NET Framework通過用XMLwriter提供了更好的建立XML文檔的方法。 XML Writer類以只前(forward-only)的方式輸出XML資料到流或者檔案中。更重要的是,XML Writer在設計時就保證所有的XML資料都符合W3C XML 1.0推薦規範,你甚至不用擔心忘記寫閉標籤,因為XML Writer會幫你寫。XmlWriter是所有 XML writer的抽象基類。.NET Framework只提供唯一的一個writer 類----XmlTextWriter類。 我們先來看看XML writers和舊的writers的不同點,下面的代碼儲存了一個string型的數組:StringBuilder sb = new StringBuilder("");sb.Append("<array>");foreach(string s in theArray) { sb.Append("<element value=/""); sb.Append(s); sb.Append("/"/>");}sb.Append("</array>"); 代碼通過迴圈取出資料中的元素,寫好標籤文本並把它們累加到一個string中。代碼保證輸出的內容是格式良好的並且注意了新行的縮排,及支援命名空間。當建立的文檔結構比較簡單時,這種方法可能不會有錯誤。然而,當你要支援處理指示,命名空間,縮排,格式化以及實體的時候,代碼的數量就成指數級增長,出錯的可能性也隨之增長。 XML writer寫方法功能對應每個可能的XML節點類型,它使建立xml文檔的過程更符合邏輯、更少的信賴於繁瑣的標記語言。圖六示範了怎麼樣用XmlTextWriter類的方法來串連一個string資料。代碼很簡潔,用XML writer的代碼更容易讀、結構更好。
Figure 6Serializing a String Arrayvoid CreateXmlFileUsingWriters(String[] theArray, string filename){ // Open the XML writer (用預設的字元集) 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並不是魔術師----它不能修複輸入的錯誤。XML writer不會檢查元素名和屬性名稱是否有效,也不保證被用的任何的Unicode字元集適合當前架構的編碼集。如上所述,為了避免輸出錯誤,必須要杜絕非XML字元。但是writer沒有提供這種方法。 另外,當建立一個屬性節點時,Writer不會檢驗屬性節點的名稱是否與已存在的元素節點的名稱相同。最後,XmlWriter類不是一個帶驗證的Writer類,也不保證輸出是否符合schema或者DTD。在.NET Framework中帶驗證的writer類目前來說還沒有提供。但是在我寫的《
Applied XML Programming for Microsoft .
NET (Microsoft Press, 2002)》書中,我自己寫了一個帶驗證的Writer組件。你可以到下面的網址去下載源碼:http://www.microsoft.com/MSPress/books/6235.asp. 圖七列出了XML writer的一些狀態值(state)。這些值都源於WriteState枚舉類。當你建立一個Writer,它的初始狀態為Start,表示你將要配置該對象,實際上writer沒有開始。下一個狀態是Prolog,該狀態是當你調用WriteStartDocument方法開始工作的時候設定的。然後,狀態的轉換就取決於你的寫的文檔及文檔的內容了。Prolog狀態一直保留到當你增加一個非元素節點時,例如注釋元素,處理指示及文件類型。當第一個節點也就是根節點寫完後,狀態就變為Element。當你調用WriterStartAtribute方法時狀態轉換為Attribute,而不是當你調用WriteAtributeString方法寫屬性時轉換為該狀態。如果那樣的話,狀態應該是Element。當你寫一個閉標籤(>)時,狀態會轉換成Content。當你寫完文檔後,調用WriteEndDocument方法,狀態就會返回為Start,直到你開始寫另一個文檔或者把Writer關掉。
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 把輸出文本存在內部的一個緩衝區內。一般情況下,緩衝區會被重新整理或者被清除,當Writer被關閉前XML文本應該要寫出。在任何時你都可以通過調用Flush方法清空緩衝區,把當前的內容寫到流中(通過BaseStream屬性暴露流),然後釋放部分佔用的記憶體,Writer仍保持為開啟狀態(open state),可以繼續操作。注意,雖然寫了部分的文檔內容,但是在Writer沒有關閉前其它的程式是不能處理該文檔的。
可以用兩種方法來寫屬性節點。第一種方法是用WriteStartAtribute方法去建立一個新的屬性節點,更新Writer的狀態。接著用WriteString方法設定屬性值。寫完後,用WriteEndElement方法結束該節點。另外,你也可以用WriteAttributeString方法去建立新的屬性節點,當writerr的狀態為Element時,WriterAttributeString開始工作,它單獨建立一個屬性。同樣的,WriteStartElement方法寫節點的開始標籤(<),然後你可以隨意的設定節點的屬性和常值內容。元素節點的閉標籤都帶”/ >”。如果想寫閉標籤可以用WriteFullEndElement方法來寫。應該避免傳送給寫方法的文本中包含敏感的標記字元,例如小於符號(<)。用WriteRaw方法寫入流的字串不會被解析,我們可以用它來對xml文檔寫入特殊的字串。下面的兩行代碼,第一行輸出的是”<”,第二行輸出”<”:writer.WriteString("<");
writer.WriteRaw("<");
讀寫流有趣的是,reader(閱讀器)和writer類提供了基於Base64 和BinHex編碼的讀寫資料流的方法。WriteBase64 和 WriteBinHex方法的功能與其它的寫方法的功能存在著細微的差別。它們都是基於流的,這兩個方法的功能像一個byte數組而不是一個string。下面的代碼首先把一個string轉換成一個byte數組,然後把它們寫成一個Base 64 編碼流。Encoding類的GetBytes靜態方法完成轉換的任務: writer.WriteBase64( Encoding.Unicode.GetBytes(buf), 0, buf.Length*2);圖八中代碼示範了把一個string資料轉換為Base 64 編碼的XML流。圖九是輸出的結果。
Figure 8 Persisting a String Array as Base64using 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 // 把數群組轉換成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 設定縮排。此選項只對元素內容進行縮排 xmlw.Formatting = Formatting.Indented; //書寫版本為“1.0”的 XML 聲明 xmlw.WriteStartDocument(); //寫出包含指定文本的注釋 <!--...-->。 xmlw.WriteComment("Array to Base64 XML"); //開始寫出array節點 xmlw.WriteStartElement("array");//寫出具有指定的首碼、本地名稱、命名空間 URI 和值的屬性 xmlw.WriteAttributeString("xmlns", "x", null, "dinoe:msdn-mag"); // 迴圈的寫入array的子節點 foreach(string s in theArray) { //寫出指定的開始標記並將其與給定的命名空間和首碼關聯起來xmlw.WriteStartElement("x", "element", null);//把S轉換成byte[]數組, 並把byte[]數組編碼為 Base64 並寫出結果文本,要寫入的位元組數為s總長度的2倍,一個string占的位元組數是2位元組。xmlw.WriteBase64(Encoding.Unicode.GetBytes(s), 0, s.Length*2); //關閉子節點 xmlw.WriteEndElement(); } //關閉根節點,只有兩級 xmlw.WriteEndDocument(); // 關閉writer xmlw.Close(); // 讀出寫入的內容 XmlTextReader reader = new XmlTextReader(filename); while(reader.Read()) { //擷取節點名為element的節點 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類有專門的解釋Base64和BinHex編碼流的方法。下面的代碼片斷示範了怎麼樣用XmlTextReader類的ReadBase64方法解析用Base64和BinHex編碼集建立的文檔。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型轉換成string型是通過Encoding類的GetString方法實現的。儘管我只介紹了基於Base64編碼集的代碼,但是可以簡單的用BinHex替換方法名就可以實現讀基於BinHex編碼的節點內容(用ReadBinHex方法)。這個技巧也可以用於讀任何用byte資料形式表示的位元據,尤其是image類型的資料。