Only getting last element of Group element text when ReadingXML file - c#

I am tryin to read an XML file but when I read it in it's only picking up on the last value of my Group element instead of setting the text property of the Group at the endelement break for each Group. All help is appreciated.
public static void ReadFile()
{
string MyText = string.Empty;
string Notes = string.Empty;
string UserName = string.Empty;
string Password = string.Empty;
string Url = string.Empty;
string Title = string.Empty;
string MyGroup = string.Empty;
Group group = new Group("Hello", 0);
XmlTextReader xmlReader = new XmlTextReader("C:/KeyText.xml");
while (xmlReader.Read()) // Read nodes sequentially
{
if (xmlReader.Name == "Group" & xmlReader.NodeType.ToString() == "EndElement")
{
group.Text = MyText;
AddGroupXML(group);
}
if (xmlReader.NodeType == XmlNodeType.Element)
{
if (xmlReader.LocalName == "Text")
{
MyText = xmlReader.ReadString();
}
if (xmlReader.LocalName == "Notes")
{
Notes = xmlReader.ReadString();
}
if (xmlReader.LocalName == "UserName")
{
UserName = xmlReader.ReadString();
}
if (xmlReader.LocalName == "Password")
{
Password = xmlReader.ReadString();
}
if (xmlReader.LocalName == "Url")
{
Url = xmlReader.ReadString();
}
if (xmlReader.LocalName == "Title")
{
Title = xmlReader.ReadString();
}
}
if (xmlReader.Name == "Key" & xmlReader.NodeType.ToString() == "EndElement")
{
Key MyKey = new Key();
MyKey.Notes = Notes;
MyKey.Title = Title;
MyKey.UserName = UserName;
MyKey.Url = Url;
MyKey.Password = Password;
group.Keys.Add(MyKey);
}
}
xmlReader.Close();
GroupsRead.Invoke();
}
XML:
<?xml version="1.0"?>
<Groups>
<Group>
<ImageIndex>0</ImageIndex>
<Text>wcwpgcuotlx</Text>
<Keys>
<Dog/>
<Key>
<Notes>4ktaiyduner</Notes>
<Password>0y2cg1kodre</Password>
<Title>a2yj4biqd5u</Title>
<Url>de2uym5vyg1</Url>
<UserName>ogcl3uyvy2r</UserName>
</Key>
<Key>
<Notes>3dmchyaqcvt</Notes>
<Password>lbgfralkng4</Password>
<Title>fnha4ienzua</Title>
<Url>n3pmk5elaso</Url>
<UserName>njk55ov4eef</UserName>
</Key>
</Keys>
</Group>
<Group>
<ImageIndex>0</ImageIndex>
<Text>vrmijzokft2</Text>
<Keys>
<Dog/>
</Keys>
</Group>
</Groups>

I think your problem is that you have one Group object that you use for all groups. So, since you call AddGroupXML() with the same argument every time, the result will contain the same object several times. And the text value will be the last you set for it.
To fix this, change the code that adds the group to something like:
group.Text = MyText;
AddGroupXML(group);
group = new Group("Hello", 0);
In the future, it might be easier if you used LINQ to XML to process the XML. Your code would be much simpler if you used that. (The disadvantage of that approach is that it loads the whole XML into memory, which is a bad idea for huge XML files.)

Related

XML parsing using C#, get specific value

I have the following XML:
<XML>
<Properties>
<Property name="ActionLogPrompt">2</Property>
<Property name="Answer"></Property>
<Property name="SubQBackColour">#FF0000</Property>
</Properties>
</XML>
What I am trying to do is to find SubQBackColour and get the value of "#FF0000". I am trying to do that with the code below, but I cannot figure out what to call to get the name of the property and check for this SubQBackColour.
while (reader.Read())
{
node = reader.NodeType;
if (node == XmlNodeType.Element)
{
if (reader.Name.Equals("XML"))
{
reader.Read();
if (reader.Name.Equals("Properties"))
{
reader.Read();
if (reader.Name.Equals("Property"))
{
reader.Read();
node = reader.NodeType;
if ((node == XmlNodeType.Text))
{
string thingy = reader.GetAttribute("name");
}
}
}
}
}
}
Try this...
string sXML = "<XML>"+
"<Properties>"+
"<Property name=\"ActionLogPrompt\">2</Property>"+
"<Property name=\"Answer\"></Property>"+
"<Property name=\"SubQBackColour\">#FF0000</Property>"+
"</Properties></XML>";
XDocument doc3 = XDocument.Parse(sXML);
var v= from p in doc3.Descendants("Property")
where p.Value == "#FF0000"
select p.Attribute("name").Value;
Console.WriteLine(v);
You will get value of "name" attribute as "SubQBackColour". You can change the query to get whatever you want.
The best implementation would be to create a class to hold property name and value and get that as output.
Something like
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
}
string sXML = "<XML>"+
"<Properties>"+
"<Property name=\"ActionLogPrompt\">2</Property>"+
"<Property name=\"Answer\"></Property>"+
"<Property name=\"SubQBackColour\">#FF0000</Property>"+
"</Properties></XML>";
XDocument doc3 = XDocument.Parse(sXML);
var v4 = from p in doc3.Descendants("Property")
where p.Value == "#FF0000"
select new Property
{
Name = p.Attribute("name").Value,
Value = p.Value
};
Console.WriteLine(v4.FirstOrDefault().Name);
You can try this code
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("c:\\yourxml.xml");
XmlNodeList nodeList = xmlDoc.DocumentElement.SelectNodes("/Properties");
string actionLogPrompt= "", answer= "", subQBackColour="";
foreach (XmlNode node in nodeList)
{
actionLogPrompt = node.SelectSingleNode("ActionLogPrompt").InnerText;
answer = node.SelectSingleNode("Answer").InnerText;
subQBackColour= node.SelectSingleNode("SubQBackColour").InnerText;
MessageBox.Show(actionLogPrompt+ " " + answer+ " " + subQBackColour);
}

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;
}

Specific XML Attribute values into Class List

Its been a while since I last tried to program and has never worked with XML before. I have a internal website that display XML
<Source>
<AllowsDuplicateFileNames>YES</AllowsDuplicateFileNames>
<Description>The main users ....</Description>
<ExportSWF>FALSE</ExportSWF>
<HasDefaultPublishDir>NO</HasDefaultPublishDir>
<Id>28577db1-956c-41f6-b775-a278c39e20a1</Id>
<IsAssociated>YES</IsAssociated>
<LogoURL>http://servername:8080/logos/9V0.png</LogoURL>
<Name>Portal1</Name>
<RequiredParameters>
<RequiredParameter>
<Id>user_name</Id>
<Name>UserID</Name>
<PlaceHolder>username</PlaceHolder>
<ShowAsDescription>true</ShowAsDescription>
</RequiredParameter>
</RequiredParameters>
I don't want the values in the child tags, there is time where there will be more than one portal thus the need/want to use a list. I only need the values inside of the Name and ID tags. also if there is a blank ID tag I don't want to store the either one of them.
My current approach to this is not working as expected:
String URLString = "http://servername:8080/roambi/SourceManager";
XmlTextReader reader = new XmlTextReader(URLString);
List<Portal> lPortals = new List<Portal>();
String sPortal = "";
String sId = "";
while (reader.Read())
{
//Get Portal ID
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Id")
{
reader.Read();
if (reader.NodeType == XmlNodeType.Text)
{
sId = reader.Value;
}
}
//Get Portal Name
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
{
reader.Read();
if (reader.NodeType == XmlNodeType.Text)
{
sPortal = reader.Value;
}
//Fill Portal List with Name and ID
if (sId != "" && sPortal != "")
{
lPortals.Add(new Portal
{
Portalname = sPortal,
Portalid = sId
});
}
}
}
foreach (Portal i in lPortals)
{
Console.WriteLine(i.Portalname + " " + i.Portalid);
}
See my standard class
class Portal
{
private String portalname;
private String portalid;
public String Portalname
{
get { return portalname; }
set { portalname = value; }
}
public String Portalid
{
get { return portalid; }
set { portalid = value; }
}
}
Please give me some advice and point me into a direction, As I said its been a while since I last programmed. My current Output is as follow:
Portal1 28577db1-956c-41f6-b775-a278c39e20a1
UserID user_name
UserID is in a child node and I do not want to display child nodes
It's much easier with XDocument class:
String URLString = "http://servername:8080/roambi/SourceManager";
XmlTextReader reader = new XmlTextReader(URLString);
XDocument doc = XDocument.Load(reader);
// assuming there's some root-node whose children are Source nodes
var portals = doc.Root
.Elements("Source")
.Select(source => new Portal
{
Portalname = (string) source.Element("Name"),
Portalid = (string) source.Element("Id")
})
.Where(p => p.Portalid != "")
.ToList();
For each <Source> node in your XML, code above will select direct children nodes (<Name> and <Id>) and build appropriate Portal instances.

XML values wont read to string

I am having difficulties with my XML code it doesn't seem to saving and when I print it out nothing happens. I am not sure what is wrong because before it would load into my listbox but it would load incorrectly. The code is below and the purpose of my XML reading is the store the values in a list and then get a selected tag and add it to a listbox.
String workingDir = Directory.GetCurrentDirectory();
XmlTextReader textReader = new XmlTextReader(workingDir + #"\XML.xml");
textReader.Read();
XmlNodeType type;
while (textReader.Read())
{
textReader.MoveToElement();
type = textReader.NodeType;
if (type == XmlNodeType.Text)
{
if (textReader.Name == "Code")
{
textReader.Read();
code = textReader.Value;
Console.WriteLine(code);
}
if (textReader.Name == "Name")
{
textReader.Read();
name = textReader.Value;
Console.WriteLine(name);
}
if (textReader.Name == "Semester")
{
textReader.Read();
semester = textReader.Value;
Console.WriteLine(semester);
}
if (textReader.Name == "Prerequisite")
{
textReader.Read();
preReq = textReader.Value;
Console.WriteLine(code);
}
if (textReader.Name == "LectureSlot")
{
textReader.Read();
lSlot = textReader.Value;
Console.WriteLine(lSlot);
}
if (textReader.Name == "TutorialSlot")
{
textReader.Read();
tSlot = textReader.Value;
Console.WriteLine(tSlot);
}
if (textReader.Name == "Info")
{
textReader.Read();
info = textReader.Value;
module.Add(new modules(name, code, semester, tSlot, lSlot, info, preReq));
}
}
foreach (object o in module)
{
modules m = (modules)o;
String hold = m.mName;
selectionBox.Items.Add(hold);
}
}
The thing is that you look for type == XmlNodeType.Text, but text nodes does not have any name, no text nodes will match textReader.Name == "Code".
You need to store textReader.Name from the last node with type == XmlNodeType.Element in a variable and use the stored name when you find the XmlNodeType.Text node.
I think the most likely reason is that in each of your if statements, you are using textReader.Read(). For most Readers this will read the next item, not the current.
As the other answer has said, you need to look at the element for the Name and then read for the value.
Consider something like this instead:
while (textReader.Read())
{
textReader.MoveToElement();
type = textReader.NodeType;
if (type == XmlNodeType.Element)
{
textReader.Read();
switch( textReader.Name )
{
case "Code":
code = textReader.Value;
break;
case "Name":
name = textReader.Value;
break;
//SNIP
case "Info":
info = textReader.Value;
module.Add(new modules(name, code, semester, tSlot, lSlot, info, preReq));
break;
default:
//Whatever you do here
break;
}
Console.WriteLine(textReader.Value);
}
foreach (object o in module)
{
modules m = (modules)o;
String hold = m.mName;
selectionBox.Items.Add(hold);
}
}
This way your XMLTextReader is only reading one node per iteration, and you have a lot fewer if checks - this is the situation a switch case was designed for.

readElements XML with XmlReader and Linq

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 : "";
}

Categories