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


69

我一直在寻找.Net中的通用方法来编码供Xml元素或属性使用的字符串,当我没有立即找到一个字符串时,我感到很惊讶。因此,在进一步介绍之前,我是否会丢失内置功能?

暂时假设它确实不存在,我正在整理自己的通用EncodeForXml(string data)方法,并在考虑实现此目的的最佳方法。

我使用的数据提示整个事情可能包含&,<,“等错误字符。它有时还可能包含正确转义的实体:&amp;,&lt;和&quot ;,这意味着仅使用一个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;").Replace("""", "&quot;").Replace(">", "gt;")
End Function

抱歉,所有C#用户-我不太在乎我使用哪种语言,但是我想使Regex静态化,并且如果不在方法外声明它,则无法在C#中做到这一点,因此将是VB。 。净

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

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

Answers:


4

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


4
或者大喊大叫那些未正确编码xml的人。
Sekhat

8
@Sekhat这是一个不合理的解决方案。在现实世界中,大型数据供应商通常不愿意为解决此类问题而烦恼,因为这样做会破坏其客户的数据。
迈克尔

2
@TrevorSullivan这种方法在学术界相当有效,但在其他地方则没有那么大的作用。如果您只知道某些金融世界通用规范的实现有半生不熟(从CRC实现到像XML这样琐碎的东西-我只是从亲身经历中讲),您可能会决定保留您的资金在家里的床垫。
2014年

8
@Mick:如果您知道今天的床垫是如何制成的,您可能会决定将钱带回银行。
MusiGenesis

2
这被接受了吗?这不是答案。有时我们不得不工作,正在使用XML字符串代码
唐·钱德尔

77

根据您对输入的了解程度,您可能必须考虑到并非所有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;

/// <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
}

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


1
很好的回答,已经看到这篇文章了类似的解决方案:seattlesoftware.wordpress.com/2008/09/11/...
帕格太阳

那篇文章很好地解释了这个问题。
Michael Kropat

有一点(0x100000 <= c && c <= 0x10FFFF)我的编译器警告我:“与整数常量的比较是没有用的;该常量超出了'char'类型的范围
像codeulike 2011年

谢谢codeulike-指出警告是我最终重写原始的错误代码所需要的踢脚。=)如果有机会,请尝试使用新代码。
2011年

1
+1以更新您的代码:)并重新访问问题(对我有帮助)
科恩(Cohen


26

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

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

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

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


14

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

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

它也具有HTML:

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

12

在.net 3.5+

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

给你:

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

事实证明,此方法未对某些应有的内容进行编码(如引号)。

SecurityElement.Escapeworkmad3的答案)似乎在此方面做得更好,并且它包含在.net的早期版本中。

如果您不介意第三方代码,并希望确保没有非法字符将其写入XML,那么我建议Michael Kropat的答案


&amp; 是无效的XML。我假设它会使用XML实体:&#38;
阿姆斯特朗(Armstrongest)

似乎最简单的解决方案有时是最好的。非常感谢,为我节省了大量时间。
KreepN 2012年

5

XmlTextWriter.WriteString() 逃避。


1
或者,在XmlNode对象上使用它的相对对象-.InnerText Getter和Setter进行解码和编码。
ddotsenko 2011年

3

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


它位于一个库中,该库将同时用于asp.net应用程序和批处理(桌面)。
乔尔·科恩荷恩

实际上,你可以访问Server.HTMLEncode()在一个桌面应用程序-所有你需要做的是广告的System.Web参考
国家统计局

Server.HtmlEncode()和HttpUtility.HtmlAttributeEncode()都不能替换'\ 0'之类的字符
Dmitry Dzygin 2011年

只是注意到任何认为这是个好主意的人,System.Web的开销很大,并不真正适合于类库/ Windows应用程序
stuartdotnet 2015年

@stuartdotnet-警告:“如果这是一个ASP.NET应用程序”。
凯夫2015年

3

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


1

如果您认真对待所有无效字符(而不仅仅是少数几个“ 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编码。


0

辉煌!这就是我能说的。

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

0

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

0

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

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

哦,它只能在VB中使用,而不能在C#中使用

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.