原來 System.Xml.XmlWellFormedWriter 有 Bug ..

果然沒啥人知道的 code, bug 也會比較慢被抓出來 ... 兩個小時前我才貼了找到 XmlNodeWriter 的替代品,用了一下就被我挖到一個 BUG ... @_@

先來看看我的 Sample Code:

XmlTextWriter v.s. XmlWellFormedWriter[copy 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>&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;</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:  }

 

而這是程式的輸出畫面:

image

 

兩段 code 除了拿到的 XmlWriter 來源不同之外,用它寫 XML DATA 的方式是一致的,不過寫出來的 XML 則完全不同。看來兩種 XmlWriter 對於 WriteRaw(...) 的實作不大相同。而照 MSDN 上的說明來說,XmlTextWriter的行為是對的,XmlWellFormedWriter 則太雞婆了,沒事多作一次編碼...

 

該說運氣好嘛? 哈哈... 繼上次撈到一個 SmtpMail 的 Bug 之後,這次又撈到一個... 要用的人注意一下,不過即使有這個 Bug, 也不會影響它的地位啦,這 Writer 解決了我很大的困擾,動搖國本也要用下去... (咳... 不過是避開一個 API ...)

 

最後我改了用法,一方面 API 有 BUG 是一回事,另一方面直接用這 API 也很危險,因為 MSDN 說它不會去做內容的驗證,也就是說透過 WriteRaw( ) 寫進不合法的資料,會讓你整份輸出都毀了... 第二個原因比較重要,因此我換了一個替代作法, 類似 Pipe 一樣,把 XmlReader 讀到的東西都寫到 XmlWriter:

XmlCopyPipe 實作[copy code]
   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 改一改:

用 XmlCopyPipe 取代 WriteRaw( )[copy code]
   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... 閃開就沒事了:

image

 

 

其中有個陷阱,就是如何用 XmlReader 讀取 XmlFragment (可以有多個 ROOT 的 XML DATA)。其實這個解法跟程式碼,大部份都是這篇看來的,只不過在裡面加了個 LOOP 跟改了名字,各位覺的好用的話記得去謝原作者 Mark Fussell, 別謝錯人了 :D






Facebook Pages

Edit Post (Pull Request)

Post Directory