Reputation: 408
Not a big XML expert here... Say I have an Xml node from a larger doc that looks something like this:
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>abc</Sub1>
<Sub2>def</Sub2>
</Element3>
<Element3>
<Sub1>ghi</Sub1>
<Sub2>jkl</Sub2>
</Element3>
<Element3>
<Sub1>mno</Sub1>
<Sub2>pqr</Sub2>
</Element3>
</TopLevel>
(Element3 can repeat an unlimited amount of times) I want to wind up with three nodes, like this:
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>abc</Sub1>
<Sub2>def</Sub2>
</Element3>
</TopLevel>
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>ghi</Sub1>
<Sub2>jkl</Sub2>
</Element3>
</TopLevel>
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>mno</Sub1>
<Sub2>pqr</Sub2>
</Element3>
</TopLevel>
If I knew Element3 could only repeat twice I'd just clone the node, remove the first instance from the original node, remove the second instance from the cloned node and InsertAfter, but I'm not sure how to do it for an unknown number of elements...
Thanks!
Upvotes: 1
Views: 425
Reputation: 116168
Something like this?
string fromXml =
@"
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>abc</Sub1>
<Sub2>def</Sub2>
</Element3>
<Element3>
<Sub1>ghi</Sub1>
<Sub2>jkl</Sub2>
</Element3>
<Element3>
<Sub1>...</Sub1>
<Sub2>...</Sub2>
</Element3>
</TopLevel>
";
XElement from = XElement.Parse(fromXml);
XElement root = new XElement("Root");
foreach (var node in from.Descendants("Element3"))
{
XElement toplevel = new XElement("TopLevel");
toplevel.Add(from.Element("Element1"));
toplevel.Add(from.Element("Element2"));
toplevel.Add(node);
root.Add(toplevel);
}
var final = root.Nodes().Aggregate("",((s,n)=>s+=n.ToString()+"\n"));
Upvotes: 0
Reputation: 43188
If XSLT is an option for you, then the following stylesheet
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/TopLevel">
<root>
<xsl:apply-templates select="Element3"/>
</root>
</xsl:template>
<xsl:template match="Element3">
<TopLevel>
<xsl:apply-templates select="../Element1|../Element2"/>
<xsl:call-template name="identity"/>
</TopLevel>
</xsl:template>
</xsl:stylesheet>
produces the following output (which I formatted):
<root>
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>abc</Sub1>
<Sub2>def</Sub2>
</Element3>
</TopLevel>
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>ghi</Sub1>
<Sub2>jkl</Sub2>
</Element3>
</TopLevel>
<TopLevel>
<Element1>...</Element1>
<Element2>...</Element2>
<Element3>
<Sub1>...</Sub1>
<Sub2>...</Sub2>
</Element3>
</TopLevel>
</root>
The output that you give is a document fragment; I added a root
element so that it could be loaded as a regular document. But XSLT will emit this without the root element as well.
Upvotes: 2
Reputation: 19213
The XML standard only allows a single top level node, so you will need a wrapper around all of your <TopLevel>
. Depending on what is consuming your output that may or may not matter.
XDocument input; //Fill this in somehow
XElement top = input.Element("TopLevel");
XElement elem1 = top.Element("Element1");
XElement elem2 = top.Element("Element2");
XDocument output = new XDocument(); //You may need to do additional initialization here
foreach (XElement elem3 in top.Elements("Element3");
{
output.Add(new XElement("TopLevel", elem1, elem2, elem3));
}
It has been a while since I have generated output files, so I am sorry if my syntax isn't 100% correct.
Upvotes: 1