2

I am trying to convert an XML to a nested XML with an element's attribute. I googled a lot and looked through some questions and answers here, but I still can't get my mind around it.

I want to group the child node under same author's name using C#, linq to xml.

Sample XML:

<authors>
  <author name="John">
    <books>
       <book type="Children">ABC</book>
    </books>
    <published> ---<new
      <print year="2011"> ---<new
         <publisher>Msoft</publisher> ---<new
      </print> ---<new
    </published> ---<new
  </author>
  <author name="May">
    <books>
       <book type="Children">A beautiful day</book>
    </books>
    <published> ---<new
      <print year="2011"> ---<new
         <publisher>hardsoft</publisher> ---<new
      </print> ---<new
    </published> ---<new
  </author>
  <author name="John">
    <books>
       <book type="Fiction">BBC</book>
    </books>
    <published> ---<new
      <print year="2013"> ---<new
         <publisher>dsney</publisher> ---<new
      </print> ---<new
    </published> ---<new
  </author>
</authors>

Output expect:

<authors>
  <author name="John">
    <books>
       <book type="Children">ABC</book>
       <book type="Fiction">BBC</book>
    </books>
    <published>
      <print year="2011">
         <publisher>Msoft</publisher>
         <publisher>hardsoft</publisher>
      </print>
    </published>
  </author>
  <author name="May">
    <books>
       <book type="Children">A beautiful day</book>
    </books>
    <published>
      <print year="2013">
         <publisher>dsney</publisher>
      </print>
    </published>
  </author>
</authors>

If there are additional nodes with attribute need to group under the same author, example should I just add another grouping or select the element from the previous group?

So far, I have tried:

XDocument doc = XDocument.Load(pathtoxmlfile);
var query = from e in doc.Elements("author")
        group e by e.Attribute("name").Value into g
        select new XElement("author", new XAttribute("name", g.Key),
               new XElement("books", 
                   g.Select(x => x.Element("books").Elements("book"))
                   , new XElement("published",
                         g.Select(y=>y.Elements("publisher")
                   )
            )
        )
 );

 XElement root = new XElement("authors", query);

It only output me inside and author node with no entry.

<author>
  <books>...this part is output as expect...
  </books>
  <published>
    <publisher />
  </published>
</author>
4
  • 3
    It doesn't looks like you are actually trying to convert something. I see only input data and expected result in your question - and can't see any of your attempts to do it. Any efforts so far? What problems have you faced? Commented Jul 9, 2015 at 12:53
  • 1
    Have you considered solving this using Xsl transformation? If your output is also Xml and you have no need for complex processing of the data, this should be easier than using Linq. Commented Jul 9, 2015 at 12:59
  • 2
    you could take a look at stackoverflow.com/questions/5603284/linq-to-xml-groupby. seems pretty similar to what you're trying to do. Commented Jul 9, 2015 at 13:00
  • @Andy Korneyev - Sorry, I was in a rush and didn't post the codes I had already tried. previous I coded " var query = from author in root.Root.Elements("author") group author by author.Attribute("name") into groupedAuthor select groupedAuthor; tried this code in VS but only give me an error. Commented Jul 10, 2015 at 7:55

3 Answers 3

1
string xml = @"<authors>
  <author name=""John"">
    <books>
       <book type=""Children"">ABC</book>
    </books>
  </author>
  <author name=""May"">
    <books>
       <book type=""Children"">A beautiful day</book>
    </books>
  </author>
  <author name=""John"">
    <books>
       <book type=""Fiction"">BBC</book>
    </books>
  </author>
</authors>";

XElement root = XElement.Parse(xml);
var query = from e in root.Elements("author")
            group e by e.Attribute("name").Value into g
            select new XElement("author", new XAttribute("name", g.Key),
                   new XElement("books", 
                                 g.Select(x => x.Element("books").Elements("book")).ToArray()));

 XElement newRoot = new XElement("authors", query.ToArray());
 Console.WriteLine(newRoot);
Sign up to request clarification or add additional context in comments.

1 Comment

both .ToArray() can be removed
0

Let's assume you already have the ungrouped XML in a document named ungrouped.xml for this example.

XDocument doc = XDocument.Load(ungrouped.xml);
var groupedAuthors = doc.Root.Elements("author")
                        .GroupBy(a => a.Attribute("name").Value, 
                                 a => a.Descendants("book"))
                        .Select(g => new XElement("author", new XAttribute("name", g.Key,
                                                            new XElement("books", g.ToArray())
                                                 )
                                );



XElement root = new XElement("authors", groupedAuthors);

Let's step through the above code to explain what's going on here.

The first is we load an XDocument object with the unorganized XML file in your input example. Then we start utuilzing the Linq to XML stuff

  1. Get all elements named "author": doc.Root.Elements("author")
  2. Group elements using the value of the attribute named "name" and insert listing of elements named "book". Since those elements are under another element named "books" we want to get descendants of the author tag instead of the direct children, which is what Elements() is used. The lambda expression

    • a => a.Attribute("name").Value gets the value we are grouping on, but we aware that this can throw a NullReferenceException if the "name" tag is missing
    • a => a.Descendants("book") gets the elements named "book" that are somewhere under the author tag. This allows us to skip having to specify the "books" tag directly (a.Element("books").Elements("book") is the long way of saying the same thing)
  3. Create an IEnumerable<XElement> that contains the grouped elements. the g.Key is the name of the author that we grouped on, and g is the IEnumerable of the grouped objects under that key.

  4. Finally, we create a root node and add all of our new elements under it!

Comments

0

Try this

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


namespace ConsoleApplication34
{
    class Program
    {

        static void Main(string[] args)
        {
            string input =
                "<authors>" +
                  "<author name=\"John\">" +
                    "<books>" +
                       "<book type=\"Children\">ABC</book>" +
                    "</books>" +
                  "</author>" +
                  "<author name=\"May\">" +
                    "<books>" +
                       "<book type=\"Children\">A beautiful day</book>" +
                    "</books>" +
                  "</author>" +
                  "<author name=\"John\">" +
                    "<books>" +
                       "<book type=\"Fiction\">BBC</book>" +
                    "</books>" +
                  "</author>" +
                "</authors>";

            XElement element = XElement.Parse(input);
            var authors = element.Descendants("author").GroupBy(x => x.Attribute("name").Value).ToList();
            foreach (var author in authors)
            {
                var books = author.Descendants("books");
                for (int i = author.Count() - 1; i >= 1 ; i--)
                {
                    var book = author.Skip(i).FirstOrDefault().Descendants("book");
                    books.Elements("book").First().Add(book);
                    author.Skip(i).DescendantNodesAndSelf().Remove();
                }
            }
        }

    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.