readElements XML with XmlReader and Linq - c#

My aim is to read this xml file stream:
<?xml version="1.0" encoding="utf-16"?>
<events>
<header>
<seq>0</seq>
</header>
<body>
<orderBookStatus>
<id>100093</id>
<status>Opened</status>
</orderBookStatus>
<orderBook>
<instrumentId>100093</instrumentId>
<bids>
<pricePoint>
<price>1357.1</price>
<quantity>20</quantity>
</pricePoint>
<pricePoint>
<price>1357.0</price>
<quantity>20</quantity>
</pricePoint>
<pricePoint>
<price>1356.9</price>
<quantity>71</quantity>
</pricePoint>
<pricePoint>
<price>1356.8</price>
<quantity>20</quantity>
</pricePoint>
</bids>
<offers>
<pricePoint>
<price>1357.7</price>
<quantity>51</quantity>
</pricePoint>
<pricePoint>
<price>1357.9</price>
<quantity>20</quantity>
</pricePoint>
<pricePoint>
<price>1358.0</price>
<quantity>20</quantity>
</pricePoint>
<pricePoint>
<price>1358.1</price>
<quantity>20</quantity>
</pricePoint>
<pricePoint>
<price>1358.2</price>
<quantity>20</quantity>
</pricePoint>
</offers>
<lastMarketClosePrice>
<price>1356.8</price>
<timestamp>2011-05-03T20:00:00</timestamp>
</lastMarketClosePrice>
<dailyHighestTradedPrice />
<dailyLowestTradedPrice />
<valuationBidPrice>1357.1</valuationBidPrice>
<valuationAskPrice>1357.7</valuationAskPrice>
<lastTradedPrice>1328.1</lastTradedPrice>
<exchangeTimestamp>1304501070802</exchangeTimestamp>
</orderBook>
</body>
</events>
I created (based on the post here: http://blogs.msdn.com/b/xmlteam/archive/2007/03/24/streaming-with-linq-to-xml-part-2.aspx
a function
public IEnumerable<XElement> readElements(XmlReader r, string matchName)
{
//r.MoveToContent();
while (r.Read())
{
switch (r.NodeType)
{
case XmlNodeType.Element:
{
if (r.Name == matchName)
{
XElement el = XElement.ReadFrom(r) as XElement;
if (el != null)
yield return el;
} break;
}
}
}
}
which I planned to use in the following way
IEnumerable<XElement> xBids = readElements(xmlReader, "bids");
publishPricePoint(xBids, "bids");
IEnumerable<XElement> xOffers = readElements(xmlReader, "offers");
publishPricePoint(xOffers, "offers");
where the method publishPricePoint looks like this:
public void publishPricePoint(IEnumerable<XElement> ie, string side)
{
PricePoint p = new PricePoint();
var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
ci.NumberFormat.NumberDecimalSeparator = ".";
var bids = (from b in ie.Elements() select b).ToList();
foreach (XElement e in bids)
{
p.price = decimal.Parse(e.Element("price").Value, ci);
p.qty = int.Parse(e.Element("quantity").Value, ci);
OnPricePointReceived(this, new MessageEventArgs(p, side));
}
}
The problem is, that in this piece of code:
IEnumerable<XElement> xBids = readElements(xmlReader, "bids");
publishPricePoint(xBids, "bids");
IEnumerable<XElement> xOffers = readElements(xmlReader, "offers");
publishPricePoint(xOffers, "offers");
only first two lines work, ie. only bids can be read, not the offers. What is wrong with this? For me, it looks like, there XmlReader disappears after bids have been read.
Thank you for help
================== Other solution =================
while (xmlReader.Read())
{
#region reading bids
if (xmlReader.IsStartElement("bids"))
{
readingBids = true;
readingOffers = false;
}
if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "bids")
{
readingBids = false;
readingOffers = false;
}
if (readingBids == true)
{
if (xmlReader.IsStartElement("price"))
price = xmlReader.ReadElementContentAsDecimal();
if (xmlReader.IsStartElement("quantity"))
{
qty = xmlReader.ReadElementContentAsInt();
OnPricePointReceived(this, new MessageEventArgs(price, qty, "bid"));
}
}
#endregion
#region reading offers
if (xmlReader.IsStartElement("offers"))
{
readingBids = false;
readingOffers = true;
}
if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "offers")
{
readingBids = false;
readingOffers = false;
}
if (readingOffers == true)
{
if (xmlReader.IsStartElement("price"))
price = xmlReader.ReadElementContentAsDecimal();
if (xmlReader.IsStartElement("quantity"))
{
qty = xmlReader.ReadElementContentAsInt();
OnPricePointReceived(this, new MessageEventArgs(price, qty, "offer"));
}
}
}

I think you will have to close and reopen the XmlReader. It simply is in EOF state.
Your solution requires reading everything twice, not too efficient.
Unless your XML is very large (eg > 100 MB) it would be much faster to read it all into an XDocument and filter the bids and offers out with Linq.
Edit: OK, so your data is continuously streamed. That means you can't use a single-tag filter, you'd be skipping the others.
A basic idea: Read every element, with XElement.ReadFrom()
push the elements you want into (separate) queues.
you'll want asynchronous processing. Use the TPL or the (beta) async/await features.

Why dont you do something like this
XDocument document = XDocument.Load(#"XMLFile1.xml");
var bidResults = (from br in document.Descendants("bids")
select br).ToList();
var offerResults = (from or in document.Descendants("offers")
select or).ToList();
then you can just iterate with a foreach (Xelement element in bidResults) to get all the data of the bids and also the data from the offers
foreach (XElement xElement in returnResult)
{
Offer off = new Offer();
off.price = xElement.Element("price") != null ? xElement.Element("price").Value : "";
off.quantity = xElement.Element("quantity") != null ? xElement.Element("quantity").Value : "";
}

Related

How do I get number of a XML nodes child in c# XmlReader?

this is my XML structure:
<classes>
<Base Name="node1">
<Book Name="child01" CoverArtName="C102.jpg" CoverBaseFolder="" Tooltip="" PluginBook=""/>
<Book Name="child02" CoverArtName="C102.jpg" CoverBaseFolder="" Tooltip="" PluginBook=""/>
<Book Name="child03" CoverArtName="C102.jpg" CoverBaseFolder="" Tooltip="" PluginBook=""/>
</Base >
<Base Name="node2">
<Book Name="child01" CoverArtName="C102.jpg" CoverBaseFolder="" Tooltip="" PluginBook=""/>
<Book Name="child02" CoverArtName="C102.jpg" CoverBaseFolder="" Tooltip="" PluginBook=""/>
</Base >
<Base Name="node3">
</Base >
</classes>
how can i get number of children of each node with xmlReader?
Update:
I read my XML with thes code:
List<Bases> base7 = new List<Bases>();
XmlReader xmlReader = XmlReader.Create("Books.xml");
while (xmlReader.Read())
{
if ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == "Base"))
{
if (xmlReader.HasAttributes)
Console.WriteLine(xmlReader.GetAttribute("Name") + ": " + xmlReader.GetAttribute("CoverBaseFolder"));
//Base Name
base7.Add(new Bases() { BaseName = xmlReader.GetAttribute("Name"), Basefolder = xmlReader.GetAttribute("CoverBaseFolder") });
}
}
mainbox.ItemsSource = base7;
The output is a list item with name of node and number of child elements of same node.
This can be done easily by using LinqToXml:
var list = XElement.Load("test.xml")
.Elements("Base")
.Select(e => new
{
Name = e.Attribute("Name").Value,
Count = e.Elements().Count()
})
.ToList();
But if you want to use the XmlReader, for example, to work with xml that does not fit in memory, the code is much more cumbersome:
var bases = new List<Base>();
using (var xmlReader = XmlReader.Create("test.xml"))
{
while (xmlReader.Read())
{
if ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == "Base"))
{
var name = xmlReader.GetAttribute("Name");
int count = 0;
using (var innerReader = xmlReader.ReadSubtree())
{
while (innerReader.Read())
{
if (innerReader.NodeType == XmlNodeType.Element && innerReader.Name == "Book")
count++;
}
}
bases.Add(new Base { Name = name, Count = count });
}
}
}
class Base
{
public string Name { get; set; }
public int Count { get; set; }
}
To count the child nodes is convenient to use the ReadSubtree method.
The XmlReader class has many useful methods. Use ReadToFollowing method allows to slightly reduce code.
var bases = new List<Base>();
using (var xmlReader = XmlReader.Create("test.xml"))
{
while (xmlReader.ReadToFollowing("Base"))
{
string name = xmlReader.GetAttribute("Name");
int count = 0;
using (var innerReader = xmlReader.ReadSubtree())
{
while (innerReader.ReadToFollowing("Book"))
count++;
}
bases.Add(new Base { Name = name, Count = count });
}
}

Parsing XML file in C#

I have an XML File, generated by library written in c++:
<?xml version="1.0" encoding="UTF-8" ?>
<main>
<days>
<day>
<date>27.06.2014</date>
<p/>
</day>
<day>
<date>28.06.2014</date>
<p>
<short>short value</short>
<name>name value</name>
<type>type value</type>
<aud>aud value</aud>
<tr>tr value</tr>
<added>added value</added>
</p>
<p>
<short>short value</short>
<name>name value</name>
<type>type value</type>
<aud>aud value</aud>
<tr>tr value</tr>
<added>added value</added>
</p>
</day>
...
</days>
<di>
<did>
<dayname>Пн</dayname>
<sw>1</sw>
<st>8:00-9:35</st>
</did>
<did>
<dayname>Вт</dayname>
<sw>2</sw>
<st>9:45-11:20</st>
</did>
...
</di>
</main>
I read these xml file like below:
XmlTextReader r = new XmlTextReader("out.xml");
while (r.Read())
{
if (r.Name == "date") { r.Read(); dates[c1] = (r.Value); c1++; }
else if (r.Name == "short") { r.Read(); shorts[c2] = (r.Value); c2++; }
else if (r.Name == "name") { r.Read(); names[c3] = (r.Value); c3++; }
else if (r.Name == "type") { r.Read(); types[c4] = (r.Value); c4++; }
else if (r.Name == "aud") { r.Read(); auds[c5] = (r.Value); c5++; }
else if (r.Name == "tr") { r.Read(); trs[c6] = (r.Value); c6++; }
else if (r.Name == "sw") { r.Read(); ws[c7] = (r.Value); c7++; }
else if (r.Name == "st") { r.Read(); st[c8] = (r.Value); c8++; }
}
r.Close();
How do I parse the file, so that I can understand what the parameter to which the day belongs, especially if there are several in one day ("p" in this case)? Trying to find a solution in the Internet, many people say it is better to use "LINQ to XML" or "XPath", but how to maintain object dependencies nobody says.
Load your xml using the XDocument class and then you can traverse it using the Descendants or Element methods.
var xml = XDocument.Load("your xml string");
foreach (var day in xml.Descendants("day"))
{
var pChildren = day.Descendants("p").ToList();
var aretThereMorePs = pChildren.Count() > 1;
foreach (var p in pChildren)
{
var shortVal = (string)p.Element("short");
//etc
}
}

Parsing this xml

I tried a lot of codes but nothing worked.
I have XML:
<books>
<book>
<title>first title</title>
<publisher>first publisher</publisher>
<description>first description</description>
<published>1410</published>
</book>
<book>
<title>second book</title>
<publisher>second publisher</publisher>
<description>second description</description>
<published>1914</published>
</book>
[another book]
[another book2]
</books>
And I want input like this:
first title | first publisher | first description | 1410
second title | second publisher | second descirpion | 1914
[another books]
"My" Code:
var xdoc = XDocument.Load(#"5.xml");
var entries = from e in xdoc.Descendants("book")
select new
{
Title = (string)e.Element("title"),
Description = (string)e.Element("description")
};
//I DON'T KNOW WHAT IT DO, I FOUND THIS BUT I DON'T KNOW WHAT NEXT
I can parse first book but i can't parse multiple. Sorry for language.
If you want to use an XDocument you may try the following:
using System;
using System.Xml.Linq;
class Program
{
static void Main()
{
var doc = XDocument.Load("5.xml");
var books = doc.Descendants("book");
foreach (var book in books)
{
string title = book.Element("title").Value;
string publisher = book.Element("publisher").Value;
string description = book.Element("description").Value;
string published = book.Element("published").Value;
Console.WriteLine("{0}\t{1}\t{2}\t{3}", title, publisher, description, published);
}
}
}
If on the other hand the XML you are trying to parse is very big and cannot fit into memory it is better to use an XmlReader which will allow you to process it record by record:
using System;
using System.Xml;
class Program
{
static void Main()
{
using (var reader = XmlReader.Create("5.xml"))
{
string title = null, publisher = null, description = null, published = null;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "book")
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}", title, publisher, description, published);
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "title")
{
title = reader.ReadInnerXml();
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "publisher")
{
publisher = reader.ReadInnerXml();
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "description")
{
description = reader.ReadInnerXml();
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "published")
{
published = reader.ReadInnerXml();
}
}
}
}
}
With this approach you can deal with arbitrary large XML files.
You can use this code to parse your XML
XDocument xDoc = XDocument.Load("5.xml");
var books = (from b in xDoc.Descendants("book")
select new
{
title = (string) b.Element("title"),
publisher = (string) b.Element("publisher"),
despription = (string) b.Element("description"),
published = (string) b.Element("published")
}).ToList();
foreach (var book in books)
{
Console.WriteLine("{0} | {1} | {2} |{3}",book.title,book.publisher,book.despription,book.published);
}

How to get value from a specific child element in XML using XmlReader?

Here's the XML string.
<?xml version="1.0" encoding="utf-16"?>
<questionresponses>
<question id="dd7e3bce-57ee-497a-afe8-e3d8d25e2671">
<text>Question 1?</text>
<response>abcdefg</response>
<correctresponse>123</correctresponse>
</question>
<question id="efc43b1d-048f-4ba9-9cc0-1cc09a7eeaf2">
<text>Question 2?</text>
<response>12345678</response>
<correctresponse>123</correctresponse>
</question>
</questionresponses>
So how could I get value of <response> element by given question Id? Say, if I give id value = "dd7e3bce-57ee-497a-afe8-e3d8d25e2671", I'd like to have string value abcdefg returned as result.
var xmlstr = "content from above xml example";
using (var reader = XmlReader.Create(new StringReader(xmlstr)))
{
while(reader.Read())
{
if(reader.IsStartElement())
{
var attr = reader["id"];
if(attr != null && attr == "dd7e3bce-57ee-497a-afe8-e3d8d25e2671")
{
if(reader.ReadToDescendant("response"))
{
result = reader.Value; // <= getting empty string? So what's wrong?
break;
}
}
}
}
}
you might need to do like this , problem i think is reader is not moving to text and because of that you are getting empty
if(reader.ReadToDescendant("response"))
{
reader.Read();//this moves reader to next node which is text
result = reader.Value; //this might give value than
break;
}
Above one is working for me you can try out at your end
I would use LINQ2XML..
XDocument doc=XDocument.Parse(xmlstr);
String response=doc.Elements("question")
.Where(x=>x.Attribute("id")==id)
.Single()
.Element("response")
.Value;
if (reader.NodeType == XmlNodeType.Element)
{
if(reader.Name == "response")
{
reader.read();
var res = reader.Value;
}
}
//it works for me !!!!
You can use this function to get a response for specific questions from XML stored in QuestionXML.xml.
private string getResponse(string questionID)
{
string response = string.Empty;
using (StreamReader sr = new StreamReader("QuestionXML.xml", true))
{
XmlDocument xmlDoc1 = new XmlDocument();
xmlDoc1.Load(sr);
XmlNodeList itemNodes = xmlDoc1.GetElementsByTagName("question");
if (itemNodes.Count > 0)
{
foreach (XmlElement node in itemNodes)
{
if (node.Attributes["id"].Value.ToString() == questionID.Trim())
{
response = node.SelectSingleNode("response").InnerText;
break;
}
}
}
}
return response;
}

How to set a value in XML using C#?

How to change the value of sourcePatientInfo in the following xml file using c#.
I can able to read the value using,
var elem = (from n in xml.Descendants("Slot")
where n.Attribute("name").Value == "sourcePatientInfo"
select n).FirstOrDefault();
How to change the same using C#?
<?xml version="1.0" encoding="utf-8"?>
<rs:SubmitObjectsRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rs="urn:oasis:names:tc:ebxml-regrep:registry:xsd:2.1" xmlns:rim="urn:oasis:names:tc:ebxml-regrep:rim:xsd:2.1" xmlns="urn:oasis:names:tc:ebxml-regrep:rim:xsd:2.1">
<LeafRegistryObjectList>
<ObjectRef id="urn:uuid:93606bcf-9494-43ec-9b4e-a7748d1a838d" />
<ExtrinsicObject id="Document01" mimeType="application/dicom" objectType="urn:uuid:7edca82f-054d-47f2-a032-9b2a5b5186c1">
<Name>
<LocalizedString value="Physical" />
</Name>
<Description />
<Slot name="sourcePatientId">
<ValueList>
<Value>pid1^^^&1.2.3&ISO</Value>
</ValueList>
</Slot>
<Slot name="sourcePatientInfo">
<ValueList>
<Value>PID-3|pid1^^^&1.2.3&ISO</Value>
<Value>PID-5|Doe^John^^^</Value>
<Value>PID-7|19560527</Value>
<Value>PID-8|M</Value>
<Value>PID-11|100 Main St^^Metropolis^Il^44130^USA</Value>
</ValueList>
</Slot>
I would like to change the values using c#. Am not able to figure out the way. Any Help to resolve this issue will be appreciated.
I want to change the
<Slot name="sourcePatientInfo">
<ValueList>
<Value>PID-3|pid1^^^&1.2.3&ISO</Value>
<Value>PID-5|Doe^John^^^</Value>
to the following value
<Slot name="sourcePatientInfo">
<ValueList> <Value>PID-3|MyPID</Value>
<Value>PID-5|MyName</Value>
I have also tried the following the code,
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDoc1.NameTable);
namespaceManager.AddNamespace("rs", "urn:oasis:names:tc:ebxml-regrep:registry:xsd:2.1");
var query = "/rs:SubmitObjectsRequest/LeafRegistryObjectList/ExtrinsicObject";
XmlNodeList nodeList = xmlDoc1.SelectNodes(query, namespaceManager);
foreach (XmlNode node1 in nodeList)
{
if (node1.Attributes["Slot"].Value == "sourcePatientInfo")
{
node1.Attributes["ValueList"].Value = "Myvalue";
}
}
In this code, nodelist.count is always zero :-(. Kindly help me to resolve the issue.
If you need to update first two values:
var slot = xml.Descendants("Slot")
.Where(n => n.Attribute("name").Value == "sourcePatientInfo")
.FirstOrDefault();
if(slot == null)
{
throw new WhateverAppropriateHereEcxeption();
}
var values = slot.Descendants("Value").ToList();
if(values.Count < 2)
{
throw new WhateverAppropriateHereEcxeption();
}
values[0].Value = "PID-3|MyPID" // updating the first value
values[1].Value = "PID-5|MyName" // updating the second value
if you have to search by value you can do:
bool UpdateValue(XElement slot, string oldValue, string newValue)
{
var elem = slot.Descendants("Slot")
.Where(n => n.Name == "Value" && n.Value == oldValue)
.FirstOrDefault();
if(elem != null)
{
elem = newValue;
return true;
}
return false;
}
and inside some function
var slot = xml.Descendants("Slot")
.Where(n => n.Attribute("name").Value == "sourcePatientInfo")
.FirstOrDefault();
if(slot == null)
{
throw new WhateverAppropriateHereEcxeption();
}
UpdateValue(slot, "PID-3|pid1^^^&1.2.3&ISO", "PID-3|MyPID");
UpdateValue(slot, "PID-5|Doe^John^^^", "PID-5|MyName");
UPD when you call xml.Descendants("Slot") xml look only for elements in default namespace. I use an extension method as a quick workaround to avoid that:
public static IEnumerable<XElement> NsDescendants(this XContainer e, string elementName)
{
return e.Descendants().Where(d => d.Name.LocalName == elementName);
}
Finally my problem is solved with the following code.
XmlDocument xmlDocSOR = new XmlDocument();
XmlDocSOR.Load("filename.xml");
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDocSOR.NameTable);
namespaceManager.AddNamespace("rs", "urn:oasis:names:tc:ebxml-regrep:registry:xsd:2.1");
namespaceManager.AddNamespace("ns", "urn:oasis:names:tc:ebxml-regrep:rim:xsd:2.1");
var query = "/rs:SubmitObjectsRequest/ns:LeafRegistryObjectList/ns:ExtrinsicObject/ns:Slot";
XmlNodeList nodeList = xmlDocSOR.SelectNodes(query, namespaceManager);
foreach (XmlNode plainnode in nodeList)
{
if (plainnode.Attributes["name"].Value == "sourcePatientId")
{
XmlNode childnode = plainnode.LastChild;
XmlElement ee1 = (XmlElement)childnode.FirstChild;
ee1.InnerText = sPatientID;
}
}
xmlDocSOR.Save("filename.xml");

Categories