简体   繁体   中英

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.

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));
}

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);
    }
}

Wouldn't this XML work better....

<root>
    <book>
         <title>book1Title</title>
         <authors>
              <author>book1Author1</author>
         </authors>
     </book>
     <book>
          <title>book2Title</title>
          <authors>
               <author>book2Author1</author>
               <author>book2Author2</author>
          </authors>
     </book>
</root>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM