La mejor forma de codificar datos de texto para XML

Estaba buscando un método genérico en .Net para codificar una cadena para usar en un elemento o atributo Xml, y me sorprendió cuando no encontré uno inmediatamente. Entonces, antes de ir mucho más allá, ¿podría estar perdiendo la función incorporada?

Asumiendo por un momento que realmente no existe, estoy armando mi propio EncodeForXml(string data) genérico EncodeForXml(string data) , y estoy pensando en la mejor manera de hacerlo.

Los datos que estoy usando que me sugirieron todo esto podrían contener caracteres incorrectos como &, <, ", etc. También podría contener en ocasiones las entidades correctamente escapadas: & amp ;, & lt ;, & & quot ;, lo que significa que simplemente usa un La sección de CDATA puede no ser la mejor idea. Eso parece algo como klunky, preferiría terminar con un buen valor de cadena que pueda usarse directamente en el xml.

He usado una expresión regular en el pasado para simplemente atrapar símbolos malos, y estoy pensando en usarlos para atraparlos en este caso, así como en el primer paso, y luego hacer un simple reemplazo para otros personajes.

Entonces, ¿podría optimizarse aún más sin hacerlo demasiado complejo, y hay algo que me falta? :

 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("", "gt;") End Function 

Lo siento por todo lo que C # -sólo amigos- realmente no me importa qué idioma utilizo, pero quería hacer que Regex esté estática y no se puede hacer eso en C # sin declararlo fuera del método, así que esto será VB .Red

Finalmente, todavía estamos en .Net 2.0 donde trabajo, pero si alguien pudiera tomar el producto final y convertirlo en un método de extensión para la clase de cadena, también sería genial.

Actualización Las primeras respuestas indican que .Net sí tiene formas integradas de hacerlo. Pero ahora que he comenzado, quiero terminar mi método EncodeForXml () solo por diversión, así que aún estoy buscando ideas para mejorar. Notablemente: una lista más completa de caracteres que deberían codificarse como entidades (tal vez almacenadas en una lista / mapa), y algo que obtiene un mejor rendimiento que hacer un .Replace () en cadenas inmutables en serie.

System.XML maneja la encoding por usted, por lo que no necesita un método como este.

Dependiendo de cuánto sepa acerca de la entrada, es posible que deba tener en cuenta que no todos los caracteres Unicode son caracteres XML válidos .

Tanto Server.HtmlEncode como System.Security.SecurityElement.Escape parecen ignorar los caracteres XML ilegales, mientras que System.XML.XmlWriter.WriteString arroja una ArgumentException cuando encuentra caracteres ilegales (a menos que deshabilite esa comprobación en cuyo caso los ignora). Una descripción general de las funciones de la biblioteca está disponible aquí .

Edit 2011/8/14: al ver que al menos algunas personas han consultado esta respuesta en los últimos años, decidí reescribir completamente el código original, que tenía numerosos problemas, incluido el mal manejo de UTF-16 .

 using System; using System.Collections.Generic; using System.IO; using System.Linq; ///  /// Encodes data so that it can be safely embedded as text in XML documents. ///  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(); } } /// The data to be encoded in UTF-16 format. /// 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. public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) { _source = source; _filterIllegalChars = filterIllegalChars; } readonly Queue _buf = new Queue(); 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 Entities = new Dictionary { { '"', """ }, { '&', "&"}, { '\'', "'" }, { '<', "<" }, { '>', ">" }, }; // 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 } 

Las pruebas unitarias y el código completo se pueden encontrar aquí .

SecurityElement.Escape

documentado aquí

En el pasado, he usado HttpUtility.HtmlEncode para codificar texto para xml. Realiza la misma tarea, realmente. Aún no me he encontrado con ningún problema, pero eso no quiere decir que no lo haré en el futuro. Como su nombre lo indica, fue hecho para HTML, no para XML.

Probablemente ya lo haya leído, pero aquí hay un artículo sobre encoding y deencoding xml.

EDITAR: por supuesto, si usa un xmlwriter o una de las nuevas clases de XElement, esta encoding se hace por usted. De hecho, puede tomar el texto, colocarlo en una nueva instancia de XElement y luego devolver la versión de cadena (.tostring) del elemento. He oído que SecurityElement.Escape también realizará la misma tarea que su método de utilidad, pero no ha leído mucho ni lo ha usado.

EDIT2: Haga caso omiso de mi comentario sobre XElement, ya que todavía está en 2.0

La clase AntiXssEncoder Class de Microsoft en System.Web.dll tiene métodos para esto:

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

también tiene HTML:

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

En .net 3.5+

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

Te dio:

I <want> to & encode this for XML

Resulta que este método no codifica algunas cosas que debería (como comillas).

SecurityElement.Escape ( la respuesta de workmad3 ) parece hacer un mejor trabajo con esto y está incluido en versiones anteriores de .net.

Si no te importa el código de terceros y quieres asegurarte de que ningún personaje ilegal lo incluya en tu XML, recomendaría la respuesta de Michael Kropat .

XmlTextWriter.WriteString() hace el escape.

Si esta es una aplicación ASP.NET, ¿por qué no usar Server.HtmlEncode ()?

Este podría ser el caso donde podría beneficiarse al usar el método WriteCData.

 public override void WriteCData(string text) Member of System.Xml.XmlTextWriter Summary: Writes out a  block containing the specified text. Parameters: text: Text to place inside the CDATA block. 

Un ejemplo simple se vería así:

 writer.WriteStartElement("name"); writer.WriteCData(""); writer.WriteFullEndElement(); 

El resultado se ve así:

 ]]> 

Al leer los valores de nodo, XMLReader elimina automáticamente la parte de CData del texto interno para que no tenga que preocuparse por ello. La única pega es que tienes que almacenar los datos como un valor de texto interno a un nodo XML. En otras palabras, no puede insertar contenido de CData en un valor de atributo.

¡Brillante! Esto es todo lo que puedo decir.

Aquí hay una variante de VB del código actualizado (no en una clase, solo una función) que limpiará y también desinfectará el 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, ">"}} 

Puede usar la clase incorporada XAttribute , que maneja la encoding automáticamente:

 using System.Xml.Linq; XDocument doc = new XDocument(); List attributes = new List(); 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(); 

Aquí hay una solución de línea única que usa XElements. Lo uso en una herramienta muy pequeña. No lo necesito por segunda vez, así que lo mantengo de esta manera. (Es raro doug)

 StrVal = (>END).ToString().Replace("END", "") 

Ah, y solo funciona en VB, no en C #

Si realmente quiere manejar todos los caracteres no válidos (no solo los pocos “html”) y tiene acceso a System.Xml , esta es la forma más sencilla de hacer una encoding Xml adecuada de los datos de valor :

 string theTextToEscape = "Something \x1d else \x1D "; var x = new XmlDocument(); x.LoadXml(""); // 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. 

Es importante saber que XmlConvert.EncodeName() no es apropiado, porque eso es para nombres de entidad / etiqueta, no valores. Usar eso sería como la encoding Url cuando necesitas codificar Html.