Reputation: 901
Say I have this XML:
<fields>
<field fieldid="fdtElem3Group">
<value actionid="1" actiontype="review">123456789</value>
<value actionid="2" actiontype="review">123456789</value>
<value actionid="3" actiontype="review">123456789</value>
<value actionid="4" actiontype="review">123456789</value>
<value actionid="5" actiontype="review">123456789</value>
</field>
<field fieldid="fdtElem7Group">
<value actionid="1" actiontype="review">29/10/75</value>
<value actionid="2" actiontype="review">29/10/74</value>
<value actionid="3" actiontype="review">29/10/74</value>
<value actionid="4" actiontype="review">29/10/76</value>
<value actionid="5" actiontype="review">29/10/74</value>
</field>
</fields>
I'm trying to get the value of the last 'value' element of each respective 'field' element using XmlReader
. How would I do that please? This doesn't work:
while (xmlReader.Read())
{
if ((xmlReader.NodeType == System.Xml.XmlNodeType.Element) && (xmlReader.Name == "field"))
{
xmlReader.ReadToDescendant("value");
while (xmlReader.ReadToNextSibling("value"))
{
//just iterate over until it reaches the end
}
xmlReader.Read();
Console.WriteLine(xmlReader.Value);
}
}
Upvotes: 0
Views: 1579
Reputation: 116805
If you don't want to load your entire XML into an XDocument
(because, e.g., it is very large), you can adopt a hybrid approach where you use an XmlReader
to iterate through the <field>
and <value>
elements in your XML file, load each <value>
element into an XElement
, then select the last one of each parent <field>
. This keeps the memory footprint small and constant no matter how large the XML file grows.
First introduce the following two extension methods:
public static class XmlReaderExtensions
{
public static IEnumerable<IEnumerable<XElement>> ReadNestedElements(this XmlReader xmlReader, string outerName, string innerName)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == outerName)
{
using (var subReader = xmlReader.ReadSubtree())
{
yield return subReader.ReadElements(innerName);
}
}
xmlReader.Read();
}
}
public static IEnumerable<XElement> ReadElements(this XmlReader xmlReader, string name)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == name)
{
var element = (XElement)XNode.ReadFrom(xmlReader);
yield return element;
}
else
{
xmlReader.Read();
}
}
}
}
Then your algorithm to get the last 'value' element of each respective 'field' element becomes very simple:
public static List<string> LastFieldValues(XmlReader reader)
{
var query = reader.ReadNestedElements("field", "value")
.Select(l => l.LastOrDefault())
.Where(v => v != null)
.Select(v => (string)v);
return query.ToList();
}
Notes:
XmlReaderExtensions.ReadNestedElements()
returns an enumerable of enumerables of <value>
elements belonging to <field>
elements. Enumerable.LastOrDefault()
is then used to select the last <value>
belonging to each <field>
.
Whenever working directly with XmlReader
, be sure to test XML both with and without indentation, for reasons explained here and here.
XmlReader.ReadSubtree()
leaves the XmlReader
positioned on the EndElement
node of the element being read, while XNode.ReadFrom()
leaves the reader positioned immediately after the EndElement
node of the element being read. Just an annoying inconsistency to watch out for.
On the other hand, if you are willing to load the entire XML into memory as an XDocument
, this can be done very simply using XPathSelectElements()
:
// Parse the XML into an XDocument.
// You can use use XDocument.Load(fileName) to load from a file
var doc = XDocument.Parse(xmlString);
var xpathLastValues = doc
.XPathSelectElements(@"//fields/field/value[last()]")
.Select(e => e.Value)
.ToList();
Sample fiddle.
Upvotes: 1
Reputation: 29
Hope this helps !
static void Main(string[] args)
{
string xml = @"<fields><field fieldid='fdtElem3Group'><value actionid='1' actiontype='review'>123456789</value><value actionid='2' actiontype='review'>123456789</value><value actionid='3' actiontype='review'>123456789</value><value actionid='4' actiontype='review'>123456789</value><value actionid='5' actiontype='review'>123456789</value></field><field fieldid='fdtElem7Group'><value actionid='1' actiontype='review'>29/10/75</value> <value actionid='2' actiontype='review'>29/10/74</value><value actionid='3' actiontype='review'>29/10/74</value><value actionid='4' actiontype='review'>29/10/76</value><value actionid='5' actiontype='review'>29/10/74</value></field></fields>";
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
foreach (XmlNode item in xmlDocument.DocumentElement.ChildNodes)
{
Console.WriteLine(item.LastChild.InnerXml);
}
Console.ReadKey();
}
Upvotes: 1
Reputation: 1859
Sorry, just read now you're looking for an xmReader solution. But using XDocument and Linq, you could do the following:
string xml = @"<fields>
<field fieldid=""fdtElem3Group"">
<value actionid=""1"" actiontype=""review"">123456789</value>
<value actionid=""2"" actiontype=""review"">123456789</value>
<value actionid=""3"" actiontype=""review"">123456789</value>
<value actionid=""4"" actiontype=""review"">123456789</value>
<value actionid=""5"" actiontype=""review"">123456789</value>
</field>
<field fieldid=""fdtElem7Group"">
<value actionid=""1"" actiontype=""review"">29/10/75</value>
<value actionid=""2"" actiontype=""review"">29/10/74</value>
<value actionid=""3"" actiontype=""review"">29/10/74</value>
<value actionid=""4"" actiontype=""review"">29/10/76</value>
<value actionid=""5"" actiontype=""review"">29/10/74</value>
</field>
</fields>";
var xmlDoc = XDocument.Parse(xml).Root;
var lastElements = xmlDoc.Descendants("field").Select(x => x.LastNode);
Upvotes: 1