這問題我遇到第二次了,第一次遇到時濛濛懂懂的解決了。但不知為何越來越有種不想讓自己迷迷糊糊過一生的感覺,所以拼了命也要將模糊的地帶給釐清,於是又花了好幾個鐘頭把來龍去脈給釐清,避免日後又再度遇到同樣的問題。這是個關於透過 XmlWriter 撰寫 XML 的問題,不管我如何設定 Encoding 編碼都無法改變輸出的編碼,經過一番努力辯證之後才徹底釐清觀念,以後也不會再混淆了。
先準備 XmlWriterSettings 類別定義輸出 XML 的排版格式與編碼,其中包括文字編碼的 Encoding 屬性:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.NewLineOnAttributes = true;
settings.Encoding = Encoding.GetEncoding("big5");
但是這裡的 XmlWriterSettings.Encoding 屬性根本就是個幌子,不管設定什麼 Encoding 輸出的結果一律都是 UTF-16 編碼。
接著,我們繼續把 XmlWriter 的程式碼寫完如下:
StringBuilder sb = new StringBuilder();
XmlWriter writer = XmlWriter.Create(sb, settings);
writer.WriteStartElement("root");
writer.WriteStartElement("person");
writer.WriteElementString("name", "游錫堃、喆喆創意");
writer.WriteEndElement();
writer.WriteEndElement();
writer.Flush();
Response.Write(sb.ToString());
輸出的結果卻是如下:
<?xml version="1.0" encoding="utf-16"?>
<root>
<person>
<name>游錫堃、喆喆創意</name>
</person>
</root>
原本我們預期的 encoding 不是 big5 嗎?為什麼總是輸出 utf-16 呢?
然後我換個方式寫,將最後得到的 StringBuilder 輸出成字串,並轉成 XmlDocument 物件處理,強迫將輸出編碼修改:
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(sb.ToString());
XmlDeclaration decl = (XmlDeclaration)xdoc.FirstChild;
decl.Encoding = "big5";
Response.Write(xdoc.OuterXml);
這是換湯不換藥,因為事實上輸出的編碼還是 UTF-16,只是 <?xml version="1.0" encoding="big5"?> 的 encoding 部分被硬改成 big5 而已,而這當然是錯誤示範,小朋友可千萬不要學,叔叔是有練過的。
這時我又再換一種寫法,改用 Stream 的型別傳入就能夠正確處理 Encoding 屬性了:
using (Stream fs = File.Open("a.xml", FileMode.CreateNew))
{
XmlWriter writer = XmlWriter.Create(fs, settings);
writer.WriteStartElement("root");
writer.WriteStartElement("person");
writer.WriteElementString("name", "游錫堃、喆喆創意");
writer.WriteEndElement();
writer.WriteEndElement();
writer.Flush();
fs.Close();
}
當然,如果我用 Response.OutputStream 直接輸出到網頁,當然也是可行的:
XmlWriter writer = XmlWriter.Create(Response.OutputStream, settings);
writer.WriteStartElement("root");
writer.WriteStartElement("person");
writer.WriteElementString("name", "游錫堃、喆喆創意");
writer.WriteEndElement();
writer.WriteEndElement();
writer.Flush();
輸出的結果正如預期的是:
<?xml version="1.0" encoding="big5"?>
<root>
<person>
<name>游錫堃、喆喆創意</name>
</person>
</root>
以上這兩個成功案例,都是用 Stream 型別傳入才成功的,但一個是直接輸出成檔案、一個是直接輸出到網頁(Response.OutputStream),如果我們需要將 XmlWriter 寫入的資料暫時放在記憶體中的話,就必須利用 MemoryStream 的方式當成緩衝區(buffer)。
我首先做了以下嘗試 (錯誤示範):
Encoding enc = Encoding.GetEncoding("big5");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.NewLineOnAttributes = true;
settings.Encoding = enc;
StringBuilder sb = new StringBuilder();
MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);
XmlWriter writer = XmlWriter.Create(sw, settings);
writer.WriteStartElement("root");
writer.WriteStartElement("person");
writer.WriteElementString("name", "游錫堃、喆喆創意");
writer.WriteEndElement();
writer.WriteEndElement();
writer.Flush();
string result = enc.GetString(ms.ToArray());
Response.ContentType = "text/xml";
Response.Write(result);
但是輸出的結果卻是亂碼:
<?xml version="1.0" encoding="utf-8"?>
<root>
<person>
<name>皜賊?s????/name>
</person>
</root>
從第一行看到就可以知道,其實 XmlWriter 還是以 UTF-8 當成處理所有 XmlWriter 內部處理的字元,所以才無法被 enc.GetString() 方法轉換成正確的 Big5 文字。
最後,我試著在 StreamWriter 加上 Encoding 參數 (如下 enc 部分),就可以正常處理所有 Big5 的字元,不過卻無法正確處理「非 Big5 字集」的字元。
StreamWriter sw = new StreamWriter(ms, enc);
輸出的結果是:
<?xml version="1.0" encoding="big5"?>
<root>
<person>
<name>游錫?、??創意</name>
</person>
</root>
到最後,我才發現我被我自己先前的程式碼給「制約」了,原來我一直在用 StreamWriter 在過濾所有應該被寫入 Stream 的文字,這反而限制了所有應該被正常處理的文字,所以才會導致有部分非字集內的文字沒有正常被轉成 Entities 的形式。
我最後把 StreamWriter 這一行給抽掉,所有的編碼的問題就解決了。以下是終極正確版:
Encoding enc = Encoding.GetEncoding("big5");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.NewLineOnAttributes = true;
settings.Encoding = enc;
MemoryStream ms = new MemoryStream();
XmlWriter writer = XmlWriter.Create(ms, settings);
writer.WriteStartElement("root");
writer.WriteStartElement("person");
writer.WriteElementString("name", "游錫堃、喆喆創意");
writer.WriteEndElement();
writer.WriteEndElement();
writer.Flush();
string result = enc.GetString(ms.ToArray());
Response.ContentType = "text/xml";
Response.Write(result);
心得結論
相關連結