tags:

views:

70

answers:

3

I Have an XML file like the one below:

<?xml version="1.0" ?> 
<System>
  <LP1>
    <Equipment>
      <FromName>Receptacle</FromName> 
      <Wire>1-#10, 1-#10, 1-#10</Wire> 
      <Length>89.8411846136344</Length> 
    </Equipment>
  </LP1>
  <X-1>
    <Equipment>
      <FromName>LP1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>10.170412377555</Length> 
    </Equipment>   
  </X-1>
  <HP1>
    <Equipment>
      <FromName>X-1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>8.2423259796908</Length> 
    </Equipment>
    <Equipment>
      <FromName>AH-1</FromName> 
      <Wire>3-#6, 1-#10</Wire> 
      <Length>32.4019419736209</Length> 
    </Equipment>
    <Equipment>
      <FromName>EF-1</FromName> 
      <Wire>3-#12, 1-#12, 1-#12</Wire> 
      <Length>8.33572105849677</Length> 
    </Equipment>
  </HP1>
</System>

I need to read it, and re-arrange it to look:

<?xml version="1.0" ?>
  <HP1>
    <Equipment>
      <FromName>X-1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>8.2423259796908</Length> 
      <Equipment>
        <FromName>LP1</FromName> 
        <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
        <Length>10.170412377555</Length> 
        <Equipment>
          <FromName>Receptacle</FromName> 
          <Wire>1-#10, 1-#10, 1-#10</Wire> 
          <Length>89.8411846136344</Length> 
        </Equipment>
      </Equipment>
    </Equipment>
    <Equipment>
      <FromName>AH-1</FromName> 
      <Wire>3-#6, 1-#10</Wire> 
      <Length>32.4019419736209</Length> 
    </Equipment>
    <Equipment>
      <FromName>EF-1</FromName> 
      <Wire>3-#12, 1-#12, 1-#12</Wire> 
      <Length>8.33572105849677</Length> 
    </Equipment>
  </HP1>
</System>

Basically, the original XML has separate Elements (LP1, X-1, HP1) that I want to put as sub elements when the equipment "FromName" matches the parent element name of the system.

I am guessing that I will need to do some recursive function, but I am kind of new to C# and programming in general and haven't had much experience with XML or recursive function.

Any help would be appreciated.

Thank you

A: 

There are more than a few tutorials on XML file manipulation. Example of both save & load (reversed for tutorial, but both there):

http://www.java2s.com/Code/CSharp/XML/Loadxmldocumentfromxmlfile.htm

The steps should be roughly...

  • open Input file
  • Load input into XmlNodeList
  • Parse input into ouput XmlNodeList()
  • Save output into new file.

I actually think I see what your wanting, after a bit of staring... I'd have to tink about the parse step for a bit personally.

WernerCD
`XmlNodeList` is, if not entirely obsolete, somewhat dated. The `XDocument`-related classes in LINQ to XML are generally more flexible.
Steven Sudit
A: 

If Xml output is what you're after, Xslt is probably your best bet. You'll need to load the xml and stylesheet into memory and then process accordingly. Something like:

// load the stylesheet
XslTransform stylesheet = new XslTransform();
stylesheet.Load(xsltFilePath);

// load the xml
XPathDocument doc = new XPathDocument(xmlFilePath);

//create the output stream
XmlTextWriter myWriter = new XmlTextWriter("output.xml", null);

// autobots! Transform!
stylesheet.Transform(doc, null, myWriter);        
myWriter.Close();

Regarding the stylesheet, I'm assuming that you'd be taking the last node in the xml file and then nesting the Equipment nodes from above. In order to do the recursive lookups, you'll need a special xslt feature that ships as part of the MSXML parser, specifically ms:node-set. This function let's you perform an xpath query and return a node rather than raw text.

Tip: Xslt is processed from the bottom up, so scroll down and then read-up.

PS -- I'm doing this from memory. Xslt can be finicky so you may want to play with this a bit.

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ms="urn:schemas-microsoft-com:xslt"
                >

  <!-- wildcard: other content is copied as is -->
  <xsl:template match="node()|@*">
       <xsl:copy>
          <xsl:apply-templates select="node()|@*" />
       </xsl:copy>
  </xsl:template>

  <!-- this is our magic recursive template.  Any element that matches "Equipment"
       will caught and processed here. Everything else will default to our
       Wildcard above -->
  <xsl:template match="Equipment">

      <!-- Read the FromName element into the variable $fromName -->
      <xsl:variable name="fromName" select="FromName/text()" />

      <!-- Manually reconstruct the Equipment Node -->
      <Equipment>
         <!-- copy out FromName, Wire and Length -->
         <xsl:copy-of select="FromName" />
         <xsl:copy-of select="Wire" />
         <xsl:copy-of select="Length" />

         <!-- this is how we recursively pull our Element nodes in, which
              will match on this template -->
         <xsl:apply-templates select="ms:node-set('//' + $fromName')/Equipment" />

      </Equipment>

  </xsl:template>

  <!-- Starting point: Find the last node under system -->
  <xsl:template match="/System/*[last()]">
      <!-- copy the elements and attributes of this node to the output stream -->
      <xsl:copy>
           <!-- match templates on its contents -->
           <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>

  </xsl:template>

</xsl:stylesheet>
bryanbcook
-1 for giving out obsolete information. `XslTransform` and `new XmlTextWriter()` have been deprecated since .NET 2.0.
John Saunders
A: 

While it can be definitely compressed to a one-liner as Steven suggested :) I chose to sprawl around a bit to make it more understandable. Of course I still failed so I'll also explain a bit.

XDocument x = XDocument.Parse(xml);

Func<string, XName> xn = s => XName.Get(s, "");

var systems = x.Elements().First();
var equipments = x.Descendants(xn("Equipment"));
equipments.ToList().ForEach(e =>
{
        string fromName = e.Element(xn("FromName")).Value;
        var found = systems.Element(xn(fromName));
        if (found != null)
        {
            e.Add(found.Elements(xn("Equipment")));
            found.Remove();
    };
});

string result = x.ToString();

Assuming xml is the string in the OP, I simply parsed an XDocument from it. Then, a simple shortcut for getting XNames, since the code would have been even more crowded with it inline.

We get all the child elements of System and store it for later; for lack of a better term I called them systems. If there are multiple levels on which these elements may appear, the logic will of course need to be adjusted to find them all reliably.

Then we iterate through all the elements with the name Equipment (equipments), get the FromName element value, and search for an element with the same name in systems. If we find it, we simply add it to the current element and remove it from its parent; since the elements are still all part of the x tree, it works as expected.

Aaand... done. result is the desired result posted by the OP.

Alex Paven
Why `x.Elements().First()` and not `X.Root`?
Steven Sudit
Why define `xn` instead of using regular `XName.Get(s)` or even using `s` itself?
Steven Sudit
For someone unfamiliar with Linq to XML I wanted to draw attention to the XName's namespace as well, because I used to had trouble with it in the beginning. And of course I should have used x.Root, yes. In my defense, it was very late.
Alex Paven
Thanks! This helped!
CornCat