Search code examples
c#linqxelement

Linq - XElement Compare


i am trying to compare two xelements () as below:

XElement parentItems =

<tcm:ListItems xmlns:tcm="http://www.tridion.com/ContentManager/5.0">
  <tcm:Item Title="070_Page" Modified="2016-01-06T18:08:36" CP1="-6185, Intro" CP2="-6182, Article Body" CP3="-14507, Article Body1" CP4="-14430, Article Body2" CP5="-14530, Article Body3" CP6="-7064, Article Body4" CP7="-14529, Article Body5" CP8="-7065, Article Body6" CPCount="8" /> 
  <tcm:Item Title="080_Page" Modified="2015-04-23T13:27:59" CP1="-6302, Intro" CP2="-6193, Article Body" CPCount="2" /> 
  <tcm:Item Title="Release Notes" Modified="2016-01-07T21:25:43" CP1="-6303, Release Notes Intro" CP2="-6196, Release Notes Article Body" CPCount="2" />
</tcm:ListItems>

XElement childItems =

<tcm:ListItems xmlns:tcm="http://www.tridion.com/ContentManager/5.0">
  <tcm:Item Title="070_Page" Modified="2016-06-06T19:36:35" CP1="-6185, Intro" CP2="-6147, Media & Delivery Intro" CP3="-6182, Article Body" CP4="-14507, Article Body1" CP5="-14430, Article Body2" CP6="-14530, Article Body3" CP7="-7064, Article Body4" CP8="-14529, Article Body5" CP9="-7065, Article Body6" CPCount="9" /> 
  <tcm:Item Title="080_Page" Modified="2016-02-09T21:03:32" CP1="-6302, Intro" CP2="-6193, Article Body" CPCount="2" /> 
  <tcm:Item Title="Release Notes" Modified="2016-02-09T21:03:33" CP1="-6303, Release Notes Intro" CP2="-6196, Release Notes Article Body" CPCount="2" /> 
  <tcm:Item Title="Release Notes1" Modified="2016-03-09T22:00:13" CP1="-6303, Release Notes Intro" CP2="-6196, Release Notes Article Body" CPCount="2" />
</tcm:ListItems>

I want my result to be (first one has different CPCount, and second one is a new element in childItems):

XElement diff =

<tcm:ListItems xmlns:tcm="http://www.tridion.com/ContentManager/5.0">
  <tcm:Item Title="070_Page" Modified="2016-06-06T19:36:35" CP1="-6185, Intro" CP2="-6147, Media & Delivery Intro" CP3="-6182, Article Body" CP4="-14507, Article Body1" CP5="-14430, Article Body2" CP6="-14530, Article Body3" CP7="-7064, Article Body4" CP8="-14529, Article Body5" CP9="-7065, Article Body6" CPCount="9" />   
  <tcm:Item Title="Release Notes1" Modified="2016-03-09T22:00:13" CP1="-6303, Release Notes Intro" CP2="-6196, Release Notes Article Body" CPCount="2" />
</tcm:ListItems>

I've tried XMLDiff API but no luck. I can loop through and get the results, but sometimes the list can be huge (3000+). Is there a best way to handle this?


Solution

  • Ok, here is the code - in a console app created using VS2015 (could be non-optimal but it works - I get the two expected elements in the end - leaving refactoring for you as a homework):

    class Program
    {
        //this is just for convenient storage of all collected data
        //also to avoid additional dictionary lookups and processing
        internal class ElementAttributes
        {
            public XElement Element { get; set; }
            public Dictionary<string, string> Attributes;
    
            public ElementAttributes(XElement element)
            {
                this.Element = element;
                this.Attributes = new Dictionary<string, string>();
            }
        }
    
        static void Main(string[] args)
        {
            //loading XML elements from embedded resources
            XElement parentItems = XElement.Parse(ResourceXML.parentItems);
            XElement childItems = XElement.Parse(ResourceXML.childItems);
    
            XElement diff = XElement.Parse(@"<tcm:ListItems xmlns:tcm=""http://www.tridion.com/ContentManager/5.0"" ></tcm:ListItems>");
    
            var parentDictionary = BuildDictionary(parentItems);
            var childDictionary = BuildDictionary(childItems);
    
            //perform diff
            foreach (string key in childDictionary.Keys)
            {
                ElementAttributes parentElementAttributes;
                if (parentDictionary.TryGetValue(key, out parentElementAttributes))
                {
                    //found Title/key in parent, compare attributes
                    foreach (var childAttribute in childDictionary[key].Attributes)
                    {
                        var attributeName = childAttribute.Key;
                        var childAttributeValue = childAttribute.Value;
    
                        string parentAttributeValue;
                        if (parentElementAttributes.Attributes.TryGetValue(attributeName, out parentAttributeValue))
                        {
                            //found attribute in parent, compare value
                            if(childAttributeValue == parentAttributeValue)
                            {
                                //values are equal, compare other attributes
                                continue;
                            }
                        }
    
                        //parent does not have this attribute OR
                        //different value in child -> show in diff
                        diff.Add(childDictionary[key].Element);
    
                        //do not compare other attributes
                        break;
                    }
    
                    //child may have missing attributes, which are in parent only
                    //NOTE: your example does not present a use case for this scenario
                    foreach (var parentAttribute in parentElementAttributes.Attributes)
                    {
                        string attributeName = parentAttribute.Key;
                        if (!childDictionary[key].Attributes.ContainsKey(attributeName))
                        {
                            //attribute found in parent, but not in child
                            diff.Add(childDictionary[key].Element);
                            break;
                        }
                    }
                }
                else
                {
                    //not found in parent, show in diff
                    diff.Add(childDictionary[key].Element);
                }
            }
        }
    
    
        private static Dictionary<string, ElementAttributes> BuildDictionary(XElement element)
        {
            XNamespace tcm = "http://www.tridion.com/ContentManager/5.0";
    
            var resultDictionary = new Dictionary<string, ElementAttributes>();
            foreach (XElement childElement in element.Elements(tcm + "Item"))
            {
                var attributeDictionary = new ElementAttributes(childElement);
                foreach (XAttribute attribute in childElement.Attributes())
                {
                    string[] excludedColumns = {"Title", "Modified"};
    
                    if (excludedColumns.Contains(attribute.Name.LocalName))
                    {
                        continue;
                    }
    
                    attributeDictionary.Attributes.Add(attribute.Name.LocalName, attribute.Value);
                }
                resultDictionary.Add(childElement.Attribute("Title").Value, attributeDictionary);
            }
            return resultDictionary;
        }
    }