Jaki jest najprostszy sposób uzyskania kodu XML z wcięciem i podziałami wierszy z XmlDocument?

106

Kiedy tworzę XML od podstaw XmlDocument, OuterXmlwłaściwość ma już wszystko ładnie wcięte z podziałami linii. Jednakże, jeśli użyję LoadXmljakiegoś bardzo „skompresowanego” XML-a (bez podziałów wierszy lub wcięć), wtedy wynik działania OuterXmlpozostaje taki. Więc ...

Jaki jest najprostszy sposób uzyskania upiększonych danych wyjściowych XML z wystąpienia programu XmlDocument?

Neil C. Obremski
źródło

Odpowiedzi:

209

Na podstawie innych odpowiedzi przyjrzałem się XmlTextWriteri opracowałem następującą metodę pomocniczą:

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();
}

To trochę więcej kodu, niż się spodziewałem, ale działa po prostu brzoskwiniowo.

Neil C. Obremski
źródło
5
Można nawet rozważyć utworzenie metody narzędzia jako metody rozszerzającej do klasy XmlDocument.
Opozycyjny
5
Co dziwne, dla mnie nie robi to nic poza ustawieniem kodowania nagłówka xml na UTF-16. O dziwo, robi to, nawet jeśli wyraźnie settings.Encoding = Encoding.UTF8;
ustawię
3
Problem z kodowaniem można rozwiązać, używając znaku MemoryStream+ StreamWriterz określonym kodowaniem zamiast znaku StringBuilderi uzyskując tekst za pomocą enc.GetString(memstream.GetBuffer(), 0, (int)memstream.Length);. Jednak wynik końcowy nadal nie jest w żaden sposób sformatowany. Czy może to być związane z tym, że zaczynam od przeczytanego dokumentu, który ma już formatowanie? Chcę tylko, żeby sformatowano również moje nowe węzły.
Nyerguds
2
Kusi mnie, aby zmodyfikować "\r\n"to Environment.Newline.
Pharap
2
doc.PreserveWhitespacenie powinna być ustawiona na true. W przeciwnym razie zakończy się niepowodzeniem, jeśli zawiera już częściowe wcięcie.
Master DJon
48

Zgodnie z adaptacją z bloga Eriki Ehrli powinno to wystarczyć:

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
źródło
10
zamknięcie usinginstrukcji spowoduje automatyczne zamknięcie pisarza po Dispose()wywołaniu.
Tyler Lee
3
Dla mnie to tylko wcięcie jednej linii. Nadal mam dziesiątki innych wierszy, które nie są wcięte.
C Johnson,
41

Lub nawet łatwiej, jeśli masz dostęp do 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
źródło
bardzo dobrze! Kciuki w górę przewaga nad akceptowaną odpowiedzią jest taka, że ​​nie wygeneruje komentarza XML, więc działa lepiej dla fragmentu XML
Umar Farooq Khawaja
3
Co dziwne, usuwa to <?xml ...?>i <!DOCTYPE ...>z pliku XML. OK dla fragmentu, ale niepożądane dla pełnego dokumentu.
Jesse Chisholm
To jedyny sposób, który u mnie zadziałał. Wszystkie inne metody używające xmltextwriter, Formatting = Formatting.Indented i XmlWriterSettings NIE przeformatowują tekstu, ale ta metoda tak.
kexx
16

Krótsza wersja metody rozszerzenia

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
źródło
Działa to bardzo dobrze i nie wymaga tworzenia niepotrzebnych plików na dysku
Zain Rizvi
13

Jeśli powyższa metoda Beautify jest wywoływana dla elementu, XmlDocumentktóry już zawiera XmlProcessingInstructionwęzeł podrzędny, generowany jest następujący wyjątek:

Nie można zapisać deklaracji XML. Metoda WriteStartDocument już go zapisała.

To jest moja zmodyfikowana wersja oryginalnej, aby pozbyć się wyjątku:

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();
    }
}

U mnie teraz działa, prawdopodobnie musiałbyś przeskanować wszystkie węzły potomne XmlProcessingInstruction, a nie tylko pierwszy?


Aktualizacja kwiecień 2015:

Ponieważ miałem inny przypadek, w którym kodowanie było nieprawidłowe, szukałem sposobu na wymuszenie UTF-8 bez BOM. Znalazłem ten post na blogu i utworzyłem na jego podstawie funkcję:

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
źródło
nie zadziała, jeśli umieścisz sekcję cdata wewnątrz węzła nadrzędnego, a przed węzłem podrzędnym
Sasha Bond
2
MemoryStream nie wydaje się być potrzebny, przynajmniej z mojej strony. W ustawieniach ustawiam: Encoding = Encoding.UTF8iOmitXmlDeclaration = true
Master DJ na
7
XmlTextWriter xw = new XmlTextWriter(writer);
xw.Formatting = Formatting.Indented;
benPearce
źródło
5
    public static string FormatXml(string xml)
    {
        try
        {
            var doc = XDocument.Parse(xml);
            return doc.ToString();
        }
        catch (Exception)
        {
            return xml;
        }
    }
przerobiony
źródło
Poniższa odpowiedź zdecydowanie przydałaby się z jakimś wyjaśnieniem, jednak zadziałała i jest znacznie prostsza niż inne rozwiązania.
CarlR
Wygląda na to, że musisz zaimportować zestaw system.link.XML, aby działał na PS 3.
CarlR
2

Prostym sposobem jest użycie:

writer.WriteRaw(space_char);

Podobnie jak w tym przykładowym kodzie, ten kod jest tym, czego użyłem do utworzenia struktury podobnej do widoku drzewa przy użyciu 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();

            }

        }

W ten sposób możesz dodawać znaki tabulacji lub podziały wierszy w zwykły sposób, tj. \ T lub \ n

Munim Dibosh
źródło
1

Wdrażając zamieszczone tutaj sugestie, miałem problem z kodowaniem tekstu. Wygląda na to, że kodowanie XmlWriterSettingsjest ignorowane i zawsze zastępowane przez kodowanie strumienia. W przypadku używania a StringBuilderjest to zawsze kodowanie tekstu używane wewnętrznie w C #, a mianowicie UTF-16.

Oto wersja, która obsługuje również inne kodowania.

WAŻNA UWAGA: Formatowanie jest całkowicie ignorowane, jeśli XMLDocumentobiekt ma preserveWhitespacewłączoną właściwość podczas ładowania dokumentu. Przez chwilę byłem zaskoczony, więc nie włączaj tego.

Mój ostateczny kod:

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);
    }
}

Spowoduje to zapisanie sformatowanego pliku XML na dysku z podanym kodowaniem tekstu.

Nyerguds
źródło
1

Jeśli masz ciąg XML, a nie dokument gotowy do użycia, możesz to zrobić w ten sposób:

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
źródło
1

Bardziej uproszczone podejście oparte na zaakceptowanej odpowiedzi:

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(); 
}

Ustawienie nowej linii nie jest konieczne. Znaki wcięcia mają również domyślne dwie spacje, więc wolałem też ich nie ustawiać.

dijoe
źródło