¿Cuál es la forma más sencilla de obtener XML sangrado con saltos de línea de XmlDocument?

105

Cuando construyo XML desde cero con XmlDocument , la OuterXmlpropiedad ya tiene todo bien sangrado con saltos de línea. Sin embargo, si llamo LoadXmla un XML muy "comprimido" (sin saltos de línea o sangría), la salida de OuterXmlpermanecerá así. Entonces ...

¿Cuál es la forma más sencilla de obtener una salida XML embellecida a partir de una instancia de XmlDocument ?

Neil C. Obremski
fuente

Respuestas:

209

Basado en las otras respuestas, busqué XmlTextWritery se me ocurrió el siguiente método de ayuda:

static public string Beautify(this XmlDocument doc)
{
    StringBuilder sb = new StringBuilder();
    XmlWriterSettings settings = new XmlWriterSettings
    {
        Indent = true,
        IndentChars = "  ",
        NewLineChars = "\r\n",
        NewLineHandling = NewLineHandling.Replace
    };
    using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
        doc.Save(writer);
    }
    return sb.ToString();
}

Es un poco más de código de lo que esperaba, pero funciona perfectamente.

Neil C. Obremski
fuente
5
Incluso podría considerar crear su método de utilidad como un método de extensión para la clase XmlDocument.
Oposición
5
Por extraño que parezca, para mí esto no hace nada más que configurar la codificación del encabezado xml en UTF-16. Por extraño que parezca, hace esto incluso si lo configuro explícitamentesettings.Encoding = Encoding.UTF8;
Nyerguds
3
El problema de codificación se puede resolver usando un MemoryStream+ StreamWritercon una codificación especificada en lugar de StringBuilder, y obteniendo el texto con enc.GetString(memstream.GetBuffer(), 0, (int)memstream.Length);. Sin embargo, el resultado final todavía no está formateado. ¿Podría estar relacionado con que estoy comenzando desde un documento leído que ya tiene formato? Solo quiero que mis nuevos nodos también estén formateados.
Nyerguds
2
Me siento tentado a modificar el "\r\n"a Environment.Newline.
Pharap
2
doc.PreserveWhitespaceno debe establecerse en verdadero. De lo contrario, falla si ya contiene una sangría parcial.
Master DJ el
48

Adaptado del blog de Erika Ehrli , esto debería hacerlo:

XmlDocument doc = new XmlDocument();
doc.LoadXml("<item><name>wrench</name></item>");
// Save the document to a file and auto-indent the output.
using (XmlTextWriter writer = new XmlTextWriter("data.xml", null)) {
    writer.Formatting = Formatting.Indented;
    doc.Save(writer);
}
DocMax
fuente
10
el cierre de la usingdeclaración cerrará automáticamente el escritor cuando Dispose()se llame.
Tyler Lee
3
Para mí, esto solo sangra una línea. Todavía tengo decenas de otras líneas sin sangría.
C Johnson
40

O incluso más fácil si tiene acceso a Linq

try
{
    RequestPane.Text = System.Xml.Linq.XElement.Parse(RequestPane.Text).ToString();
}
catch (System.Xml.XmlException xex)
{
            displayException("Problem with formating text in Request Pane: ", xex);
}
JFK
fuente
¡muy agradable! La ventaja del pulgar hacia arriba sobre la respuesta aceptada es que no producirá un comentario XML, por lo que funciona mejor para un fragmento XML
Umar Farooq Khawaja
3
Curiosamente, esto elimina el <?xml ...?>y el <!DOCTYPE ...>del XML. Está bien para un fragmento, pero no es deseable para un documento completo.
Jesse Chisholm
Esta es la única forma que funcionó para mí. Todos los demás métodos que usan xmltextwriter, Formatting = Formatting.Indented y XmlWriterSettings NO reformatea el texto, pero este método sí.
kexx
16

Una versión de método de extensión más corta

public static string ToIndentedString( this XmlDocument doc )
{
    var stringWriter = new StringWriter(new StringBuilder());
    var xmlTextWriter = new XmlTextWriter(stringWriter) {Formatting = Formatting.Indented};
    doc.Save( xmlTextWriter );
    return stringWriter.ToString();
}
Jonathan Mitchem
fuente
Esto funciona muy bien y no implica la creación de archivos innecesarios en el disco
Zain Rizvi
13

Si se llama al método Beautify anterior para un nodo XmlDocumentque ya contiene un XmlProcessingInstructionnodo secundario, se lanza la siguiente excepción:

No se puede escribir una declaración XML. El método WriteStartDocument ya lo ha escrito.

Esta es mi versión modificada de la original para deshacerme de la excepción:

private static string beautify(
    XmlDocument doc)
{
    var sb = new StringBuilder();
    var settings =
        new XmlWriterSettings
            {
                Indent = true,
                IndentChars = @"    ",
                NewLineChars = Environment.NewLine,
                NewLineHandling = NewLineHandling.Replace,
            };

    using (var writer = XmlWriter.Create(sb, settings))
    {
        if (doc.ChildNodes[0] is XmlProcessingInstruction)
        {
            doc.RemoveChild(doc.ChildNodes[0]);
        }

        doc.Save(writer);
        return sb.ToString();
    }
}

Funciona para mí ahora, probablemente necesitaría escanear todos los nodos secundarios para el XmlProcessingInstructionnodo, no solo el primero.


Actualización de abril de 2015:

Como tuve otro caso en el que la codificación era incorrecta, busqué cómo hacer cumplir UTF-8 sin BOM. Encontré esta publicación de blog y creé una función basada en ella:

private static string beautify(string xml)
{
    var doc = new XmlDocument();
    doc.LoadXml(xml);

    var settings = new XmlWriterSettings
    {
        Indent = true,
        IndentChars = "\t",
        NewLineChars = Environment.NewLine,
        NewLineHandling = NewLineHandling.Replace,
        Encoding = new UTF8Encoding(false)
    };

    using (var ms = new MemoryStream())
    using (var writer = XmlWriter.Create(ms, settings))
    {
        doc.Save(writer);
        var xmlString = Encoding.UTF8.GetString(ms.ToArray());
        return xmlString;
    }
}
Uwe Keim
fuente
no funcionará si coloca la sección cdata dentro del nodo principal y antes del nodo secundario
Sasha Bond
2
MemoryStream no parece ser necesario, al menos de mi lado. En la configuración configuré: Encoding = Encoding.UTF8yOmitXmlDeclaration = true
Master DJ el
7
XmlTextWriter xw = new XmlTextWriter(writer);
xw.Formatting = Formatting.Indented;
benPearce
fuente
5
    public static string FormatXml(string xml)
    {
        try
        {
            var doc = XDocument.Parse(xml);
            return doc.ToString();
        }
        catch (Exception)
        {
            return xml;
        }
    }
reescribió
fuente
La respuesta a continuación definitivamente podría funcionar con alguna explicación, sin embargo, funcionó para mí y es mucho más simple que las otras soluciones.
CarlR
Parece que necesita importar el ensamblado system.link.XML para que esto funcione en PS 3.
CarlR
2

Una forma sencilla es utilizar:

writer.WriteRaw(space_char);

Al igual que este código de muestra, este código es lo que usé para crear una estructura similar a una vista de árbol usando XMLWriter:

private void generateXML(string filename)
        {
            using (XmlWriter writer = XmlWriter.Create(filename))
            {
                writer.WriteStartDocument();
                //new line
                writer.WriteRaw("\n");
                writer.WriteStartElement("treeitems");
                //new line
                writer.WriteRaw("\n");
                foreach (RootItem root in roots)
                {
                    //indent
                    writer.WriteRaw("\t");
                    writer.WriteStartElement("treeitem");
                    writer.WriteAttributeString("name", root.name);
                    writer.WriteAttributeString("uri", root.uri);
                    writer.WriteAttributeString("fontsize", root.fontsize);
                    writer.WriteAttributeString("icon", root.icon);
                    if (root.children.Count != 0)
                    {
                        foreach (ChildItem child in children)
                        {
                            //indent
                            writer.WriteRaw("\t");
                            writer.WriteStartElement("treeitem");
                            writer.WriteAttributeString("name", child.name);
                            writer.WriteAttributeString("uri", child.uri);
                            writer.WriteAttributeString("fontsize", child.fontsize);
                            writer.WriteAttributeString("icon", child.icon);
                            writer.WriteEndElement();
                            //new line
                            writer.WriteRaw("\n");
                        }
                    }
                    writer.WriteEndElement();
                    //new line
                    writer.WriteRaw("\n");
                }

                writer.WriteEndElement();
                writer.WriteEndDocument();

            }

        }

De esta manera, puede agregar saltos de tabulación o de línea de la forma en que está acostumbrado, es decir, \ t o \ n

Munim Dibosh
fuente
1

Al implementar las sugerencias publicadas aquí, tuve problemas con la codificación del texto. Parece que la codificación del XmlWriterSettingsflujo se ignora y siempre se anula por la codificación de la secuencia. Cuando se usa a StringBuilder, esta es siempre la codificación de texto utilizada internamente en C #, es decir, UTF-16.

Así que aquí hay una versión que también admite otras codificaciones.

NOTA IMPORTANTE: El formato se ignora por completo si su XMLDocumentobjeto tiene su preserveWhitespacepropiedad habilitada al cargar el documento. Esto me dejó perplejo por un tiempo, así que asegúrese de no habilitarlo.

Mi código final:

public static void SaveFormattedXml(XmlDocument doc, String outputPath, Encoding encoding)
{
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.IndentChars = "\t";
    settings.NewLineChars = "\r\n";
    settings.NewLineHandling = NewLineHandling.Replace;

    using (MemoryStream memstream = new MemoryStream())
    using (StreamWriter sr = new StreamWriter(memstream, encoding))
    using (XmlWriter writer = XmlWriter.Create(sr, settings))
    using (FileStream fileWriter = new FileStream(outputPath, FileMode.Create))
    {
        if (doc.ChildNodes.Count > 0 && doc.ChildNodes[0] is XmlProcessingInstruction)
            doc.RemoveChild(doc.ChildNodes[0]);
        // save xml to XmlWriter made on encoding-specified text writer
        doc.Save(writer);
        // Flush the streams (not sure if this is really needed for pure mem operations)
        writer.Flush();
        // Write the underlying stream of the XmlWriter to file.
        fileWriter.Write(memstream.GetBuffer(), 0, (Int32)memstream.Length);
    }
}

Esto guardará el xml formateado en el disco, con la codificación de texto dada.

Nyerguds
fuente
1

Si tiene una cadena de XML, en lugar de un documento listo para usar, puede hacerlo de esta manera:

var xmlString = "<xml>...</xml>"; // Your original XML string that needs indenting.
xmlString = this.PrettifyXml(xmlString);

private string PrettifyXml(string xmlString)
{
    var prettyXmlString = new StringBuilder();

    var xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(xmlString);

    var xmlSettings = new XmlWriterSettings()
    {
        Indent = true,
        IndentChars = " ",
        NewLineChars = "\r\n",
        NewLineHandling = NewLineHandling.Replace
    };

    using (XmlWriter writer = XmlWriter.Create(prettyXmlString, xmlSettings))
    {
        xmlDoc.Save(writer);
    }

    return prettyXmlString.ToString();
}
theJerm
fuente
1

Un enfoque más simplificado basado en la respuesta aceptada:

static public string Beautify(this XmlDocument doc) {
    StringBuilder sb = new StringBuilder();
    XmlWriterSettings settings = new XmlWriterSettings
    {
        Indent = true
    };

    using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
        doc.Save(writer);
    }

    return sb.ToString(); 
}

No es necesario configurar la nueva línea. Los caracteres de sangría también tienen los dos espacios predeterminados, por lo que preferí no establecerlo también.

dijoe
fuente