编码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
最后,我们仍然在.Net 2.0上工作,但是如果有人可以使用最终产品并将其转换为字符串类的扩展方法,那也将很酷。
更新前几个响应表明.Net确实具有内置的方法。但是,既然我已经开始,我有点想完成我的EncodeForXml()方法只是为了好玩,所以我仍在寻找改进的想法。值得注意的是:应该被编码为实体的更完整的字符列表(可能存储在列表/映射中),并且比对串行不可变字符串执行.Replace()可以获得更好的性能。
解决方案
过去,我曾使用HttpUtility.HtmlEncode为xml编码文本。实际上,它执行相同的任务。我还没有遇到任何问题,但这并不是说我将来不会。顾名思义,它是为HTML而设计的,而不是XML。
我们可能已经阅读过,但是这里是有关xml编码和解码的文章。
编辑:当然,如果我们使用xmlwriter或者新的XElement类之一,则将为我们完成此编码。实际上,我们可以只获取文本,将其放置在新的XElement实例中,然后返回该元素的字符串(.tostring)版本。我听说SecurityElement.Escape还将执行与实用程序方法相同的任务,但是还没有阅读或者使用过多。
EDIT2:忽略我对XElement的评论,因为我们仍在使用2.0
如果这是一个ASP.NET应用程序,为什么不使用Server.HtmlEncode()?
System.XML为我们处理编码,因此我们不需要这样的方法。
SecurityElement.Escape
记录在这里
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字符。
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
}
单元测试和完整的代码可以在这里找到。
System.Web.dll中的Microsoft AntiXss库AntiXssEncoder类具有用于以下目的的方法:
AntiXss.XmlEncode(string s) AntiXss.XmlAttributeEncode(string s)
它也具有HTML:
AntiXss.HtmlEncode(string s) AntiXss.HtmlAttributeEncode(string s)

