Dot-Net

為 XML 編碼文本數據的最佳方法

  • October 1, 2008

我一直在尋找 .Net 中的通用方法來編碼用於 Xml 元素或屬性的字元串,但當我沒有立即找到時感到很驚訝。那麼,在我走得太遠之前,我會不會錯過內置功能?

假設它真的不存在,我正在整理我自己的通用EncodeForXml(string data)方法,並且我正在考慮最好的方法來做到這一點。

我正在使用的提示整個事情的數據可能包含像 &、<、" 等壞字元。它有時還可能包含正確轉義的實體:&、< 和 “,這意味著只使用CDATA 部分可能不是最好的主意。這似乎有點笨拙;我寧願最終得到一個可以直接在 xml 中使用的漂亮字元串值。

我過去曾使用正則表達式來擷取錯誤的&符號,我正在考慮在這種情況下以及第一步中使用它來擷取它們,然後對其他字元進行簡單的替換。

那麼,這是否可以在不使其過於復雜的情況下進一步優化,還有什麼我遺漏的嗎?:

Function EncodeForXml(ByVal data As String) As String
   Static badAmpersand As new Regex("&(?![a-zA-Z]{2,6};|#[0-9]{2,4};)")

   data = badAmpersand.Replace(data, "&amp;")

   return data.Replace("&lt;", "&lt;").Replace("""", "&quot;").Replace("&gt;", "gt;")
End Function

對不起所有 C# 的人——我真的不在乎我使用哪種語言,但我想讓 Regex 成為靜態的,你不能在 C# 中做到這一點而不在方法之外聲明它,所以這將是 VB 。網

最後,我們仍然在我工作的 .Net 2.0 上,但如果有人可以將最終產品轉化為字元串類的擴展方法,那也很酷。

更新前幾個響應表明.Net 確實有內置的方法來做到這一點。但是現在我已經開始了,我有點想完成我的 EncodeForXml() 方法只是為了好玩,所以我仍在尋找改進的想法。值得注意的是:應該編碼為實體的更完整的字元列表(可能儲存在列表/映射中),並且比對串列不可變字元串執行 .Replace() 獲得更好的性能。

System.XML 為您處理編碼,因此您不需要這樣的方法。

根據您對輸入的了解程度,您可能必須考慮到並非所有 Unicode 字元都是有效的 XML 字元

Server.HtmlEncodeSystem.Security.SecurityElement.Escape似乎都忽略了非法 XML 字元,而System.XML.XmlWriter.WriteString遇到非法字元時會拋出ArgumentException (除非您禁用該檢查,在這種情況下它會忽略它們)。此處提供了庫函式的概述。

**編輯 2011/8/14:**看到至少有幾個人在過去幾年中諮詢過這個答案,我決定完全重寫原來的程式碼,它有很多問題,包括可怕的錯誤處理 UTF-16

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

/// &lt;summary&gt;
/// Encodes data so that it can be safely embedded as text in XML documents.
/// &lt;/summary&gt;
public class XmlTextEncoder : TextReader {
   public static string Encode(string s) {
       using (var stream = new StringReader(s))
       using (var encoder = new XmlTextEncoder(stream)) {
           return encoder.ReadToEnd();
       }
   }

   /// &lt;param name="source"&gt;The data to be encoded in UTF-16 format.&lt;/param&gt;
   /// &lt;param name="filterIllegalChars"&gt;It is illegal to encode certain
   /// characters in XML. If true, silently omit these characters from the
   /// output; if false, throw an error when encountered.&lt;/param&gt;
   public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) {
       _source = source;
       _filterIllegalChars = filterIllegalChars;
   }

   readonly Queue&lt;char&gt; _buf = new Queue&lt;char&gt;();
   readonly bool _filterIllegalChars;
   readonly TextReader _source;

   public override int Peek() {
       PopulateBuffer();
       if (_buf.Count == 0) return -1;
       return _buf.Peek();
   }

   public override int Read() {
       PopulateBuffer();
       if (_buf.Count == 0) return -1;
       return _buf.Dequeue();
   }

   void PopulateBuffer() {
       const int endSentinel = -1;
       while (_buf.Count == 0 && _source.Peek() != endSentinel) {
           // Strings in .NET are assumed to be UTF-16 encoded [1].
           var c = (char) _source.Read();
           if (Entities.ContainsKey(c)) {
               // Encode all entities defined in the XML spec [2].
               foreach (var i in Entities[c]) _buf.Enqueue(i);
           } else if (!(0x0 &lt;= c && c &lt;= 0x8) &&
                      !new[] { 0xB, 0xC }.Contains(c) &&
                      !(0xE &lt;= c && c &lt;= 0x1F) &&
                      !(0x7F &lt;= c && c &lt;= 0x84) &&
                      !(0x86 &lt;= c && c &lt;= 0x9F) &&
                      !(0xD800 &lt;= c && c &lt;= 0xDFFF) &&
                      !new[] { 0xFFFE, 0xFFFF }.Contains(c)) {
               // Allow if the Unicode codepoint is legal in XML [3].
               _buf.Enqueue(c);
           } else if (char.IsHighSurrogate(c) &&
                      _source.Peek() != endSentinel &&
                      char.IsLowSurrogate((char) _source.Peek())) {
               // Allow well-formed surrogate pairs [1].
               _buf.Enqueue(c);
               _buf.Enqueue((char) _source.Read());
           } else if (!_filterIllegalChars) {
               // Note that we cannot encode illegal characters as entity
               // references due to the "Legal Character" constraint of
               // XML [4]. Nor are they allowed in CDATA sections [5].
               throw new ArgumentException(
                   String.Format("Illegal character: '{0:X}'", (int) c));
           }
       }
   }

   static readonly Dictionary&lt;char,string&gt; Entities =
       new Dictionary&lt;char,string&gt; {
           { '"', "&quot;" }, { '&', "&amp;"}, { '\'', "&apos;" },
           { '&lt;', "&lt;" }, { '&gt;', "&gt;" },
       };

   // References:
   // [1] http://en.wikipedia.org/wiki/UTF-16/UCS-2
   // [2] http://www.w3.org/TR/xml11/#sec-predefined-ent
   // [3] http://www.w3.org/TR/xml11/#charsets
   // [4] http://www.w3.org/TR/xml11/#sec-references
   // [5] http://www.w3.org/TR/xml11/#sec-cdata-sect
}

單元測試和完整程式碼可以在這裡找到。

引用自:https://stackoverflow.com/questions/157646