tags:

views:

709

answers:

3

I'm trying to filter an Xml document so into a subset of itself using XPath.

I have used XPath get an XmlNodeList, but I need to transform this into an XML document.

Is there a way to either transform an XMLNodeList into an XmlDocument or to produce an XmlDocument by filtering another XmlDocument directly?

A: 

I've actualy just thought of a way to do this but it doesn't seem very elegant.

use a StringBuilder to combine the OuterXml of each of the XmlNodes in the XmlNodeList...

As I said it's inelegant but I think it might work. I'd appreciate any other suggestion...

Omar Kooheji
+4  A: 

With XmlDocument, you will need to import those nodes into a second document;

    XmlDocument doc = new XmlDocument();
    XmlElement root = (XmlElement)doc.AppendChild(doc.CreateElement("root"));
    XmlNodeList list = // your query
    foreach (XmlElement child in list)
    {
        root.AppendChild(doc.ImportNode(child, true));
    }
Marc Gravell
In the end this is the answer I went with.
Omar Kooheji
+1  A: 

This is a pretty typical reason to use XSLT, which is an efficient and powerful tool for transforming one XML document into another (or into HTML, or text).

Here's a minimal program to perform an XSLT transform and send the results to the console:

using System;
using System.Xml;
using System.Xml.Xsl;

namespace XsltTest
{
    class Program
    {
        static void Main(string[] args)
        {
            XslCompiledTransform xslt = new XslCompiledTransform();
            xslt.Load("test.xslt");

            XmlWriter xw = XmlWriter.Create(Console.Out);
            xslt.Transform("input.xml", xw);
            xw.Flush();
            xw.Close();

            Console.ReadKey();
        }
    }
}

Here's the actual XSLT, which is saved in test.xslt in the program directory. It's pretty simple: given an input document whose top-level element is named input, it creates an output element and copied over every child element whose value attribute is set to true.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/input">
      <output>
        <xsl:apply-templates select="*[@value='true']"/>
      </output>
    </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

And here's input.xml:

<?xml version="1.0" encoding="utf-8" ?>
<input>
  <element value="true">
    <p>This will get copied to the output.</p>
    <p>Note that the use of the identity transform means that all of this content
    gets copied to the output simply because templates were applied to the 
    <em>element</em> element.
  </p>
  </element>
  <element value="false">
    <p>This, on the other hand, won't get copied to the output.</p>
  </element>
</input>
Robert Rossney
It would be but I'm filtering dynamically so I don't know beforehand what I need to filter it to. +1 for XSLT which are pretty cool.
Omar Kooheji
There are plenty of ways to filter dynamically with XSLT. You can pass arguments into an transform; those arguments can contain filtering criteria which your transform reads and applies.
Robert Rossney