Pregunta

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 de inmediato. Entonces, antes de ir demasiado lejos, ¿podría perderme la función incorporada?

Suponiendo 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 hacerlo.

Los datos que estoy usando que provocaron que todo esto pudiera contener caracteres malos como & amp ;, < ;, " ;, etc. También podrían contener en ocasiones las entidades que se escaparon correctamente: & amp; amp ;, & amp; lt ;, y & amp; quot ;, lo que significa que usar una sección CDATA puede no ser la mejor idea. Eso parece un poco rudo de todos modos; Prefiero terminar con un buen valor de cadena que se puede usar directamente en el xml.

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

Entonces, ¿podría esto 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, "&amp;")

    return data.Replace("<", "&lt;").Replace("""", "&quot;").Replace(">", "gt;")
End Function

Perdón por todos ustedes, C #, solo amigos, no me importa el lenguaje que uso, pero quería hacer que Regex sea estático y no pueden hacerlo en C # sin declararlo fuera del método , así que 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 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 todavía estoy buscando ideas para mejorar. Notablemente: una lista más completa de caracteres que deberían codificarse como entidades (tal vez almacenados en una lista / mapa), y algo que obtenga un mejor rendimiento que hacer un .Replace () en cadenas inmutables en serie.

¿Fue útil?

Solución

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

Otros consejos

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 .

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

Editar 14/8/2011: al ver que al menos algunas personas han consultado esta respuesta en los últimos dos años, decidí reescribir completamente el código original, que tenía numerosos problemas, incluidos < a href = "https://stackoverflow.com/questions/1049947/should-utf-16-be-considered-harmful"> mal manejo de 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í .

SecurityElement.Escape

documentado aquí

En el pasado, he utilizado HttpUtility.HtmlEncode para codificar texto para xml. Realiza la misma tarea, realmente. Todavía 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 XML.

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

EDITAR: por supuesto, si utiliza un xmlwriter o una de las nuevas clases XElement, esta codificación se realiza por usted. De hecho, podría 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 leí ni usé mucho sobre él.

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

Microsoft biblioteca AntiXss Clase AntiXssEncoder 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 <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 como debería (como comillas).

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

Si no le importa el código de terceros y quiere asegurarse de que no haya caracteres ilegales en su XML, le recomendaría Michael Kropat's responder .

XmlTextWriter.WriteString() hace el escape.

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

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 sería el siguiente:

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

El resultado se ve así:

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

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

¡Brillante! Eso es todo lo que puedo decir.

Aquí hay una variante 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, "&quot;"}, {"&"c, "&amp;"}, {"'"c, "&apos;"}, {"<"c, "&lt;"}, {">"c, "&gt;"}}

Puede usar la clase integrada 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();

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

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

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

Si te tomas en serio el manejo de todos los caracteres no válidos (no solo los pocos " html " unos), y tienes 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 la codificación de URL cuando necesita codificar en HTML.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top