mmdfan
mmdfan

Reputation: 346

How to concatenate all child elements with same names values using LINQ to XML

Say XML file looks like this:

<root>
  <book>
    <title>book1Title</title>
    <author>book1Author1</author>
  </book>
  <book>
    <title>book2Title</title>
    <author>book2Author1</author>
    <author>book2Author2</author>
  </book>
  ...
  <book>
    <title>book9Title1</title>
    <title>book9Title2</title>
    <author>book9Author</author>
  </book>
</root>

What i need is output like:

<root>
  <book>
    <title>book1Title</title>
    <author>book1Author1</author>
  </book>
  <book>
    <title>book2Title</title>
    <author>book2Author1, book2Author2</author>
  </book>
  ...
  <book>
    <title>book9Title1, book9Title2</title>
    <author>book9Author</author>
  </book>
</root>

My idea was to use nested foreach:

foreach(var book in doc1.Root.Elements("book"))  //doc1 is XDocument
  {
    foreach (var datafield in book)
      {
        //select all XElements with name datafield.Name
        //put all the values to list
        //delete all selected XElements
        //create new XElement with name datafield.Name and write text values from list to it
      }
  }

But I can't do it like this because:

"foreach statement cannot operate on variables of type 'System.Xml.Linq.XElement' because 'System.Xml.Linq.XElement' does not contain a public definition for 'GetEnumerator'"

even though var book is an element that has another elements unlike the case in XElement Iteration and adding it to parent question.

Upvotes: 2

Views: 1753

Answers (2)

Chris Hayes
Chris Hayes

Reputation: 4037

Sounds like a job for xslt

using System;
using System.Xml;
using System.Xml.Xsl;
using System.IO;

public class Program
{
    public static void Main()
    {
        var xsl = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xsl:stylesheet version=""1.0""
xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">

<xsl:template match=""/"">
  <root>
    <xsl:for-each select=""root/book"">
        <book>
        <author>
        <xsl:for-each select=""author"">
          <xsl:value-of select=""current()""/>
          <xsl:if test=""last() > position()"">
            <xsl:text>, </xsl:text>
          </xsl:if>
        </xsl:for-each>
        </author>
        <title>
          <xsl:for-each select=""title"">
          <xsl:value-of select=""current()""/>
          <xsl:if test=""last() > position()"">
            <xsl:text>, </xsl:text>
          </xsl:if>
        </xsl:for-each>
        </title>
      </book>
    </xsl:for-each>
  </root>
</xsl:template>
</xsl:stylesheet>";

        var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
        <root>
  <book>
    <title>book1Title</title>
    <author>book1Author1</author>
  </book>
  <book>
    <title>book2Title</title>
    <author>book2Author1</author>
    <author>book2Author2</author>
  </book>
  <book>
    <title>book9Title1</title>
    <title>book9Title2</title>
    <author>book9Author</author>
  </book>
</root>";

        string output = String.Empty;
        using (StringReader srt = new StringReader(xsl)) // xslInput is a string that contains xsl
        using (StringReader sri = new StringReader(xml)) // xmlInput is a string that contains xml
        {
            using (XmlReader xrt = XmlReader.Create(srt))
            using (XmlReader xri = XmlReader.Create(sri))
            {
                var xslt = new XslCompiledTransform();
                xslt.Load(xrt);
                using (StringWriter sw = new StringWriter())
                using (XmlWriter xwo = XmlWriter.Create(sw, xslt.OutputSettings)) // use OutputSettings of xsl, so it can be output as HTML
                {
                    xslt.Transform(xri, xwo);
                    output = sw.ToString();
                }
            }
        }
        Console.WriteLine(output);
    }
}

Upvotes: 1

ocuenca
ocuenca

Reputation: 39326

You could do this:

foreach(var book in doc1.Root.Elements("book"))
{
   var authors=String.Join(",",book.Elements("author").Select(e=>e.Value));// get all authors and create the result that you need
   book.Elements("author").Remove();// remove all authors from current book
   book.Add(new XElement("author", authors)); // create one node with all the author's names

   //Do the same with the titles
   var titles=String.Join(",",book.Elements("title").Select(e=>e.Value));
   book.Elements("title").Remove();
   book.Add(new XElement("title", titles));
}

Upvotes: 3

Related Questions