static void TestXMLWriter()
{
    MemoryStream ms = new MemoryStream();
    XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
    xtw.Formatting = Formatting.Indented;
    XmlDocument xd = new XmlDocument();
    xd.LoadXml("<Group><User>Jeffrey</User></Group>");
    xd.Save(xtw);
    xtw.Flush();
    xtw.Close();
    string xml = Encoding.UTF8.GetString(ms.ToArray());
    Console.WriteLine(xml);
    xd.LoadXml(xml);
}

XmlTextWriter設為Fomatting.Indented時,可以把XML整成美美的縮排格式,於是我寫了以上的Code。但是這段程式有點問題,明明是XmlTextWriter輸出的Byte Array轉成字串後,再被另一個XmlDocument.LoadXml()卻會發生Data at the root level is invalid. Line 1, Position 1.的錯誤!

問題出在BOM!! Byte Order Mark,相信有很多人在使用文字編輯軟體都有發現Save UTF-8 with BOM這類的選項,BOM是加在文字檔前的幾個位元,可以協助應用程式識別文字的儲存格式(多用於Windows,許多被這幾個位元搞暈頭的開發者視之為微軟的餘毒),這裡有篇好文章

Byte order markDescription
EF BB BFUTF-8
FF FEUTF-16, little endian
FE FFUTF-16, big endian
FF FE 00 00UTF-32, little endian
00 00 FE FFUTF-32, big-endian

先前的程式寫法,XmlTextWriter在寫入MemoryStream時,預設就會加上BOM。若我們將Byte Array寫入檔案,再用StreamReader或XmlDocument.Load讀取,BOM被用來識別編碼格式後將被丟棄不出現在內容字串中;當我們直接用Encoding.GetString轉成字串時,BOM則會被帶入,造成LoadXml()解析錯誤,雖可以設法Trim掉,但我覺得更好的方法是利用UTF8Encoding建構式所支援的encoderShouldEmitUTF8Identifier參數,設為false,輸出結果即不會加註BOM。因此程式要改成:

XmlTextWriter xtw = new XmlTextWriter(ms, new System.Text.UTF8Encoding(false));

That's all, folks.


Comments

Be the first to post a comment

Post a comment