1

I wish to merge two XML files, similar to What is the fastest way to combine two xml files into one

but I cannot wrap my head around how to group and merge them based on of the node values (Configuration node's Domain node value) I am trying it with Linq, but it doesn't make it easier, even the group by and where clause is there.

Basically I wish all Component nodes (duplicates are allowed) to be listed under the same Configuration node, which Domain name node values are equal.

In other words with the below example: the result XML has two Configuration nodes, one with Domain: MyDom01 the other is Domain: MyDom02 and under each configuration I have one Components node with all the Component listed.

Is that even possible?

One.XML

<System>
    <Configurations>
    
        <Configuration>
            <Domain>MyDom01</Domain>
            <Components>
                <Component>
                    <Name>Memory</Name>
                    <Size>16</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>8</Size>
                </Component>
            </Components>
        </Configuration>

        <Configuration>
            <Domain>MyDom01</Domain>
            <Components>
                <Component>
                    <Name>HDD</Name>
                    <Size>1</Size>
                </Component>
            </Components>
        </Configuration>
                    
        <Configuration>
            <Domain>MyDom02</Domain>
            <Components>
                <Component>
                    <Name>CPU</Name>
                    <Size>12</Size>
                </Component>
            </Components>
        </Configuration>

    </Configurations>
</System>

Another.XML

<System>
    <Configurations>
    
        <Configuration>
            <Domain>MyDom01</Domain>
            <Components>
                <Component>
                    <Name>Memory</Name>
                    <Size>128</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>32</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>32</Size>
                </Component>
            </Components>
        </Configuration>
                    
        <Configuration>
            <Domain>MyDom02</Domain>
            <Components>
                <Component>
                    <Name>Memory</Name>
                    <Size>32</Size>
                </Component>
            </Components>
        </Configuration>

    </Configurations>
</System>

Merged.XML:

<System>
    <Configurations>
    
        <Configuration>
            <Domain>MyDom01</Domain>
            <Components>
                <Component>
                    <Name>Memory</Name>
                    <Size>16</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>8</Size>
                </Component>

                <Component>
                    <Name>HDD</Name>
                    <Size>1</Size>
                </Component>

                <Component>
                    <Name>Memory</Name>
                    <Size>128</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>32</Size>
                </Component>
                <Component>
                    <Name>CPU</Name>
                    <Size>32</Size>
                </Component>                
            </Components>
        </Configuration>
                    
        <Configuration>
            <Domain>MyDom02</Domain>
            <Components>
                <Component>
                    <Name>CPU</Name>
                    <Size>12</Size>
                </Component>
                <Component>
                    <Name>Memory</Name>
                    <Size>32</Size>
                </Component>
            </Components>
        </Configuration>

    </Configurations>
</System>
Avi
  • 1,066
  • 1
  • 15
  • 37

2 Answers2

2

You can try to find <Components> element with matching <Domain> in XML1 then add <Component> elements from XML2 to it, and add <Configuration> from XML2 to <Configurations> in XML1 if no such matching <Domain> was found, for example:

var xdoc1 = XDocument.Load("path/to/xml1.xml");
var xdoc2 = XDocument.Load("path/to/xml2.xml");

foreach (var item in xdoc2.Descendants("Configuration"))
{
    var domain = (string)item.Element("Domain");

    // try to find <Components> with matching <Domain> in XML1
    var parent = xdoc1.Descendants("Configuration")
                        .Where(o => (string)o.Element("Domain") == domain)
                        .Elements("Components")
                        .FirstOrDefault();

    // if such <Components> is found, add <Component> from XML2 to it
    if(parent != null) 
    {
        parent.Add(item.Elements("Components").Elements("Component"));
    }
    // otherwise add <Configuration> from XML2 to <Configurations> in XML1 
    else 
    {
        xdoc1.Root.Element("Configurations").Add(item);
    }
}

// don't forget to save the modified XML1 if you need to
// here we only print the merged XML1
System.Console.WriteLine(xdoc1.ToString());

dotnetfiddle demo

har07
  • 88,338
  • 12
  • 84
  • 137
  • This is great, but it seems to be a "left join if not exist" setup, which i cannot guarantee that is true. More like a "full outer join" is needed. I try to figure it out on top of your idea. – Avi Feb 26 '21 at 15:02
  • Hmm... I don't get it. In the end this should add every `` from XML 2 to XML 1, but in two different ways depending on whether a matching `` is found. Can you give an example case that isn't handled by this logic? – har07 Feb 26 '21 at 15:12
  • It's a bit different from the "join" that you mentioned, because we're adding XML2 to XML1 here and take the modified XML1 as the result. We only need to take care of XML2 (left/right join, if you will) because everything from XML1 are already there (because we will take XML1 as the result), so no need for "full outer join" setup in this case AFAICS – har07 Feb 26 '21 at 15:17
  • You are right, it works. What i try to fix, is that your code sure gets adding nodes from other file, but there are nodes in the same file, with the same Domain name. That should go under one Configuration as well. – Avi Feb 26 '21 at 15:40
1

Using Xml Linq :

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

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        const string FILENAME1 = @"c:\temp\test1.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);
            XElement configurations = doc.Descendants("Configuration").FirstOrDefault();
            Dictionary<string, XElement> dict = doc.Descendants("Configuration")
                .GroupBy(x => (string)x.Element("Domain"), y => y)
                .ToDictionary(x => x.Key, y => y.FirstOrDefault());
       

            XDocument doc1 = XDocument.Load(FILENAME1);

            XElement configurations1 = doc1.Descendants("Configurations").FirstOrDefault();
            foreach (XElement configuration1 in configurations1.Elements("Configuration"))
            {
                string domain = (string)configuration1.Element("Domain");
                if (dict.ContainsKey(domain))
                {
                    XElement config = dict[domain];
                    config.Element("Components").Add(configuration1.Descendants("Component"));
                }
                else
                {
                    configurations.Add(configuration1);
                    dict.Add(domain, configuration1);
                }
            }
        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • @jdwemd Thanks, but this merge Configurations together regardles what's in their node. Only configurations with the same domain should be merged together. – Avi Feb 26 '21 at 13:51
  • @jdwend Almost perfect. It cannot deal when there is another Configuration with the same Domain name in the same file. Added such case to the example, search for HDD. I am trying to add it. Maybe i need to merge all XML into one giant XML then run this code. – Avi Feb 26 '21 at 16:16
  • 1
    I thought about that but didn't think it would occur. The domain meed to be added into the dictionary.. I will modify code. It is one line in the else. – jdweng Feb 26 '21 at 19:20