Question

I'm modifying some .vcrpoj files in .NET but when I save them the formatting changes (which wrecks havoc with my diff tool), the original files look like this:

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

But when I save the changes it looks like this:

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

I'm using the following XmlWritterSettings

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

Is there a way to define the settings in order to match the format visual studio uses? (I need NewLineOnAttributes otherwise it's even worse).

Was it helpful?

Solution

I don't think you can do it with the built-in XmlWriter implementation... you could inherit from XmlTextWriter, and override the appropriate method (not sure which one it is...) to write elements with the desired format


Here are a few XML-aware diff tools (that will compare the files based on semantics, ignoring the formatting) :

With those tools, you won't need to worry about the XML format you generate

OTHER TIPS

Does it matter? Presumably the .NET IDE reads standard XML. All that counts is that your XML is legal, and apparantly it is. Do you really have a problem?

EDIT: (Another user indicates the real problem is with diff'ing). Let's call whatever process you are using to produce your new result P, with the old file being F. If you run P(F) that is, simply read F and write it back out without any changes, you'll get your new (inconvenient) format on the original file.

I'm guessing what you are doing is running P(F+epsilon), where you are modifying the original F with epsilon changes, and producing this, and then you have difficulty comparing the new with the original. One way to solve this problem is to simply run P(F) on the original, and compare it with P(F+epsilon). Now presumably the formatting styles of both are identical and your diffs will be reasonable. This kind of stunt is called "normalization".

The other alternative is to run a diff tool that understands XML, and thus knows what formatting is irrelevant.

Also WinMerge (free, GPL), with the xml plugin

Maybe changing your diff tool will solve your problems, as everything else apparently runs fine. Some diff tools like WinMerge have a option to filter what differences you want to ignore, even you can supply a regular expression to define the rule

Start by saveing it back to xml, read and rewrite with a modified version of the method below to inject the line-feeds and tabs. To do this you can get the tab count by using rdr.Depth and use wtr.WriteRaw("\r\n" + new String('\t', tabCount)) just before you call the WriteEndElement) to write the non-significant white space. Sorry, this is UNTESTED code but it is as close as I can get you.

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

XmlWriter.Create returns a specific XmlRawWriter wrapped in a XmlWellFormedWriter, all of which are defined as internal so you can't extend them.

However, you can extend XmlTextWriter, but it has a very limited feature set compared to the well formed writer.

So...

Here is a custom XmlTextWriter I made to deal with this issue, that has most of the features missing from the well formed writer, and excepts 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);
            }
        }
    }
}

Just make a file containing the above code in your project, and then use it like so:

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

It would be nice if this functionality were just added to the well formed writer as an option in XmlWriterSettings.

Sometimes changing the diff tool is not an option (or even the problem in the first place) When using systems like perforce to auto merge changes via a tool, it is best to keep the changes as atomic as possible.

For example... If the last attribute is changed by one person, and an additional attribute is added to the end by another person, there is no reason to create an artificial conflict based on which line should contain the closing > (or />). The best solution for this scenario is if the closing bracket is on its own line, and avoid the conflict all together.

It sounds like you're modifying a lot of files, so this may not be practical, but opening the file in VS and saving it should restore the standard formatting.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top