使用XmlDocument获取具有缩进和换行的XML最简单的方法是什么?

122
当我使用XmlDocument从头构建XML时,OuterXml属性已经很好地缩进并换行。 但是,如果我在某些非常“压缩”的XML上调用LoadXml(没有换行或缩进),那么OuterXml的输出方式将保持原样。那么...最简单的方法是什么来自XmlDocument实例的漂亮的XML输出呢?
12个回答

232

在参考其他答案后,我研究了XmlTextWriter并编写了以下辅助方法:

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

代码比我希望的要多一些,但它运行得非常好。


6
你甚至可以考虑将你的实用方法创建为XmlDocument类的扩展方法。 - Oppositional
6
有趣的是,对我而言,这仅仅是将XML头部的编码设置为UTF-16。令人奇怪的是,即使我明确地设置了settings.Encoding = Encoding.UTF8;,它仍然会这样做。 - Nyerguds
3
可以通过使用指定编码的 MemoryStream + StreamWriter 来解决编码问题,而不是使用 StringBuilder,并使用 enc.GetString(memstream.GetBuffer(), 0, (int)memstream.Length); 获取文本。然而最终结果仍然没有格式。这可能与我从已有格式的读取文档开始有关吗?我只想让我的新节点也有格式。 - Nyerguds
2
我被引诱将 "\r\n" 修改为Environment.Newline - Pharap
你不需要关于换行的设置选项。关于缩进的选项似乎就足够了。两个空格也是默认的,所以只需要 Indent = true。虽然我更喜欢制表符,所以我还需要 IndentChars = "\t"。(制表符也可以节省空间。) - ygoe
3
如果doc.PreserveWhitespace被设置为true,则会因其包含部分缩进而失败。建议不要将其设置为true。 - Master DJon


43

如果你有访问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);
}

�常好��相比�被��的答案,它�会产生XML注释,因此在XML片段中�行得更好。 - Umar Farooq Khawaja
3
奇怪的是,这会从 XML 中删除 <?xml ...?><!DOCTYPE ...>。对于碎片来说没问题,但对于完整文档并不理想。 - Jesse Chisholm
这是唯一对我有效的方法。所有其他使用xmltextwriter、Formatting = Formatting.Indented和XmlWriterSettings的方法都无法重新格式化文本,但这种方法可以。 - kexx

22

一个更短的扩展方法版本

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

这个非常有效,而且不需要创建不必要的磁盘文件。 - Zain Rizvi

14
如果以上的美化方法被用于已经包含XmlProcessingInstruction子节点的XmlDocument,则会抛出以下异常:

无法编写XML声明。WriteStartDocument方法已经编写了它。

这是我修改后的版本,以消除异常:
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();
    }
}

现在它对我有效,可能您需要扫描所有子节点以获取XmlProcessingInstruction节点,而不仅仅是第一个节点?
2015年4月更新: 自从我遇到另一个编码错误的情况后,我搜索了如何强制执行UTF-8而没有BOM。我找到了这篇博客文章并基于此创建了一个函数:
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;
    }
}

如果您将CDATA部分放置在父节点内且在子节点之前,则无法正常工作。 - Sasha Bond
2
MemoryStream 在我的代码中似乎不是必需的。在设置中,我将 Encoding = Encoding.UTF8OmitXmlDeclaration = true - Master DJon

7
XmlTextWriter xw = new XmlTextWriter(writer);
xw.Formatting = Formatting.Indented;

6
    public static string FormatXml(string xml)
    {
        try
        {
            var doc = XDocument.Parse(xml);
            return doc.ToString();
        }
        catch (Exception)
        {
            return xml;
        }
    }

下面的答案肯定需要一些解释,但它对我很有效,并且比其他解决方案简单得多。 - CarlR
似乎您需要导入system.link.XML程序集才能在PS 3上运行。 - CarlR

2
在实施这里发布的建议时,我遇到了文本编码问题。似乎忽略了XmlWriterSettings的编码,并且总是被流的编码覆盖。当使用StringBuilder时,这总是C#内部使用的文本编码,即UTF-16。
因此,这里有一个支持其他编码的版本。
重要提示:如果在加载文档时启用了XMLDocument对象的preserveWhitespace属性,则完全忽略格式。这让我困惑了一段时间,所以请务必不要启用它。
我的最终代码:
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);
    }
}

这将保存格式化的XML到磁盘,使用给定的文本编码。


1
保留空格的事实破坏了XmlWriter的格式化功能,这是非常重要的信息 - 这让我困扰了相当长的一段时间。谢谢! - dwillis77

2

一个简单的方法是使用:

writer.WriteRaw(space_char);

像这样的示例代码,是我使用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();

            }

        }

这样你可以按照平常使用的方式添加制表符或换行符,即 \t 或 \n。


1
基于被接受的答案,这是一个更为简化的方法:
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(); 
}

设置新行不是必需的。缩进字符也有默认的两个空格,所以我更喜欢不设置它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接