La mejor manera de codificar datos de texto para XML


Estaba buscando un método genérico en.Net para codificar una cadena para su uso en un elemento o atributo Xml, y me sorprendió cuando no encontré uno inmediatamente. Por lo tanto, antes de ir demasiado lejos, ¿podría faltar la función incorporada?

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

Los datos que estoy usando que incitaron todo esto podría contener malo caracteres como&,

He utilizado una expresión regular en el pasado para simplemente atrapar malos ampersands, y estoy pensando en usarlo para atraparlos en este caso, así como el primer paso, y luego hacer un reemplazo simple 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("<", "&lt;").Replace("""", "&quot;").Replace(">", "gt;")
End Function

Lo siento por todos ustedes, amigos de solo C# really Realmente no me importa qué lenguaje use, pero quería hacer la expresión regular estática y no pueden hacer eso en C# sin declararlo fuera del método, así que esto será VB.Net

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 string, que sería bastante genial también.

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

Author: Blorgbeard, 2008-10-01

13 answers

Sistema.XML maneja la codificación por usted, por lo que no necesita un método como este.

 5
Author: MusiGenesis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-04 04:03:33

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

Ambos Servidor.HtmlEncode y Sistema.Seguridad.Elemento de seguridad.Escape parece ignorar caracteres XML ilegales, mientras que Sistema.XML.XmlWriter.WriteString lanza una ArgumentException cuando encuentra caracteres ilegales (a menos que deshabilite esa comprobación en cuyo caso los ignora). Una visión general de funciones de biblioteca está disponible aquí.

Editar 2011/8/14: viendo 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, incluyendo terriblemente mal manejo 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
}

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

 74
Author: Michael Kropat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:37

Elemento de seguridad.Escape

Documentado aquí

 31
Author: workmad3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-21 21:42:52

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

Probablemente ya lo haya leído, pero aquí hay un artículo sobre codificación y decodificación xml.

EDITAR: Por supuesto, si utiliza un xmlwriter o una de las nuevas clases de XElement, esta codificación se realiza por usted. De hecho, podrías simplemente tome el texto, colóquelo en una nueva instancia de XElement, luego devuelva la cadena (.tostring) versión del elemento. He oído que Elemento de seguridad.Escape también realizará la misma tarea que su método de utilidad, pero no ha leído mucho sobre él ni lo ha usado.

EDIT2: Ignora mi comentario sobre XElement, ya que todavía estás en 2.0

 26
Author: Kilhoffer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-01 13:51:32

Microsoft Biblioteca AntiXSS AntiXssEncoder Class en el sistema.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)
 14
Author: Luke Quinane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-09 22:44:48

En .net 3.5+

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

Te da:

I &lt;want&gt; to &amp; 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 no haya caracteres ilegales en tu XML, recomendaría La respuesta de Michael Kropat.

 12
Author: Ronnie Overby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:35

XmlTextWriter.WriteString() hace el escape.

 5
Author: GSerg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-30 19:01:44

Si se trata de un ASP.NET aplicación ¿por qué no utilizar el servidor.HtmlEncode() ?

 3
Author: Kev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-01 13:46:07

Este podría ser el caso en el que podría beneficiarse del uso del método 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.

Un ejemplo simple se vería como el siguiente:

writer.WriteStartElement("name");
writer.WriteCData("<unsafe characters>");
writer.WriteFullEndElement();

El resultado se ve como:

<name><![CDATA[<unsafe characters>]]></name>

Al leer los valores del nodo, XmlReader elimina automáticamente la parte CData del texto interno para que no tenga que preocuparse por ello. El único problema es que debe almacenar los datos como un valor innerText en un nodo XML. En otras palabras, no se puede insertar contenido CData en un valor del atributo.

 3
Author: Dscoduc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-07 20:30:20

Brillante! Es todo lo que puedo decir.

Aquí hay una variante VB del código actualizado (no en una clase, solo en 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, "&quot;"}, {"&"c, "&amp;"}, {"'"c, "&apos;"}, {"<"c, "&lt;"}, {">"c, "&gt;"}}
 0
Author: nepaluz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-18 06:27:40

Puede usar la clase incorporada XAttribute , que maneja la codificación automáticamente:

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
Author: Cosmin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 11:29:51

Aquí hay una solución de una sola línea usando los XElements. Lo uso en una herramienta muy pequeña. No lo necesito una segunda vez así que lo mantengo así. (Es dirdy doug)

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

Oh y solo funciona en VB no en C#

 0
Author: Phillip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-30 09:55:02

Si se toma en serio el manejo de todos de 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 codificación Xml adecuada de datos de valor :

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.

Es importante saber que XmlConvert.EncodeName() no es apropiado, porque eso es para nombres de entidades/etiquetas, no para valores. Usar eso sería como codificar Url cuando necesitaras codificar Html.

 0
Author: Granger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-19 16:32:58