[英]Best way to encode text data for 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 2.0 上,但是如果有人可以将最终产品转化为字符串类的扩展方法,那也很酷。
更新前几个响应表明.Net 确实有内置的方法来做到这一点。 但是现在我已经开始了,我有点想完成我的 EncodeForXml() 方法只是为了好玩,所以我仍在寻找改进的想法。 值得注意的是:应该编码为实体的更完整的字符列表(可能存储在列表/映射中),并且比对串行不可变字符串执行 .Replace() 获得更好的性能。
根据您对输入的了解程度,您可能必须考虑到并非所有 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
}
单元测试和完整代码可以在这里找到。
SecurityElement.Escape
记录在这里
过去,我使用 HttpUtility.HtmlEncode 为 xml 编码文本。 它执行相同的任务,真的。 我还没有遇到任何问题,但这并不是说我将来不会。 顾名思义,它是为 HTML 而不是 XML 制作的。
您可能已经阅读过它,但这里有一篇关于 xml 编码和解码的文章。
编辑:当然,如果您使用 xmlwriter 或新的 XElement 类之一,则此编码已为您完成。 实际上,您可以只获取文本,将其放在新的 XElement 实例中,然后返回元素的字符串 (.tostring) 版本。 我听说SecurityElement.Escape也将执行与您的实用程序方法相同的任务,但尚未阅读或使用它。
EDIT2:忽略我对 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 <want> to & encode this for XML
事实证明,这种方法没有编码一些它应该编码的东西(比如引号)。
SecurityElement.Escape
( workmad3 的答案)似乎在这方面做得更好,它包含在早期版本的 .net 中。
如果您不介意 3rd 方代码并希望确保没有非法字符进入您的 XML,我会推荐Michael Kropat 的答案。
XmlTextWriter.WriteString()
进行转义。
System.XML 为您处理编码,因此您不需要这样的方法。
如果这是一个 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 内容插入到属性值中。
如果您认真对待处理所有无效字符(不仅仅是少数“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  else  <script>alert('123');</script>
// Repeat the last 2 lines to escape additional strings.
重要的是要知道XmlConvert.EncodeName()
是不合适的,因为那是实体/标签名称,而不是值。 当您需要 Html 编码时,使用它就像 Url 编码一样。
您可以使用内置类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#
杰出的! 这就是我能说的。
这是更新代码的 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, """}, {"&"c, "&"}, {"'"c, "'"}, {"<"c, "<"}, {">"c, ">"}}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.