FRassetto FRassetto - 6 months ago 9
JSON Question

Compare Node Names in every level of XML

I am developing a app with

C#
and trying to complete a
XML
that I have got from a
JSON
. And for the
XML
to be valid for my app, I need to group the elements with the same name under a father element.
For example, I got this
XML


<root>
<row>
<id>0001</id>
<type>credit</type>
<investment>1000</investment>
<ppr>0.83</ppr>
<candidate>
<id>5001</id>
<name>Hugo</name>
</candidate>
<candidate>
<id>5002</id>
<name>Jack</name>
</candidate>
<candidate>
<id>5005</id>
<name>Kate</name>
</candidate>
</row>




And I need to group all the elements with the name candidate, under a father node candidates, like this

<root>
<row>
<id>0001</id>
<type>credit</type>
<investment>1000</investment>
<ppr>0.83</ppr>
<candidates>
<candidate>
<id>5001</id>
<name>Hugo</name>
</candidate>
<candidate>
<id>5002</id>
<name>Jack</name>
</candidate>
<candidate>
<id>5005</id>
<name>Kate</name>
</candidate>
</candidates>
</row>




But here is my problem: I don't know the names that I can receive from the
JSON
. So I need to do this comparison and complete the XML without knowing the "candidate" node name. I need this for any name that I can receive.

Also in this example the
XML
only has 2 levels, but it can have any number of levels. I can iterate over the
XML
without problem with this function:

public void findAllNodes(XmlNode node)
{
Console.WriteLine(node.Name);
foreach (XmlNode n in node.ChildNodes)
findAllNodes(n);
}


How can I make the comparison and group the nodes?

Answer

A fairly naive implementation could use LINQ to group elements by name and add a parent element for those that have more than 1 item in a group. This would be recursive, so child elements of an element were grouped until the tree was exhausted.

The naive-ness is that such a solution would break if there were mixed content elements, and it would group elements that weren't siblings (basically, both issues will result in things ending up in the wrong order). It should give you a good start, and could be enough for your purposes.

private static IEnumerable<XElement> GroupElements(IEnumerable<XElement> elements)
{
    var elementsByName = elements.GroupBy(x => x.Name);

    foreach (var grouping in elementsByName)
    {
        var transformed = grouping.Select(e =>
            new XElement(e.Name,
                GroupElements(e.Elements()),
                e.Attributes(),
                e.Nodes().OfType<XText>()));

        if (grouping.Count() == 1)
        {
            yield return transformed.Single();
        }
        else
        {
            var groupName = grouping.Key + "s";
            yield return new XElement(groupName, transformed);
        }
    }
}

You can use this by parsing/loading your existing XML and then transforming the root elements and creating a new document from those:

var original = XDocument.Parse(xml);
var grouped = new XDocument(GroupElements(original.Elements()));

See this fiddle for a working demo.