果然沒啥人知道的 code, bug 也會比較慢被抓出來 ... 兩個小時前我才貼了找到 XmlNodeWriter 的替代品,用了一下就被我挖到一個 BUG ... @_@
先來看看我的 Sample Code:
1: // test xml text writer, correct result
2: // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
3: {
4: Console.WriteLine("Using XmlTextWriter:");
5: XmlWriter writer = XmlWriter.Create(Console.Out);
6: writer.WriteStartElement("root");
7: writer.WriteRaw("<a/><a/><a/><a/><a/>");
8: writer.WriteEndElement();
9: writer.Flush();
10: Console.WriteLine();
11: Console.WriteLine();
12: }
13: // test xml node writer, wrong result
14: // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
15: {
16: Console.WriteLine("Using XmlWellFormedWriter:");
17: XmlDocument xmldoc = new XmlDocument();
18: XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
19: writer.WriteStartElement("root");
20: writer.WriteRaw("<a/><a/><a/><a/><a/>");
21: writer.WriteEndElement();
22: writer.Close();
23: xmldoc.Save(Console.Out);
24: Console.WriteLine();
25: Console.WriteLine();
26: }
而這是程式的輸出畫面:
兩段 code 除了拿到的 XmlWriter 來源不同之外,用它寫 XML DATA 的方式是一致的,不過寫出來的 XML 則完全不同。看來兩種 XmlWriter 對於 WriteRaw(...) 的實作不大相同。而照 MSDN 上的說明來說,XmlTextWriter的行為是對的,XmlWellFormedWriter 則太雞婆了,沒事多作一次編碼...
該說運氣好嘛? 哈哈... 繼上次撈到一個 SmtpMail 的 Bug 之後,這次又撈到一個... 要用的人注意一下,不過即使有這個 Bug, 也不會影響它的地位啦,這 Writer 解決了我很大的困擾,動搖國本也要用下去... (咳... 不過是避開一個 API ...)
最後我改了用法,一方面 API 有 BUG 是一回事,另一方面直接用這 API 也很危險,因為 MSDN 說它不會去做內容的驗證,也就是說透過 WriteRaw( ) 寫進不合法的資料,會讓你整份輸出都毀了... 第二個原因比較重要,因此我換了一個替代作法, 類似 Pipe 一樣,把 XmlReader 讀到的東西都寫到 XmlWriter:
1: /// <summary>
2: /// 從 XmlReader 複製到 XmlWriter
3: /// </summary>
4: /// <param name="reader"></param>
5: /// <param name="writer"></param>
6: private static void XmlCopyPipe(XmlReader reader, XmlWriter writer)
7: {
8: if (reader == null)
9: {
10: throw new ArgumentNullException("reader");
11: }
12: if (writer == null)
13: {
14: throw new ArgumentNullException("writer");
15: }
16: while (reader.Read() == true)
17: {
18: switch (reader.NodeType)
19: {
20: case XmlNodeType.Element:
21: writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
22: writer.WriteAttributes(reader, true);
23: if (reader.IsEmptyElement)
24: {
25: writer.WriteEndElement();
26: }
27: break;
28: case XmlNodeType.Text:
29: writer.WriteString(reader.Value);
30: break;
31: case XmlNodeType.Whitespace:
32: case XmlNodeType.SignificantWhitespace:
33: writer.WriteWhitespace(reader.Value);
34: break;
35: case XmlNodeType.CDATA:
36: writer.WriteCData(reader.Value);
37: break;
38: case XmlNodeType.EntityReference:
39: writer.WriteEntityRef(reader.Name);
40: break;
41: case XmlNodeType.XmlDeclaration:
42: case XmlNodeType.ProcessingInstruction:
43: writer.WriteProcessingInstruction(reader.Name, reader.Value);
44: break;
45: case XmlNodeType.DocumentType:
46: writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
47: break;
48: case XmlNodeType.Comment:
49: writer.WriteComment(reader.Value);
50: break;
51: case XmlNodeType.EndElement:
52: writer.WriteFullEndElement();
53: break;
54: }
55: }
56: }
很好用的作法,就像過去需要 COPY XML 資料,最常見的就是把來源跟目的都用 XmlDocument 載入,直接用 ImportNode( ) 把 XML 片段資料搬到另一個 XmlDocument 再儲存。跟上一篇的原因一樣,看起來很蠢... 就想到這個作法,透過 XmlReader, 拿到的是已經 parsing 過的資料,直接寫到 XmlWriter。而我用的 Writer 正好又可避開重複作 parsing 動作的優點,正好這樣效能跟可用性都兼顧了... 經過 parsing, 至少寫出來的東西會安心一點...
把最後我的程式搭配這個 XmlPipeCopy 改一改:
1: XmlDocument xmldoc = new XmlDocument();
2: XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
3: writer.WriteStartElement("root");
4: XmlReaderSettings settings = new XmlReaderSettings();
5: settings.ConformanceLevel = ConformanceLevel.Fragment;
6: XmlReader reader = XmlReader.Create(
7: new StringReader("<a/><a/><a/><a/><a/>"),
8: settings);
9: XmlCopyPipe(reader, writer);
10: writer.WriteEndElement();
11: writer.Close();
12: xmldoc.Save(Console.Out);
試了一下,果然如預期的執行了 :D,結果也沒錯,還好 XmlWellFormedWriter 的 Bug 只存在於 WriteRaw... 閃開就沒事了:
其中有個陷阱,就是如何用 XmlReader 讀取 XmlFragment (可以有多個 ROOT 的 XML DATA)。其實這個解法跟程式碼,大部份都是這篇看來的,只不過在裡面加了個 LOOP 跟改了名字,各位覺的好用的話記得去謝原作者 Mark Fussell, 別謝錯人了 :D