tags:

views:

129

answers:

2

The scope of XML and DOM in .NET 3.5 is so large that I'm having trouble coming up with a simple solution to my problem without using too many lines of messy code. Since people here always come up with some elegant solutions, I thought it would be a good question.

How do I take in an XML file (that I created and saved earlier in the program) that has n instances of the 'unit' node beneath the root 'report' node to create an XML DOM that has the 'report' values and one 'unit'.

I need to do this for each unit and then create an HTML file using my existing XSL transformation stylesheet on the new XML DOMs. I can already get it to work for all the units (the existing XML file), but I need a report for each unit.

EDIT (per comment request):

What it looks like:

<report>
  <report_name>Name</report_name>
  <report_date>yyyy/mm/dd</report_date>
  <unit>
    <ip>127.0.0.1</ip>
    <label>localhost</label>
    ..etc
  </unit>
  <unit>
    <ip>255.255.255.255</ip>
    <label>broadcast</label>
    ..etc
  </unit>
<report>

And I want each of:

<report>
  <report_name>Name</report_name>
  <report_date>yyyy/mm/dd</report_date>
  <unit>
    <ip>127.0.0.1</ip>
    <label>localhost</label>
    ..etc
  </unit>
</report>

and

<report>
  <report_name>Name</report_name>
  <report_date>yyyy/mm/dd</report_date>
  <unit>
    <ip>255.255.255.255</ip>
    <label>broadcast</label>
    ..etc
  </unit>
<report>
+1  A: 
foreach (XmlElement xmlUnit in xmlMain.SelectNodes("/report/unit"))
{
    var xmlDest = new XmlDocument();
    xmlDest.AppendChild(xmlDest.CreateElement("report"));

    // Add the report properties...
    foreach ( XmlElement xmlValue in xmlMain.SelectNodes( "/report/report_name | /report/report_date" ) )
     xmlDest.DocumentElement.AppendChild(xmlDest.ImportNode(xmlValue, true));

    // Add the "<unit>" element from the main document...
    xmlDest.DocumentElement.AppendChild(xmlDest.ImportNode(xmlUnit, true));

    // Now generate report using xmlDest
}
David
Nice, this is along the lines of what I was hoping for.Is there anyway to avoid explicitly writing all nodes for the report properties? Is there any way to select all in case I go and add more properties later on?
cgyDeveloper
Something like this should be close: /report/node()[ name() != 'unit' ]
David
That did it, thanks!
cgyDeveloper
A: 

If you didn't need to access information outside the unit element, you could do this:

foreach (XmlNode n in d.SelectNodes("/report/unit"))
{
   using (StringReader sr = new StringReader(n.OuterXml))
   using (XmlReader xr = XmlReader.Create(sr))
   using (XmlWriter xw = XmlWriter.Create(Console.Out))
   {
       xslt.Transform(xr, xw);
   }
}

The downside of this approach is that the XSLT can access only the XML in the selected node. If you need to access XML elsewhere in the document, you need to tell the transform which node to process:

for (int i = 0; i < d.SelectNodes("/report/unit").Count; i++)
{
    XsltArgumentList args = new XsltArgumentList();
    args.AddParam("position", "", i + 1);
    using (StringReader sr = new StringReader(d.OuterXml))
    using (XmlReader xr = XmlReader.Create(sr))
    using (XmlWriter xw = XmlWriter.Create(Console.Out))
    {
        xslt.Transform(xr, args, xw);
    }
}

...and have your XSLT include the likes of this:

<xsl:param name="position"/>
<xsl:template match="/">
    <xsl:apply-templates select="/report/unit[$position=position()]"/>
</xsl:template>
Robert Rossney