Bill Osuch
Bill Osuch

Reputation: 408

How to split this XmlDocument?

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

Answers (3)

L.B
L.B

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

harpo
harpo

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

Guvante
Guvante

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

Related Questions