Escape de cadena en XML

90

¿Existe alguna función de C # que pueda usarse para escapar y anular el escape de una cadena, que podría usarse para completar el contenido de un elemento XML?

Estoy usando VSTS 2008 + C # + .Net 3.0.

EDITAR 1: Estoy concatenando un archivo XML simple y corto y no uso la serialización, por lo que necesito escapar explícitamente el carácter XML a mano, por ejemplo, necesito a<bingresar <foo></foo>, así que necesito una cadena de escape a<by ponerla en el elemento foo.

George2
fuente
15
Más corto que puedo pensar:new XText(unescaped).ToString()
sehe
3
Para cualquier otra persona que se encuentre con esto, he descubierto que esta es la mejor respuesta: stackoverflow.com/a/5304827/1224069
Philip Pittle

Respuestas:

74
public static string XmlEscape(string unescaped)
{
    XmlDocument doc = new XmlDocument();
    XmlNode node = doc.CreateElement("root");
    node.InnerText = unescaped;
    return node.InnerXml;
}

public static string XmlUnescape(string escaped)
{
    XmlDocument doc = new XmlDocument();
    XmlNode node = doc.CreateElement("root");
    node.InnerXml = escaped;
    return node.InnerText;
}
Darin Dimitrov
fuente
5
Ni siquiera necesita agregar el elemento al documento. Sin embargo, todavía diría que es mejor no intentar hacer esto en primer lugar; parece que George se está ganando el trabajo haciendo las cosas a mano ...
Jon Skeet
15
Realmente no me gusta esta respuesta porque es demasiado pesada. XmlDocument usará XmlReader / XmlWriter para hacer el trabajo real, así que ¿por qué no ir al grano y evitar ese DOM pesado?
Steven Sudit
7
@Will, el OP solicitó una función que escape a un texto que podría colocarse en un elemento XML y no en un atributo. Mi función no escapa a las comillas simples o dobles porque se pueden colocar en elementos XML.
Darin Dimitrov
5
@darin buen punto, y uno que debería ser enfatizado. Estoy satisfecho con el resultado de esta conversación y retiro mis reservas. Buen día señor.
1
Me pregunto si HttpUtility.HtmlEncodede System.Webseguridad se podría utilizar?
Pooven
126

SecurityElement.Escape (cadenas)

Dana Holt
fuente
8
Esta respuesta escapa a las comillas, a diferencia de la respuesta seleccionada.
1
Esta respuesta no parece funcionar con caracteres no válidos como
pirateado el
16
¿Y cómo se deshace?
Gondy
2
Esta respuesta está incompleta. Solo responde la mitad de la pregunta.
Brian Webster
1
De acuerdo con los comentarios anteriores: incompletos y no 100% precisos.
G. Stoynev
38

EDITAR: Dice "Estoy concatenando un archivo XML simple y corto y no uso la serialización, por lo que necesito escapar explícitamente del carácter XML a mano".

Le recomiendo encarecidamente que no lo haga a mano. Use las API XML para hacerlo todo por usted: lea los archivos originales, combine los dos en un solo documento como lo necesite (probablemente quiera usarlo XmlDocument.ImportNode) y luego escríbalo nuevamente. No desea escribir sus propios analizadores / formateadores XML. La serialización es algo irrelevante aquí.

Si nos puede dar un ejemplo breve pero completo de exactamente lo que está tratando de hacer, probablemente podamos ayudarlo a evitar tener que preocuparse por escapar en primer lugar.


Respuesta original

No está del todo claro lo que quiere decir, pero normalmente las API XML lo hacen por usted. Establece el texto en un nodo y automáticamente escapará de todo lo que necesite. Por ejemplo:

Ejemplo de LINQ to XML:

using System;
using System.Xml.Linq;

class Test
{
    static void Main()
    {
        XElement element = new XElement("tag",
                                        "Brackets & stuff <>");

        Console.WriteLine(element);
    }
}

Ejemplo de DOM:

using System;
using System.Xml;

class Test
{
    static void Main()
    {
        XmlDocument doc = new XmlDocument();
        XmlElement element = doc.CreateElement("tag");
        element.InnerText = "Brackets & stuff <>";
        Console.WriteLine(element.OuterXml);
    }
}

Salida de ambos ejemplos:

<tag>Brackets &amp; stuff &lt;&gt;</tag>

Eso es asumiendo que desea que XML escape, por supuesto. Si no es así, publique más detalles.

Jon Skeet
fuente
Gracias Jon, he puesto más detalles en la sección EDIT 1 de mi publicación original. Le agradecería que me pudiera dar algunos comentarios y consejos. :-)
George2
"después de escapar XML" - ¿quieres decir? ¿Podría hablar en otras palabras por favor? El inglés no es mi lengua materna. :-)
George2
Hola Jon, ¿cómo deshacer el escape del formato XML al formato de cadena normal, es decir, de la entrada "Corchetes & amp; cosas & lt; & gt;", obtenemos la salida "Corchetes & cosas <>"?
George2
2
@ George2: le pide al XElement su valor, o al XmlElement su InnerText.
Jon Skeet
25

Gracias a @sehe por el escape de una línea:

var escaped = new System.Xml.Linq.XText(unescaped).ToString();

Le agrego el un-escape de una línea:

var unescapedAgain = System.Xml.XmlReader.Create(new StringReader("<r>" + escaped + "</r>")).ReadElementString();
Keith Robertson
fuente
XText no escapa a las comillas.
Mert Gülsoy
9

George, es simple. Utilice siempre las API XML para manejar XML. Ellos hacen todo el escape y desaparición por ti.

Nunca cree XML agregando cadenas.

John Saunders
fuente
Palabras por las cuales vivir. Hay muchas opciones de API XML disponibles, pero en lo único en lo que deberíamos estar de acuerdo es en que la concatenación manual de cadenas no es aceptable.
Steven Sudit
Aunque generalmente estoy de acuerdo con esto, puede haber casos muy raros en los que sea necesario el escape manual. Por ejemplo, al crear documentación XML con Roslyn.
svick
@svick: ¿por qué no crear el XML usando LINQ to XML y luego usar .ToString ()?
John Saunders
@JohnSaunders, porque Roslyn tiene su propio conjunto de clases XML, como XmlElementSyntax. Y también es complicado por el hecho de que también necesita generar el ///. Y no puedo generar cada línea por separado XObject, porque eso no funcionaría para etiquetas multilínea.
svick
1
@svick: genere el xml, todo en una línea, péguelo ///al frente y luego vuelva a formatear el código. No es un gran problema, y ​​ciertamente es un caso de esquina. Si es absolutamente necesario, estoy seguro de que puede crear una costumbre XmlWriterpara hacer saltos de línea y espacios en blanco de la forma que desee, pero colocándolos ///delante de nuevas líneas. Alternativamente, use un XSLT para imprimir de forma bonita el XML. Pero en cualquier caso, XML debería ser generado por una API XML.
John Saunders
5

Y si desea, como yo cuando encontré esta pregunta, escapar de los nombres de nodo XML, como por ejemplo al leer de una serialización XML, use la forma más fácil:

XmlConvert.EncodeName(string nameToEscape)

También escapará espacios y cualquier carácter no válido para elementos XML.

http://msdn.microsoft.com/en-us/library/system.security.securityelement.escape%28VS.80%29.aspx

Charlie Brown
fuente
Creo, según las preguntas, que solo quieren texto interior. Su solución funcionará, pero es algo exagerada, ya que está destinada a manejar cosas como nombres de elementos y atributos. \
Sean Duggan
Bueno, llegué aquí tratando de escapar de los nombres de los nodos y pensé que mis hallazgos podrían ayudar a cualquiera en el futuro. Tampoco veo cuál es la "exageración", pero está bien. ;)
CharlieBrown
Oh, es información útil. :) Me imaginé que señalaría que una de las razones por las que quizás no te hayan votado a favor es porque la gente podría sentir que no estás respondiendo la pregunta en cuestión.
Sean Duggan
El enlace conduce a documentos para SecurityElement.Escape (String), ¿fue intencional? XmlConvert.EncodeName (String) tiene su propia página. Sé que han pasado algunos años desde que se preguntó esto, pero ¿cómo sé cuál usar? ¿No hacen lo mismo pero de diferentes formas?
micnil
4

ADVERTENCIA: Nigromante

Aún así, la respuesta de Darin Dimitrov + System.Security.SecurityElement.Escape (string s) no está completa.

En XML 1.1, la forma más sencilla y segura es simplemente codificar TODO.
Como &#09;para \ t.
No se admite en absoluto en XML 1.0.
Para XML 1.0, una posible solución es codificar en base 64 el texto que contiene los caracteres.

//string EncodedXml = SpecialXmlEscape("привет мир");
//Console.WriteLine(EncodedXml);
//string DecodedXml = XmlUnescape(EncodedXml);
//Console.WriteLine(DecodedXml);
public static string SpecialXmlEscape(string input)
{
    //string content = System.Xml.XmlConvert.EncodeName("\t");
    //string content = System.Security.SecurityElement.Escape("\t");
    //string strDelimiter = System.Web.HttpUtility.HtmlEncode("\t"); // XmlEscape("\t"); //XmlDecode("&#09;");
    //strDelimiter = XmlUnescape("&#59;");
    //Console.WriteLine(strDelimiter);
    //Console.WriteLine(string.Format("&#{0};", (int)';'));
    //Console.WriteLine(System.Text.Encoding.ASCII.HeaderName);
    //Console.WriteLine(System.Text.Encoding.UTF8.HeaderName);


    string strXmlText = "";

    if (string.IsNullOrEmpty(input))
        return input;


    System.Text.StringBuilder sb = new StringBuilder();

    for (int i = 0; i < input.Length; ++i)
    {
        sb.AppendFormat("&#{0};", (int)input[i]);
    }

    strXmlText = sb.ToString();
    sb.Clear();
    sb = null;

    return strXmlText;
} // End Function SpecialXmlEscape

XML 1.0:

public static string Base64Encode(string plainText)
{
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    return System.Convert.ToBase64String(plainTextBytes);
}

public static string Base64Decode(string base64EncodedData)
{
    var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
    return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
Stefan Steiger
fuente
Entonces, en XML 1.1, ¿cómo se escapa de todo?
Philip Pittle
@Philip Pittle: Ver SpecialXmlEscape
Stefan Steiger
4

Otra toma basada en la respuesta de John Skeet que no devuelve las etiquetas :

void Main()
{
    XmlString("Brackets & stuff <> and \"quotes\"").Dump();
}

public string XmlString(string text)
{
    return new XElement("t", text).LastNode.ToString();
} 

Esto devuelve solo el valor pasado, en formato codificado XML:

Brackets &amp; stuff &lt;&gt; and "quotes"
Rick Strahl
fuente
3

Las siguientes funciones harán el trabajo. No probé contra XmlDocument, pero supongo que esto es mucho más rápido.

public static string XmlEncode(string value)
{
    System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings 
    {
        ConformanceLevel = System.Xml.ConformanceLevel.Fragment
    };

    StringBuilder builder = new StringBuilder();

    using (var writer = System.Xml.XmlWriter.Create(builder, settings))
    {
        writer.WriteString(value);
    }

    return builder.ToString();
}

public static string XmlDecode(string xmlEncodedValue)
{
    System.Xml.XmlReaderSettings settings = new System.Xml.XmlReaderSettings
    {
        ConformanceLevel = System.Xml.ConformanceLevel.Fragment
    };

    using (var stringReader = new System.IO.StringReader(xmlEncodedValue))
    {
        using (var xmlReader = System.Xml.XmlReader.Create(stringReader, settings))
        {
            xmlReader.Read();
            return xmlReader.Value;
        }
    }
}
Ramazan Binarbasi
fuente
3

Usando una biblioteca de terceros ( Newtonsoft.Json ) como alternativa:

public static string XmlEncode(string unescaped)
{
    if (unescaped == null) return null;
    return JsonConvert.SerializeObject(unescaped); ;
}

public static string XmlDecode(string escaped)
{
    if (escaped == null) return null;
    return JsonConvert.DeserializeObject(escaped, typeof(string)).ToString();
}

Ejemplo:

a<b <==> "a&lt;b"

<foo></foo> <==> "foo&gt;&lt;/foo&gt;"

Abberdeen
fuente