Mjakda
Mjakda

Reputation: 53

Using LINQ to surround all "group" elements with the new element "groups" in C#

I'm new to LINQ and C# but I have an xml file generated from a database table. Within the XML document is a element called "group", I would like to wrap all group elements with the element called "groups".

An extract of the XML document is:

<members>
- <user>
  <fullname>John Smith</fullname> 
  <username>SmithA</username> 
  <email>[email protected]/email> 
  <distinguishedName>xxx</distinguishedName> 
  <group>London</group>
  </user>
- <user>
  <fullname>Sue Jones</fullname> 
  <username>JonesS</username> 
  <email>[email protected]/email> 
  <distinguishedName>xxx</distinguishedName> 
  <group>London</group>
  </user>
</members>

The end result I struggling to code in C# ASP.NET is:

<members>
- <user>
  <fullname>John Smith</fullname> 
  <username>SmithA</username> 
  <email>[email protected]/email> 
  <distinguishedName>xxx</distinguishedName> 
  <groups>
  <group>London</group>
  <groups>
  </user>
- <user>
  <fullname>Sue Jones</fullname> 
  <username>JonesS</username> 
  <email>[email protected]/email> 
  <distinguishedName>xxx</distinguishedName> 
  <groups>
  <group>London</group>
  <groups>
  </user>
</members>

Any help will be appreciated.

Upvotes: 0

Views: 816

Answers (2)

Ahmad Mageed
Ahmad Mageed

Reputation: 96477

One way to do this is to:

  1. Go through each user element
  2. Add the new groups element
  3. Add the existing group to the new groups element
  4. Remove the original group element

This approach would be similar to this, provided xml is an XElement:

foreach (var user in xml.Elements("user"))
{
    user.Add(new XElement("groups", user.Element("group")));
    user.Element("group").Remove(); 
}

If you're using an XDocument you could use xml.Root.Elements("user") instead.


EDIT: in response to your comment, if the XML contained multiple numbered groups you could filter on all elements that start with "group" and do almost the same thing.

foreach (var user in xml.Elements("user"))
{
    var groups = user.Elements()
                     .Where(e => e.Name.LocalName.StartsWith("group"))
                     .ToArray();

    // rename groups
    foreach (var group in groups)
        group.Name = "group";

    user.Add(new XElement("groups", groups));
    groups.Remove();
}

Notice that I used ToArray() to prevent the Remove call from reevaluating the expression, which would incorrectly remove the newly added groups element since it too matches the condition of starting with "group." Another way around this would be to change the Where predicate to also check that the name ends with a digit. It's extra work to prevent accidentally selecting any other element that may start with "group" but it's up to you based on your knowledge of the XML structure.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1499770

I think this is what you want - it seems to work for me:

using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        XDocument doc = XDocument.Load("test.xml");
        var groupedGroups = doc.Descendants("group")
                   .GroupBy(x => x.Parent);

        foreach (var groupedGroup in groupedGroups)
        {
            // Create the new element (copies each <group>)
            groupedGroup.Key.Add(new XElement("groups", groupedGroup));

            // Remove all the old ones
            foreach (var element in groupedGroup)
            {
                element.Remove();
            }
        }
        Console.WriteLine(doc);
    }
}

Apologies for the name - it's kinda hard to come up with names when every concept is "group" :)

Upvotes: 3

Related Questions