I'm trying to filter an XML file using XPath. The XPath that I'm using is definitely filtering to the data that I want, but I'm just not sure how to filter the file overall.
Here's the sample XML file:
<fields>
<field name='F'>
<field name='0'><value>F.0 stuff</value></field>
<field name='1'><value>F.1 stuff</value></field>
<field name='2'><value>F.2 stuff</value></field>
</field>
<field name='B'>
<field name='0'><value>B.0 stuff</value></field>
<field name='1'><value>B.1 stuff</value></field>
<field name='2'><value>B.2 stuff</value></field>
<field name='3'><value>B.3 stuff</value></field>
</field>
</fields>
Here's the desired output:
<fields>
<field name='F'>
<field name='1'><value>F.1 stuff</value></field>
<field name='2'><value>F.2 stuff</value></field>
</field>
<field name='B'>
<field name='3'><value>B.3 stuff</value></field>
</field>
</fields>
The solution does not necessarily have to be solved by XPath, but since this is a .NET application, .NET APIs would be appreciated! The following code can be cut and pasted into LINQPad with no edits to see what I'm trying to do.
var doc = XDocument.Parse(@"
<fields>
<field name='F'>
<field name='0'><value>F.0 stuff</value></field>
<field name='1'><value>F.1 stuff</value></field>
<field name='2'><value>F.2 stuff</value></field>
</field>
<field name='B'>
<field name='0'><value>B.0 stuff</value></field>
<field name='1'><value>B.1 stuff</value></field>
<field name='2'><value>B.2 stuff</value></field>
<field name='3'><value>B.3 stuff</value></field>
</field>
</fields>");
doc.Dump("Original XML");
var xpath = "//fields/field[@name='F']/field[@name='1' or @name='2'] | //fields/field[@name='B']/field[@name='3']";
doc.XPathSelectElements(xpath).Dump("XPath Combined");
var desired = XDocument.Parse(@"
<fields>
<field name='F'>
<field name='1'><value>F.1 stuff</value></field>
<field name='2'><value>F.2 stuff</value></field>
</field>
<field name='B'>
<field name='3'><value>B.3 stuff</value></field>
</field>
</fields>");
desired.Dump("Desired Filtered XML");
EDIT: I completely missed XML Transforms - thank you for the solution! Here's the solution you can paste into LINQPad to see it working:
var filterString = @"@name=""F""]/field[@name=""0""] | field[@name=""B""]/field[not(@name=""3"")";
var xslFmt = @"
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:output omit-xml-declaration='yes' indent='yes'/>
<xsl:template match='node()|@*'>
<xsl:copy>
<xsl:apply-templates select='node()|@*'/>
</xsl:copy>
</xsl:template>
<xsl:template match=
'field[{0}]
'/>
</xsl:stylesheet>";
var xslMarkup = string.Format(xslFmt, filterString);
var xmlTree = XDocument.Parse(@"
<fields>
<field name='F'>
<field name='0'><value>F.0 stuff</value></field>
<field name='1'><value>F.1 stuff</value></field>
<field name='2'><value>F.2 stuff</value></field>
</field>
<field name='B'>
<field name='0'><value>B.0 stuff</value></field>
<field name='1'><value>B.1 stuff</value></field>
<field name='2'><value>B.2 stuff</value></field>
<field name='3'><value>B.3 stuff</value></field>
</field>
</fields>");
xmlTree.Dump("Original XML");
// Code from MSDN: http://msdn.microsoft.com/en-us/library/bb675186.aspx
var newTree = new XDocument();
using (var writer = newTree.CreateWriter()) {
// Load the style sheet.
var xslt = new XslCompiledTransform();
xslt.Load(XmlReader.Create(new StringReader(xslMarkup)));
// Execute the transform and output the results to a writer.
xslt.Transform(xmlTree.CreateReader(), writer);
}
newTree.Dump("Transformed XML");