Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if XML element equals another XML element, ignoring empty values

Tags:

c#

xml

I'm checking if two XML elements given by string are equal with this method:

private static bool XmlEquals(string s1, string s2)
{
    return XNode.DeepEquals(XElement.Parse(s1), XElement.Parse(s2));
}

This work unless one of the elements has opening and closing tags and the other has a closed tag like this:

<MyElement SomeAttribute="some value" />
<MyElement SomeAttribute="some value"></MyElement>

Can I somehow compare two XML elements in a way, that the above case is considered equal?

like image 269
Hinek Avatar asked Jan 08 '15 13:01

Hinek


2 Answers

Straightforward way to solve this particular problem is to introduce closing brackets explicitly:

 private static bool XmlEquals(string s1, string s2)
 {
      var firstElement = XElement.Parse(s1);
      var secondElement = XElement.Parse(s2);
      IntroduceClosingBracket(firstElement);
      IntroduceClosingBracket(secondElement);

      return XNode.DeepEquals(firstElement, secondElement);
 }

 private static void IntroduceClosingBracket(XElement element)
 {
      foreach (var descendant in element.DescendantsAndSelf())
      {
           if (descendant.IsEmpty)
           {
                descendant.SetValue(String.Empty);
           }
      }
 }

Loop through all descendants may cause a performance hit, though.

like image 147
default locale Avatar answered Nov 05 '22 04:11

default locale


If the XMLs does not have to be perfectly identical like in @defaultlocale's solution which really compares everything (even comments) you could use LINQ to compare only things that you are interested in. To show the idea I've made the comparison of attribute values optional.

Test data:

var xml1 = @"
    <elm1 attr1='val1'>
        <elm2 attr2='val1'>
            <elm3 attr3='val1' />
        </elm2>
    </elm1>";

var xml2 = @"
    <elm1 attr1='val1'>
        <elm2 attr2='val1'>
            <elm3 attr3='val1' attr='val2' />
        </elm2>
    </elm1>";

Recursive comparison:

// Just a helper.
private static Tuple<XElement, XElement> Compare(
    string xml1, 
    string xml2, 
    bool compareAttributeValues)
{
    return Compare(
        XElement.Parse(xml1), 
        XElement.Parse(xml2), 
        compareAttributeValues);
}

// Compares XElements recursively
// and returns the two nodes that are different if any.
private static Tuple<XElement, XElement> Compare(
    XElement xElm1,
    XElement xElm2, 
    bool compareAttributeValues)
{
    // Elements are different if they have a different number of children.
    if (xElm1.Elements().Count() != xElm2.Elements().Count())
    {
        return new Tuple<XElement, XElement>(xElm1, xElm2);
    }

    // Enumerate both elements at the same time.
    var elements = Enumerable.Zip(
        xElm1.Elements(), 
        xElm2.Elements(), 
        (x, y) => new Tuple<XElement, XElement>(x, y));
    foreach (var item elements )
    {
        // Elements are equal if they have the same names...
        bool haveSameNames = xElm1.Name.LocalName == xElm2.Name.LocalName;

        // and the same attributes.
        bool haveSameAttributes =
            item.Item1
            // Concatenate and group attributes by their name.
            .Attributes()
            .Concat(item.Item2.Attributes())
            .GroupBy(x => x.Name.LocalName, (key, items) => new
            {
                Name = key,
                Items = items,
                // Attiribute value comparison can be skipped.
                // If enabled compare attribute values.
                // They are equal if the result is only one group.
                HaveSameValues = 
                    compareAttributeValues == false 
                    || items.GroupBy(y => y.Value).Count() == 1,
                Count = items.Count()
            })
            // Each attribute group must contain two items
            // if they are identical (one per element).
            .Where(x => x.Count != 2 || x.HaveSameValues == false)
            .Any() == false;
        if (!haveSameNames || !haveSameAttributes)
        {
            return item;
        }
        else
        {
            // Return the different nodes.
            return Compare(item.Item1, item.Item2, compareAttributeValues);
        }
    }
    // No differences found.
    return null;
}
like image 26
t3chb0t Avatar answered Nov 05 '22 05:11

t3chb0t