Compartir a través de


Conservar el espacio en blanco al serializar (LINQ to XML)

En este artículo se describe cómo controlar el espacio en blanco al serializar un árbol XML.

Un caso muy común es aquel en el que se leen datos XML con sangría, se crea un árbol XML en memoria sin ningún nodo de texto con espacios en blanco (es decir, sin conservar los espacios en blanco), se realizan ciertas operaciones sobre el XML y este se guarda con sangría. Al serializar el XML con formato, solo se conserva un espacio en blanco significativo en el árbol XML. Este es el comportamiento predeterminado para LINQ to XML.

Otro escenario común es leer y modificar XML que ya ha sido indentado intencionadamente. Es posible que no desee modificar esta sangría de ninguna forma. Para ello en LINQ to XML, se conservan los espacios en blanco al cargar o analizar el XML y deshabilitar el formato al serializar el XML.

Comportamiento del espacio en blanco de métodos que serializan árboles XML

Los métodos siguientes en las XElement clases y XDocument serializan un árbol XML. Puede serializar un árbol XML en un archivo, un TextReader o un XmlReader. El método ToString se serializa como una cadena.

Si el método no recibe un SaveOptions como argumento, el método realizará un formato (sangría) del XML serializado. En este caso, se descartan todos los espacios en blanco insignificantes del árbol XML.

Si el método toma SaveOptions como argumento, puede especificar que el método no formatee (sangría) el XML serializado. En este caso, se conserva todo el espacio en blanco del árbol XML.

Intercambio bidireccional de XML con entidades de retorno de carro

La conservación del espacio en blanco que se describe en este artículo es diferente del intercambio bidireccional de XML. Cuando XML contiene entidades de retorno de carro (
), la serialización estándar de LINQ a XML podría no conservarlas de una manera que permita un intercambio bidireccional perfecto.

Considere el siguiente XML de ejemplo que contiene entidades de retorno de carro:

<x xml:space="preserve">a&#xD;
b
c&#xD;</x>

Al analizar este XML con XDocument.Parse(), el valor del elemento raíz se convierte en "a\r\nb\nc\r". Sin embargo, si reserializa utilizando métodos de LINQ a XML, no se crean entidades con los retornos de carro:

string xmlWithCR = """
    <x xml:space="preserve">a
    b
    c
</x>
    """;

XDocument doc = XDocument.Parse(xmlWithCR);
Console.WriteLine($"Original parsed value: {string.Join("", doc.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
// Output: a\r\nb\nc\r

string reserialized = doc.ToString(SaveOptions.DisableFormatting);
Console.WriteLine($"Reserialized XML: {reserialized}");
// Output: <x xml:space="preserve">a
// b
// c</x>

XDocument reparsed = XDocument.Parse(reserialized);
Console.WriteLine($"Reparsed value: {string.Join("", reparsed.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
// Output: a\nb\nc\n

Los valores son diferentes: el original era "a\r\nb\nc\r" pero después del proceso de ida y vuelta se convierte en "a\nb\nc\n".

Solución: Uso de XmlWriter con NewLineHandling.Entitize

Para lograr un verdadero intercambio bidireccional de XML que conserve las entidades de retorno de carro, use XmlWriter con NewLineHandling establecido en Entitize:

string xmlWithCR = """
    <x xml:space="preserve">a
    b
    c
</x>
    """;

XDocument doc = XDocument.Parse(xmlWithCR);

// Create XmlWriter settings with NewLineHandling.Entitize
XmlWriterSettings settings = new XmlWriterSettings
{
    NewLineHandling = NewLineHandling.Entitize,
    OmitXmlDeclaration = true
};

// Serialize using XmlWriter
using StringWriter stringWriter = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(stringWriter, settings))
{
    doc.WriteTo(writer);
}

string roundtrippedXml = stringWriter.ToString();
Console.WriteLine($"Roundtripped XML: {roundtrippedXml}");
// Output: <x xml:space="preserve">a
// b
// c
</x>

// Verify roundtripping preserves the original value
XDocument roundtrippedDoc = XDocument.Parse(roundtrippedXml);
bool valuesMatch = doc.Root!.Value == roundtrippedDoc.Root!.Value;
Console.WriteLine($"Values match after roundtripping: {valuesMatch}");

Cuando necesite conservar las entidades de retorno de carro para la conversión bidireccional de XML, use XmlWriter con XmlWriterSettings adecuados en lugar de los métodos de serialización integrados de LINQ a XML.

Para obtener más información sobre XmlWriter y su configuración, vea System.Xml.XmlWriter.