Click here to Skip to main content
Click here to Skip to main content

Solving the .resx Merge Problem

, 9 Jul 2014
Rate this:
Please Sign up or sign in to vote.
This article provides a console utility and a method for extending a merge tool to ensure that only real changes (not reordering) appear when merging files from different source control branches.

Comparison Results

Introduction

I've been using Windows Forms for the last 13 years or so and, while it has its quirks, it's by and large been pretty straightforward to use. However, the one case where Windows Forms causes me real pain occurs when I need to merge a .resx file from two branches of a source control system. It turns out that the Windows Forms implementation in Visual Studio reorders (apparently randomly) the elements in the .resx file when changes are made to a form or custom control. While this doesn't affect the behavior of the form or control (the sequence is irrelevant), it wreaks havoc on any merge/diff tools you are using because every resequencing is treated as a change.

This project is written with .NET 3.5 and has not been tested with any other configurations that support LINQ. If you used this successfully with .NET 4.5, .NET 4.0, .NET 3.0 or .NET 2.0 with SP1, please add a message outlining your experience so that others can benefit from it.

The Problem

When merging a Windows Forms form or control between two branches of a source control system, the merge tool typically displays many false conflicts because insignificant changes in element sequences are treated as significant.

The Solution

This article describes a simple console application that can be used as a pre-comparison conversion to sort the elements in a .resx file by name attribute. While you could use this filter standalone to modify the .resx files before comparison, many merge/diff utilities can be configured to run the conversion prior to comparison and merge.

Because both files are sorted in a deterministic way, the merge/diff utility can accurately determine what elements are new and changed without introducing false conflicts because of circumstantial differences in element location.

Implementation

I originally thought it would be simplest to implement an XSLT transform to sort the various elements in the .resx XML, but discovered, to my delight, that I could use LINQ to achieve the same result trivially. The entire code for the project follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace SortRESX
{
  // Assume two inputs, a source .resx file path and a target .resx file path.
  // The program reads the source and writes a sorted version of it to the
  // target .resx file.
  class Program
  {
    static void Main(string[] args)
    {
      // Check parameters
      if (args.Length != 2)
      {
        ShowHelp();
        return;
      }
      try
      {
        XDocument doc = XDocument.Load(args[0]);        // Create a LINQ XML document 
                                                        // from the source file.
        XDocument  sortedDoc  =  SortDataByName(doc);   // Create a sorted version
                                                        // of the XML
        sortedDoc.Save(args[1]);                        // Save it to the target file
      }
      catch (Exception ex)
      {
        Console.Error.WriteLine("Error loading resx file {0}" + 
            "or error saving it to {1}: {2}", args[0], args[1], ex.Message);
      }
      return;
    }
    // Use LINQ to sort the elements. The comment, schema, 
    // resheader, assembly, metadata, data appear in that order,
    // with resheader, assembly, metadata and data elements sorted by name attribute.
 private static XDocument SortDataByName(XDocument resx)
    {
      return  new  XDocument(
        new  XElement(resx.Root.Name,
          from comment in resx.Root.Nodes() where comment.NodeType == 
                    XmlNodeType.Comment select comment,
          from schema in resx.Root.Elements() where schema.Name.LocalName == 
                            "schema" select schema,
          from resheader in resx.Root.Elements("resheader") 
        orderby (string) resheader.Attribute("name") select resheader,
          from assembly in resx.Root.Elements("assembly") 
        orderby (string) assembly.Attribute("name") select assembly,
          from metadata in resx.Root.Elements("metadata") 
        orderby (string)metadata.Attribute("name") select metadata,
          from data in resx.Root.Elements("data") 
        orderby (string)data.Attribute("name") select data
        )
      );
    }

    // Write invocation instructions to stderr.
    private static void ShowHelp()
    {
      string sExeName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
      Console.Error.WriteLine("Command line format\n{0} <input resx file> 
                    <output resx file>", sExeName);
    }
  }
}

There's not much to it. The input file is used to initialize a LINQ XDocument XML document. The SortDataByName() method is called to return a sorted version of the document, and the converted file is saved to the target path. The real work is done by the SortDataByName() method, which contains a single LINQ statement.

The LINQ statement constructs a new document with a single root element with the same name as the root element in the source document. The contents of that element are defined by the six LINQ queries corresponding to the groups of nodes desired in the target:

  • The standard comment node.
  • The .resx schema element
  • The resheader elements, sorted by name attribute
  • The assembly elements, sorted by name attribute
  • The metadata elements, sorted by name attribute
  • The data elements, sorted by name attribute

And that's it. The SortRESX.exe program is now ready for integration with a merge/diff utility.

Integration with Merge/Diff Tools

Many commercial merge/diff utilities provide a mechanism that permits you to pre-process files with specified extensions using an external program before they are compared or used for merging. By associating SortRESX.exe with .resx file types, the comparison is done against sorted files, which enables only significant changes to appear. To associate SortRESX.exe with .resx file types for such a tool, you can copy SortRESX.exe into the installation directory for the utility, then use the instructions provided in the utility to associate it with the ".resx" file type.

Points of Interest

The same .resx sorting code used in this project could probably be used as an add-in to Visual Studio that would perform the sort every time the .resx file is saved. This alternative approach has the advantage that it enables all diff/merge tools to behave non-pathologically if all .resx files had been thus sorted. The disadvantage of this approach is that if the merge involved files created before the add-in was adopted, the merge problem described above would still manifest itself.

History

  • June 3, 2009: Initial version.
  • June 15, 2009: Changed introductory paragraph.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Tom Clement
Team Leader
United States United States
I've been programming in C, C++, Visual Basic and C# for almost 30 years. I've worked at Sierra Systems, ViewStar, Mosaix, Lucent, Avaya, Avinon, Apptero and now Serena in various roles over my career.

Comments and Discussions

 
SuggestionTeam Foundation Server PinpremiumTom Clement21hrs 11mins ago 
SuggestionCool, I may add this as a new feature to my ResXManager extension if someone votes for it. Pinmembertom-englert10-Jul-14 23:02 
GeneralMy vote of 5 PinmemberScruffyDuck23-Jun-14 4:32 
GeneralRe: My vote of 5 [modified] PinpremiumTom Clement23-Jun-14 5:30 
GeneralRe: My vote of 5 PinmemberScruffyDuck23-Jun-14 5:37 
QuestionHow to do with Tortoise SVN PinmemberMember 103857286-Nov-13 22:18 
QuestionThanks PinmemberRavi Dasari26-Jul-13 7:31 
AnswerRe: Thanks PinmentorTom Clement26-Jul-13 8:24 
Generalexcellent work PinmemberM Sheik Uduman Ali7-Nov-09 1:31 
GeneralRe: excellent work PinmemberTom Clement7-Nov-09 6:14 
Answeranother way in Visual Studio Pinmemberbxb9-Jun-09 23:16 
GeneralRe: another way in Visual Studio PinmemberTom Clement10-Jun-09 5:10 
AnswerRe: another way in Visual Studio PinmemberDa_Hero31-Oct-12 1:04 
GeneralTicks all three boxes... PinmemberMaxxx_8-Jun-09 14:46 
GeneralRe: Ticks all three boxes... PinmemberTom Clement8-Jun-09 18:16 
GeneralAbsolutely Brilliant PinmemberAKHEIROL8-Jun-09 8:38 
GeneralRe: Absolutely Brilliant PinmemberTom Clement11-Jun-09 4:59 
Generalcomparison tool Pinmembersheitman806-Jun-09 13:02 
hi,
 
i would like to know wich comparison tool you used on the screenshot.
 
best regards,
sven
GeneralRe: comparison tool [modified] PinmemberTom Clement6-Jun-09 14:31 
GeneralSimple and yet genius PinmemberMarcelo de Aguiar5-Jun-09 9:13 
GeneralRe: Simple and yet genius PinmemberTom Clement5-Jun-09 9:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 9 Jul 2014
Article Copyright 2009 by Tom Clement
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid