tags:

views:

196

answers:

4

Duplicate:

Merge XMLs with attributes

I have two XML files which I'd like to merge.

The merged file should contain every element from both files (maintaining the hierarchy), when elements from the second XML can override elements from the first XML:

When two elements are identical (same XPATH, same properties), I'd like to override.

There are probably a million ways to do this - which is the most effortless (without learning XSLT, preferably)

Sample result:

File 1

<a>
 <b foo="d"/>
 <b bar="c"/>
 <c/>
</a>

File 2

<a>
 <b foo="e"/>
 <b boo="c"/>
 <c/>
</a>
<x>
 <b bar="c"/>
</x>

Output

<a>
 <b foo="d"/>
 <b bar="c"/>
 <b boo="c"/>
 <c/>
</a>
<x>
 <b bar="c"/>
</x>
A: 

http://www.javaworld.com/javaworld/jw-07-2007/jw-07-xmlmerge.html

Stobor
Yeah, I found these in Google but haven't had time to check it out yet. Do you know it works for what I need?
ripper234
A: 

Using xml2 could make this quite simple using the following process:

  • use xml2 on both files for a simple representation
  • merge both
  • handle overrides
  • convert back to xml using 2xml

I'm not yet sure how to do the overrides, but the rest could look similar to

xml2 file1 file2 | sort | 2xml

Karl Bartel
A: 

I didn't find anything useful in the first answer, and the second seemed to patchy, so I wrote it for myself.

The code is rather ugly (I'd love to hear of suggestions - my XML-handling capabilities are ... not that good).

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;

namespace XmlMerge
{
    internal class Program
    {
     private static int Main(string[] args)
     {
      if (args.Length != 3)
      {
       Console.WriteLine("Usage: XmlMerge <mainFile> <mergedFile> <output>");
       return 1;
      }

      string mainFile = args[0];
      string mergedFile = args[1];
      string outputFile = args[2];

      if (!File.Exists(mainFile) ||
          !File.Exists(mergedFile))
      {
       Console.WriteLine("One of the input files doesn't exist");
       return 1;
      }

      var main = new XmlDocument();
      main.Load(mainFile);
      var merged = new XmlDocument();
      merged.Load(mergedFile);
      foreach (XmlNode element in merged.SelectNodes("/config/section/value"))
      {
       string xpath = GetXpath(element);
       XmlNode mainNode = main.SelectSingleNode(xpath);
       if (mainNode != null)
       {
        // existing value
        mainNode.InnerText = element.InnerText;
       }
       else
       {
        // go to father, add as new node
        AddNewNode(element, main, xpath);
       }
      }

      main.Save(outputFile);

      return 0;
     }

     /// <summary>
     /// Add <paramref name="toAdd"/> to <paramref name="existing"/> under <paramref name="xpath"/>
     /// </summary>
     /// <param name="toAdd"></param>
     /// <param name="existing"></param>
     /// <param name="xpath"></param>
     private static void AddNewNode(XmlNode toAdd, XmlNode existing, string xpath)
     {
      foreach (var part in xpath.Split('/'))
      {
       if (part == "")
        continue;

       var child = existing.SelectSingleNode(part); 
       if (child != null)
       {
        existing = child;
        continue;
       }

       // similar child does not exist, add it ourselves
       var partMatch = Regex.Match(part, @"(.*)(?:\[(.*)\])");
       var name = partMatch.Groups[1].Value;
       var attributes = partMatch.Groups[2].Value;

       var newChild = existing.OwnerDocument.CreateElement(name);
       if (attributes != null)
       {
        foreach (var attributeStr in attributes.Split(new[]{"and"}, StringSplitOptions.None))
        {
         var attributeMatch = Regex.Match(attributeStr, "@(.*)='(.*)'");
         var attributeName = attributeMatch.Groups[1].Value;
         var attributeValue = attributeMatch.Groups[2].Value;

         XmlAttribute attribute = existing.OwnerDocument.CreateAttribute(attributeName);
         attribute.Value = attributeValue;
         newChild.Attributes.Append(attribute);
        }
       }
       existing.AppendChild(newChild);
      }
     }

     private static string GetXpath(XmlNode node)
     {
      if (node.ParentNode == null)
       return "";

      string attributeStr = GetAttributeStr(node);
      return GetXpath(node.ParentNode) + "/" + node.Name + attributeStr;
     }

     private static string GetAttributeStr(XmlNode node)
     {
      if (node.Attributes.Count == 0)
       return "";

      var result = "[";
      bool first = true;
      foreach (XmlAttribute attribute in node.Attributes)
      {
       if (!first)
        result += " and ";
       result += "@" + attribute.Name + "='" + attribute.Value + "'";
       first = false;
      }
      return result + "]";
     }
    }
}
ripper234
+1  A: 

XSLT might look like a challenge but it can offer a good solution to your problem. You might want to consider XSLT that is publically available for your problem. This is going to be quite platform independant as many platforms implement these transforms. Perhaps try this:

http://www2.informatik.hu-berlin.de/~obecker/XSLT/#merge

There are a few options that subtly change how the merge is performed too, giving you perhaps a more flexible solution.

andygavin
Thanks, I hadn't thought of looking for pre-written XSLT.
ripper234