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

中寻找一个泛型方法。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 # 的人,我很抱歉——我并不在乎我使用哪种语言,但是我想让正则表达式是静态的,你们不能在 C # 中不在方法之外声明它,所以这将是 VB.Net

终于,我们还在一起。我工作的地方是 Net 2.0,但是如果有人能够将最终产品转换成字符串类的扩展方法,那也会非常酷。

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

130109 次浏览

过去我曾经使用过 HttpUtility。HtmlEncode 为 xml 编码文本。实际上,它执行相同的任务。我还没遇到任何问题,但这并不意味着我以后不会遇到。顾名思义,它是为 HTML 而生的,而不是 XML。

您可能已经阅读了它,但是在 xml 编码和解码上使用了 这里有一篇文章

编辑: 当然,如果使用 xmlwriter 或新的 XElement 类之一,就可以完成这种编码。实际上,您可以只获取文本,将其放在一个新的 XElement 实例中,然后返回字符串(。Tostring)元素的版本。我听说 安全元素。转义也将执行与您的实用工具方法相同的任务,但是还没有阅读或使用它。

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

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

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

安全元素。转义

记录在案的 给你

XmlTextWriter.WriteString()负责逃跑。

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

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

服务器系统。安全。安全元素。转义似乎都忽略非法 XML 字符,而 XmlWriter. WriteString在遇到非法字符时抛出 异常(除非您禁用该检查,否则它将忽略它们)。库函数的概述可以在 给你中找到。

编辑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
}

单元测试和完整的代码可以找到 给你

微软 System.Web.dll 中的 AntiXss 库 AntiXssEncoder 类提供了以下方法:

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

它也有 HTML:

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

太棒了,我只能说这么多。

下面是一个 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;"}}

在.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,我建议使用 迈克尔 · 克罗帕特的回答

您可以使用内置类 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 的单行解决方案。我在一个非常小的工具中使用它。我不需要它第二次,所以我保持这种方式。(肮脏的道格)

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

哦,而且它只能在 VB 中工作,不能在 C # 中工作

如果您认真处理无效字符的 所有(而不仅仅是几个“ 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 编码一样。