为XML编码文本数据的最佳方法

我在.Net中寻找一个通用的方法来编码用于Xml元素或属性的string,并且当我没有立即find一个string时感到惊讶。 那么,在我进一步深入之前,我是否可能错过了内置函数?

假设它真的不存在,我把我自己的通用EncodeForXml(string data)方法放在一起,我正在考虑这样做的最好方法。

我使用的数据提示,整个事件可能包含像&,<,“等不好的字符。它也可能包含正确转义的实体:&amp;,&lt;和”,这意味着只使用CDATA部分可能不是最好的主意,这似乎有点klunky;我宁愿结束了一个很好的string值,可以直接在XML中使用。

我在过去使用正则expression式来捕捉不好的&符号,而且我正在考虑用它来捕捉它们,以及第一步,然后简单地replace其他字符。

那么,这可以进一步优化,而不是太复杂,有什么我失踪? :

 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;").Replace("""", "&quot;").Replace(">", "gt;") End Function 

对不起,对于你所有的C# – 只有人 – 我真的不在乎我使用哪种语言,但是我想要使正则expression式是静态的,并且你不能在C#中做这些,而不是在方法之外声明它,所以这将是VB 。净

最后,我们仍然在.NET 2.0上工作,但是如果有人可以把最终产品变成一个string类的扩展方法,那也是非常酷的。

更新前几个回应表明,.net确实有内置的方法来做到这一点。 但是现在我已经开始了,我只想完成我的EncodeForXml()方法,只是为了好玩,所以我仍然在寻找改进的想法。 值得注意的是:应该被编码为实体(可能存储在列表/映射中)的字符的更完整列表,以及比在串行中的不可变串上执行.Replace()更好的性能的东西。

System.XML为你处理编码,所以你不需要像这样的方法。

根据您对input的了解程度,您可能必须考虑到并非所有的Unicode字符都是有效的XML字符 。

Server.HtmlEncodeSystem.Security.SecurityElement.Escape似乎都忽略了非法的XML字符,而System.XML.XmlWriter.WriteString在遇到非法字符(除非在这种情况下它将忽略它们)时抛出一个ArgumentExceptionexception。 这里提供了库函数的概述。

编辑2011/8/14:在过去的几年中,至less有几个人咨询过这个问题,于是我决定彻底改写原来的代码,这个代码有很多问题,包括UTF-16可怕的error handling 。

 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> { { '"', "&quot;" }, { '&', "&amp;"}, { '\'', "&apos;" }, { '<', "&lt;" }, { '>', "&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 } 

unit testing和完整的代码可以在这里find。

SecurityElement.Escape

logging在这里

在过去,我用HttpUtility.HtmlEncode来为xml文本进行编码。 它执行相同的任务,真的。 我还没有遇到任何问题,但这并不是说我不会在将来。 顾名思义,它是用于HTML而不是XML的。

您可能已经阅读过,但是这里是关于xml编码和解码的文章 。

编辑:当然,如果你使用xmlwriter或新的XElement类之一,这个编码是为你完成的。 实际上,您可以将文本放在新的XElement实例中,然后返回该元素的string(.tostring)版本。 我听说SecurityElement.Escape将执行与您的实用方法相同的任务,但没有读取太多或使用它。

编辑2:无视我对XElement的评论,因为你仍然在2.0

System.Web.dll中的Microsoft AntiXss库 AntiXssEncoder类具有以下方法:

 AntiXss.XmlEncode(string s) AntiXss.XmlAttributeEncode(string s) 

它也有HTML:

 AntiXss.HtmlEncode(string s) AntiXss.HtmlAttributeEncode(string s) 

在.net 3.5+

 new XText("I <want> to & encode this for XML").ToString(); 

给你:

I &lt;want&gt; to &amp; encode this for XML

原来,这种方法不编码一些事情,它应该(像引号)。

SecurityElement.Escape ( workmad3的答案 )似乎在这方面做得更好,它包含在.net的早期版本中。

如果您不介意第三方代码,并希望确保没有非法字符将其放入XML中,我会推荐Michael Kropat的答案 。

XmlTextWriter.WriteString()进行转义。

如果这是一个ASP.NET应用程序,为什么不使用Server.HtmlEncode()?

这可能是您可以从使用WriteCData方法中受益的情况。

 public override void WriteCData(string text) Member of System.Xml.XmlTextWriter Summary: Writes out a <![CDATA[...]]> block containing the specified text. Parameters: text: Text to place inside the CDATA block. 

一个简单的例子如下所示:

 writer.WriteStartElement("name"); writer.WriteCData("<unsafe characters>"); writer.WriteFullEndElement(); 

结果如下所示:

 <name><![CDATA[<unsafe characters>]]></name> 

当读取节点值时,XMLReader会自动去除内联文本的CData部分,所以您不必担心。 唯一的问题是你必须将数据作为innerText值存储到XML节点。 换句话说,您不能将CData内容插入到属性值中。

辉煌! 这就是我所能说的。

这是更新后的代码的VB变体(不是在一个类,只是一个函数),将清理和消毒的XML

 Function cXML(ByVal _buf As String) As String Dim textOut As New StringBuilder Dim c As Char If _buf.Trim Is Nothing OrElse _buf = String.Empty Then Return String.Empty For i As Integer = 0 To _buf.Length - 1 c = _buf(i) If Entities.ContainsKey(c) Then textOut.Append(Entities.Item(c)) ElseIf (AscW(c) = &H9 OrElse AscW(c) = &HA OrElse AscW(c) = &HD) OrElse ((AscW(c) >= &H20) AndAlso (AscW(c) <= &HD7FF)) _ OrElse ((AscW(c) >= &HE000) AndAlso (AscW(c) <= &HFFFD)) OrElse ((AscW(c) >= &H10000) AndAlso (AscW(c) <= &H10FFFF)) Then textOut.Append(c) End If Next Return textOut.ToString End Function Shared ReadOnly Entities As New Dictionary(Of Char, String)() From {{""""c, "&quot;"}, {"&"c, "&amp;"}, {"'"c, "&apos;"}, {"<"c, "&lt;"}, {">"c, "&gt;"}} 

您可以使用自动处理编码的内置类XAttribute :

 using System.Xml.Linq; XDocument doc = new XDocument(); List<XAttribute> attributes = new List<XAttribute>(); attributes.Add(new XAttribute("key1", "val1&val11")); attributes.Add(new XAttribute("key2", "val2")); XElement elem = new XElement("test", attributes.ToArray()); doc.Add(elem); string xmlStr = doc.ToString(); 

这是使用XElements的单行解决scheme。 我用它在一个非常小的工具。 我不需要第二次,所以我保持这种方式。 (它的dirdy道格)

 StrVal = (<xa=<%= StrVal %>>END</x>).ToString().Replace("<xa=""", "").Replace(">END</x>", "") 

哦,它只适用于VB不在C#