【问题标题】:Best way to encode text data for XML为 XML 编码文本数据的最佳方法
【发布时间】:2010-09-14 12:46:42
【问题描述】:

我在 .Net 中寻找一种通用方法来编码用于 Xml 元素或属性的字符串,但当我没有立即找到时感到很惊讶。那么,在我走得太远之前,我会不会错过内置功能?

暂时假设它真的不存在,我正在组合我自己的通用 EncodeForXml(string data) 方法,并且我正在考虑最好的方法来做到这一点。

我正在使用的提示整个事情的数据可能包含坏字符,例如 &、

我过去使用正则表达式来捕获错误的 & 符号,我正在考虑在这种情况下以及第一步中使用它来捕获它们,然后对其他字符进行简单的替换。

那么,是否可以在不使其过于复杂的情况下进一步优化它,还有什么我遗漏的吗? :

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

对不起所有 C# 的人——我真的不在乎我使用哪种语言,但我想让 Regex 成为静态的,你不能在 C# 中做到这一点而不在方法之外声明它,所以这将是 VB.Net

最后,我们仍然在我工作的 .Net 2.0 上,但如果有人可以将最终产品转化为字符串类的扩展方法,那也太酷了。

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

【问题讨论】:

    标签: .net xml encoding .net-2.0


    【解决方案1】:

    根据您对输入的了解程度,您可能必须考虑到not all Unicode characters are valid XML characters

    Server.HtmlEncodeSystem.Security.SecurityElement.Escape 似乎都忽略了非法 XML 字符,而 System.XML.XmlWriter.WriteString 在遇到非法字符时抛出 ArgumentException (除非您禁用该检查,在这种情况下它会忽略它们)。 here 提供库函数概述。

    编辑 2011/8/14: 看到至少有几个人在过去几年中咨询过这个答案,我决定完全重写原来的代码,其中有很多问题,包括 @ 987654323@.

    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
    }
    

    单元测试和完整代码可以在here找到。

    【讨论】:

    • 很好的答案,从这篇文章中看到了类似的解决方案:seattlesoftware.wordpress.com/2008/09/11/…
    • 那篇文章很好地解释了这个问题。
    • 对于 (0x100000 &lt;= c &amp;&amp; c &lt;= 0x10FFFF) 位,我的编译器警告我:“与整数常量比较是没有用的;该常量超出了 'char' 类型的范围
    • 感谢 codeulike — 指出警告是我最终重写原始错误代码所需的动力。 =) 如果有机会,请尝试新代码。
    • +1 用于更新您的代码 :) 并重新审视问题(帮助我)
    【解决方案2】:

    SecurityElement.Escape

    记录在here

    【讨论】:

    • 这似乎是我正在寻找的东西,但底部有一些 cmets 表明实现不那么出色。
    • link
    【解决方案3】:

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

    您可能已经阅读过它,但here is an article 了解 xml 编码和解码。

    编辑:当然,如果您使用 xmlwriter 或新的 XElement 类之一,则此编码已为您完成。实际上,您可以只获取文本,将其放在新的 XElement 实例中,然后返回元素的字符串 (.tostring) 版本。我听说SecurityElement.Escape 也将执行与您的实用程序方法相同的任务,但还没有阅读或使用它。

    EDIT2:忽略我对 XElement 的评论,因为您仍在使用 2.0

    【讨论】:

      【解决方案4】:

      System.Web.dll 中微软的 AntiXss library AntiXssEncoder Class 有这样的方法:

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

      它也有 HTML:

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

      【讨论】:

        【解决方案5】:

        在 .net 3.5+ 中

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

        给你:

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

        原来这个方法没有编码一些它应该编码的东西(比如引号)。

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

        如果您不介意第 3 方代码并希望确保没有非法字符进入您的 XML,我建议您使用 Michael Kropat's answer

        【讨论】:

        • &不是有效的 XML。我假设它会使用 XML 实体:&amp;#38;
        • 似乎最简单的解决方案有时是最好的。为我节省了大量时间,非常感谢。
        【解决方案6】:

        XmlTextWriter.WriteString() 进行转义。

        【讨论】:

        • 或者,在 XmlNode 对象上使用它是相对的 - .InnerText Getter 和 Setter 解码和编码。
        【解决方案7】:

        System.XML 为您处理编码,因此您不需要这样的方法。

        【讨论】:

        • 或者去对那些没有正确编码他们的 xml 的人大喊大叫。
        • @Sekhat 这是一个不合理的解决方案。在现实世界中,大型数据供应商通常不会费心解决这些类型的问题,因为这样做会破坏他们客户的数据。
        • @TrevorSullivan 这种方法在学术界相当有效,但在其他地方则不然。如果您只知道金融世界的一些通用规范的实现是多么半生不熟(从 CRC 实现到 XML 之类的微不足道的东西——我只是根据我的第一手经验说话),您可能会决定把钱存入家里的床垫。
        • @Mick:如果你知道今天的床垫是怎么做的,你可能会决定把钱还给银行。
        • 这被接受了吗?这不是一个答案。有时我们必须处理使用 XML 字符串的代码
        【解决方案8】:

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

        【讨论】:

        • 这是一个库,将用于 asp.net 应用程序和批处理(桌面)。
        • 您实际上可以在桌面应用程序中访问 Server.HTMLEncode() - 您所要做的就是添加对 System.Web 的引用
        • Server.HtmlEncode() 和 HttpUtility.HtmlAttributeEncode() 都不会替换像 '\0' 这样的字符
        • 请注意任何认为这是个好主意的人,System.Web 开销很大,并不真正适用于类库/Windows 应用程序
        • @stuartdotnet - 因此需要注意“如果这是一个 ASP.NET 应用程序”。
        【解决方案9】:

        在这种情况下,您可能会从使用 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 内容插入到属性值中。

        【讨论】:

          【解决方案10】:

          如果您认真对待所有无效字符(不仅仅是少数“html”字符),并且您可以访问System.Xml,那么这里是处理正确 Xml 的最简单方法值数据的编码

          string theTextToEscape = "Something \x1d else \x1D <script>alert('123');</script>";
          var x = new XmlDocument();
          x.LoadXml("<r/>"); // simple, empty root element
          x.DocumentElement.InnerText = theTextToEscape; // put in raw string
          string escapedText = x.DocumentElement.InnerXml; // Returns:  Something &#x1D; else &#x1D; &lt;script&gt;alert('123');&lt;/script&gt;
          
          // Repeat the last 2 lines to escape additional strings.
          

          重要的是要知道XmlConvert.EncodeName() 不合适,因为这是针对实体/标签名称,而不是值。当您需要 Html 编码时,使用它就像 Url 编码。

          【讨论】:

            【解决方案11】:

            太棒了!我只能说这些了。

            这是更新代码的 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;"}}
            

            【讨论】:

              【解决方案12】:

              您可以使用内置类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();
              

              【讨论】:

                【解决方案13】:

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

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

                哦,它只适用于 VB 而不是 C#

                【讨论】:

                  猜你喜欢
                  • 2010-10-01
                  • 2011-02-08
                  • 2011-02-19
                  • 2011-09-14
                  • 2010-10-08
                  • 1970-01-01
                  • 2015-03-10
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多