如何在新行上关闭节点和标记来格式化XML?

时间:2009-08-17 14:03:15

标签: .net xml formatting xmlwriter

我正在修改.NET中的一些.vcrpoj文件但是当我保存它们格式化更改(这会破坏我的diff工具)时,原始文件看起来像这样:

<VisualStudioProject
ProjectType="Visual C++"
Version="8.00"
>
<Platforms>
    <Platform
        Name="Win32"
    />
</Platforms>
<ToolFiles>
</ToolFiles>

但是当我保存更改时,它看起来像这样:

<VisualStudioProject
ProjectType="Visual C++"
Version="8.00">
<Platforms>
    <Platform
        Name="Win32" />
</Platforms>
<ToolFiles></ToolFiles>

我正在使用以下XmlWritterSettings

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = ("\t");
settings.Encoding = Encoding.UTF8;
settings.NewLineOnAttributes = true;

有没有办法定义设置以匹配visual studio使用的格式? (我需要NewLineOnAttributes否则会更糟糕。

7 个答案:

答案 0 :(得分:6)

我不认为你可以使用内置的XmlWriter实现来实现它...你可以从XmlTextWriter继承,并覆盖适当的方法(不确定它是哪一个......)来编写具有所需格式的元素


以下是一些支持XML的diff工具(将基于语义比较文件,忽略格式化):

使用这些工具,您无需担心生成的XML格式

答案 1 :(得分:2)

重要吗?据推测,.NET IDE会读取标准XML。 重要的是,您的XML是合法的,并且显然是合法的。 你真的有问题吗?

编辑:(另一位用户表示真正的问题在于差异化)。 让我们调用您正在使用的任何过程来产生新结果 P,旧文件为F.如果你运行P(F)就是这么简单 读取F并将其写回,不做任何更改,你会得到 您在原始文件上的新(不方便)格式。

我在猜你在做什么 正在运行P(F + epsilon),您正在使用epsilon更改修改原始F, 并产生这个,然后你很难比较新的 与原来的。解决这个问题的一种方法就是简单地说 在原稿上运行P(F),并将其与P(F + epsilon)进行比较。 现在大概两者的格式化样式是相同的 你的差异是合理的。这种特技叫做 “归一化”。

另一种选择是运行一个理解XML的diff工具, 因此知道什么格式是无关紧要的。

答案 2 :(得分:1)

还有WinMerge(免费,GPL),带有xml插件

答案 3 :(得分:1)

也许改变你的差异工具将解决你的问题,因为其他一切显然运行正常。某些差异工具(如WinMerge)可以选择过滤要忽略的差异,即使您可以提供正则表达式来定义规则

答案 4 :(得分:1)

首先将其保存回xml,使用下面方法的修改版本进行读取和重写,以注入换行符和制表符。为此,您可以使用rdr.Depth获取选项卡计数,并在调用WriteEndElement之前使用wtr.WriteRaw(“\ r \ n”+ + new String('\ t',tabCount))来编写非重要的白色空间。对不起,这是UNTESTED代码,但它尽可能接近我。

    public void CopyXmlContentsToFile(XmlTextReader rdr, XmlTextWriter wtr)
    {
        try
        {
            rdr.WhitespaceHandling = WhitespaceHandling.Significant;
            wtr.Formatting = Formatting.Indented;
            CopyNodes(rdr, wtr);
        }
        finally
        {
            rdr.Close();
            wtr.Close();
        }
    }


    void CopyNodes(XmlReader rdr, XmlWriter wtr)
    {
        if (rdr.NodeType == XmlNodeType.Text || rdr.NodeType == XmlNodeType.SignificantWhitespace)
        {
            wtr.WriteString(rdr.Value);
        }
        else if (rdr.NodeType == XmlNodeType.Whitespace)
            return;
        else if (rdr.NodeType == XmlNodeType.Element)
        {
            string elemName = rdr.LocalName;
            bool empty = rdr.IsEmptyElement;

            wtr.WriteStartElement(elemName);

            while (rdr.MoveToNextAttribute())
            {
                if (rdr.Prefix.Length == 0)
                    wtr.WriteAttributeString(rdr.LocalName, rdr.Value);
            }

            if (rdr.NodeType == XmlNodeType.Attribute)
                rdr.MoveToElement();

            if (!empty)
            {
                while (rdr.Read() && rdr.NodeType != XmlNodeType.EndElement)
                    CopyNodes(rdr, wtr);
            }

            if (!empty && wtr.WriteState != WriteState.Content)
                wtr.WriteRaw("");

            if (!empty)
            {
                //Here we can inject our custom formatting with WriteRaw():
                wtr.WriteRaw(Environment.NewLine + new String('\t', rdr.Depth));
            }

            wtr.WriteEndElement();
        }
        else
        {
            throw new ApplicationException(
                String.Format("Unexpected node type {0} at line {1}.", rdr.NodeType, ((XmlTextReader)rdr).LineNumber)
                );
        }
    }

答案 5 :(得分:1)

XmlWriter.Create返回一个包含在XmlWellFormedWriter中的特定XmlRawWriter,所有这些都定义为内部,因此您无法扩展它们。

但是,您可以扩展XmlTextWriter,但与格式良好的编写器相比,它的功能集非常有限。

因此...

这是我用来处理这个问题的自定义XmlTextWriter,它具有良好形成的编写器中缺少的大部分功能,并且除了XmlWriterSettings之外:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace System.Xml
{
    public class CustomXmlTextWriter : XmlTextWriter
    {
        internal class CustomStreamWriter : StreamWriter
        {
            public CustomStreamWriter(Stream stream, Encoding encoding) : base(stream) { }
            // This prevents the XmlTextWriter from writing the extra space before attributes, and the short EndElement " />"
            public bool DisableSpace { get; set; }
            public override void Write(char value)
            {
                if (DisableSpace && value == ' ') return;
                else base.Write(value);
            }
            public override void Write(string value)
            {
                if (DisableSpace && value == " /") base.Write('/');
                else base.Write(value);
            }
        }

        public CustomXmlTextWriter(string filename, XmlWriterSettings settings) : this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read), settings) { }
        public CustomXmlTextWriter(Stream stream, XmlWriterSettings settings) : this(new CustomStreamWriter(stream, settings.Encoding), settings) { }
        internal CustomXmlTextWriter(CustomStreamWriter writer, XmlWriterSettings settings)
            : base(writer)
        {
            m_Writer = writer;
            m_Settings = settings;

            if (m_Settings.OmitXmlDeclaration == false)
            {
                string encoding = (m_Writer.Encoding.CodePage == 1201) ? "UTF-16BE" : m_Writer.Encoding.WebName;
                m_Writer.WriteLine("<?xml version=\"1.0\" encoding=\"{0}\"?>", encoding);
            }
        }

        private bool m_HasAttributes = false;
        private Stack<bool> m_HasAttributesStack = new Stack<bool>();
        private CustomStreamWriter m_Writer;
        private XmlWriterSettings m_Settings;

        public override XmlWriterSettings Settings { get { return m_Settings; } }

        public override void WriteStartElement(string prefix, string localName, string ns)
        {
            if (WriteState == WriteState.Element)
            {
                if (m_HasAttributes && Settings.NewLineOnAttributes) { WriteIndent(m_HasAttributesStack.Count); }
                WriteRaw(""); // Trick the XmlTextWriter into closing the previous element, and updating the WriteState
                m_Writer.DisableSpace = false;
            }
            int indentLevel = m_HasAttributesStack.Count;
            if (indentLevel > 0)
            {
                WriteIndent(indentLevel);
            }
            m_HasAttributesStack.Push(m_HasAttributes);
            m_HasAttributes = false;

            base.WriteStartElement(prefix, localName, ns);
        }

        public override void WriteEndElement()
        {
            if (m_HasAttributes && Settings.NewLineOnAttributes)
            {
                WriteIndent(m_HasAttributesStack.Count - 1);
            }
            m_HasAttributes = m_HasAttributesStack.Pop();
            base.WriteEndElement();

            m_Writer.DisableSpace = false;
        }

        public override void WriteFullEndElement()
        {
            m_HasAttributes = m_HasAttributesStack.Pop();
            WriteIndent(m_HasAttributesStack.Count);
            base.WriteFullEndElement();
        }

        public override void WriteStartAttribute(string prefix, string localName, string ns)
        {
            if (Settings.NewLineOnAttributes)
            {
                WriteIndent(m_HasAttributesStack.Count);
                m_Writer.DisableSpace = true;
            }
            m_HasAttributes = true;
            base.WriteStartAttribute(prefix, localName, ns);
        }

        public override void WriteString(string text)
        {
            if (m_Settings.NewLineHandling == NewLineHandling.Replace)
            {
                text = Regex.Replace(text, @"\r\n?|\n", m_Settings.NewLineChars);
            }
            else if (m_Settings.NewLineHandling == NewLineHandling.Entitize)
            {
                text = Regex.Replace(text, @"\n|\r", m => String.Format("&#x{0:X};", (int)m.Value[0]));
            }
            base.WriteString(text);
        }

        private void WriteIndent(int indentLevel)
        {
            if (Settings.Indent == false) return;
            m_Writer.Write(Settings.NewLineChars);
            for (int i = 0; i < indentLevel; ++i)
            {
                m_Writer.Write(Settings.IndentChars);
            }
        }
    }
}

只需在项目中创建一个包含上述代码的文件,然后像这样使用它:

        // Create the XmlWriter Settings as you normally would
        // *Note: You can change or omit these, they are just for an example of what I supported
        XmlWriterSettings settings = new XmlWriterSettings()
        {
            Encoding = Encoding.UTF8,
            //OmitXmlDeclaration = true,
            Indent = true,
            //IndentChars = "  ",
            IndentChars = "\t",
            NewLineOnAttributes = true,
            //NewLineHandling = NewLineHandling.Entitize,
            //NewLineHandling = NewLineHandling.Replace,
            //NewLineChars = @"\n",
        };

        // Replace XmlWriter.Create with new CustomXmlTextWriter
        //using (XmlWriter writer = XmlWriter.Create(path, settings))
        using (XmlWriter writer = new CustomXmlTextWriter(path, settings))
        {
            xml.WriteTo(writer);
        }

如果将此功能作为XmlWriterSettings中的一个选项添加到格式良好的编写器中,那将是很好的。

有时候改变diff工具不是一个选择(甚至首先是问题) 当使用像perforce这样的系统通过工具自动合并更改时,最好将更改保持为原子。

例如...... 如果一个人更改了最后一个属性,并且另一个人将另一个属性添加到最后,则没有理由根据哪一行应包含结束&gt;创建人为冲突。 (或/&gt;)。 这种情况的最佳解决方案是,如果结束括号是独立的,并避免冲突。

答案 6 :(得分:0)

听起来你正在修改很多文件,所以这可能不实用,但在VS中打开文件并保存它应该恢复标准格式。