解決 XmlWriter 輸出的 XML 無法正確設定 Encoding 的問題

分享到噗浪!

這問題我遇到第二次了,第一次遇到時濛濛懂懂的解決了。但不知為何越來越有種不想讓自己迷迷糊糊過一生的感覺,所以拼了命也要將模糊的地帶給釐清,於是又花了好幾個鐘頭把來龍去脈給釐清,避免日後又再度遇到同樣的問題。這是個關於透過 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>游錫&#x5803;、&#x5586;&#x5586;創意</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);

心得結論

相關連結

  

此文章由 will 發表於 2009/10/3 上午 12:51:17

永久連結 | 評論 (5) | 此文章的RSSRSS comment feed |

分類: .Net | C#

標籤: , , , ,

評論

十月 3. 2009 00:56

demo

讚....馬上來應用

demo 台灣

十月 3. 2009 07:13

laneser

後面的心得結論太棒了, 而且整個過程都有記錄下來, 很容易讓人學習結論.
另外就是, String 在 .NET Framework 當中都是 UTF-16 歐,
很容易可以推論 StringBuilder 都是 UTF-16 處理的...
(就算不是, 一旦呼叫  StringBuilder 的 ToString() 就註定了 Encoding)
所以只要牽扯到 Encoding, 請一定想到 byte[] , MemoryBuffer, Stream 之類的才有用...

laneser

十月 3. 2009 10:36

San

可以參考一下 MSDN 文件上的說明:
msdn.microsoft.com/.../...ersettings.encoding.aspx

"The Encoding property only applies to the XmlWriter instances that are created either with the specified Stream or with the specified file name."

只有以 binary 方式輸出時 (含 stream, filename) encoding 才會生效,如果是輸出到 TextWriter 的話,encoding 就會被 TextWriter 自身的 encoding 所覆蓋

San 台灣

十月 3. 2009 13:05

Will 保哥

San:

中文版的 MSDN 是這樣寫的:「這個屬性只適用於將文字內容輸出至資料流的 XmlWriter 執行個體,否則就會忽略這項設定。」

原來「資料流」在這句話裡描述的是個「型別」,我如果早點看懂這段話,就沒有這篇文章了。^_^

再度證明,技術文件必須中英文對照看,或乾脆只看英文,由於中文斷字不易,要練會用中文看翻譯過的技術文件著實不是件容易的事,實在太容易誤解了。

給 MSDN 文件的建議:『只要有出現其名詞本身代表的是某個 .NET 型別的情況都能加上「超連結」過去,這樣子翻譯成中文時讀者會看到這有個連結,代表某種意思,這樣應該比較不會被誤解。』

Will 保哥 台灣

七月 30. 2010 13:40

Ark

請教~若asmx要改出非標準的SOAP HEAD
比方說:?WSDL 第一行顯示
<?xml version="1.0" encoding="big5"?>
要做哪些設定?
web.config globalization ? 很像不夠

如果是MVC 倒是可以在Controller處理
接的參數Request.InputStream =>Response.OutputStream

Ark 台灣

新增評論


( 您輸入的Email不會顯示於網站上 )

  Country flag

biuquote
  • 評論
  • 線上預覽
Loading