Reputation: 346
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
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
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