為 XML 編碼文本數據的最佳方法
我一直在尋找 .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, "&") return data.Replace("<", "<").Replace("""", """).Replace(">", "gt;") End Function對不起所有 C# 的人——我真的不在乎我使用哪種語言,但我想讓 Regex 成為靜態的,你不能在 C# 中做到這一點而不在方法之外聲明它,所以這將是 VB 。網
最後,我們仍然在我工作的 .Net 2.0 上,但如果有人可以將最終產品轉化為字元串類的擴展方法,那也很酷。
更新前幾個響應表明.Net 確實有內置的方法來做到這一點。但是現在我已經開始了,我有點想完成我的 EncodeForXml() 方法只是為了好玩,所以我仍在尋找改進的想法。值得注意的是:應該編碼為實體的更完整的字元列表(可能儲存在列表/映射中),並且比對串列不可變字元串執行 .Replace() 獲得更好的性能。
System.XML 為您處理編碼,因此您不需要這樣的方法。
根據您對輸入的了解程度,您可能必須考慮到並非所有 Unicode 字元都是有效的 XML 字元。
Server.HtmlEncode和System.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; /// <summary> /// Encodes data so that it can be safely embedded as text in XML documents. /// </summary> 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(); } } /// <param name="source">The data to be encoded in UTF-16 format.</param> /// <param name="filterIllegalChars">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.</param> public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) { _source = source; _filterIllegalChars = filterIllegalChars; } readonly Queue<char> _buf = new Queue<char>(); 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 <= c && c <= 0x8) && !new[] { 0xB, 0xC }.Contains(c) && !(0xE <= c && c <= 0x1F) && !(0x7F <= c && c <= 0x84) && !(0x86 <= c && c <= 0x9F) && !(0xD800 <= c && c <= 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<char,string> Entities = new Dictionary<char,string> { { '"', """ }, { '&', "&"}, { '\'', "'" }, { '<', "<" }, { '>', ">" }, }; // 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 }單元測試和完整程式碼可以在這裡找到。