Problem with reading from an XML with attributes in C#

烂漫一生 提交于 2020-01-25 11:41:18

问题


I'm making an application which saves and loads data from an XML file.

Here is my xml:

<?xml version="1.0" encoding="utf-8" ?> 
<storage>   
<Save Name ="Lifeline">
 <Seconds>12</Seconds>
 <Minutes>24</Minutes>
 <Hours>9</Hours>
 <Days>25</Days>
 <Months>8</Months>
 <Years>2010</Years>
 <Health>90</Health>
 <Mood>100</Mood>  
</Save> 

<Save Name ="Hellcode">   
 <Seconds>24</Seconds>
 <Minutes>48</Minutes>
 <Hours>18</Hours>
 <Days>15</Days>
 <Months>4</Months>
 <Years>1995</Years>
 <Health>50</Health>
 <Mood>50</Mood>  
</Save> 
</storage>

The thing is that I whant to specify the "save" by loading "name" from a listbox in such a way

       System.IO.StreamReader sr = new System.IO.StreamReader(@"Saves.xml");

       System.Xml.XmlTextReader xr = new System.Xml.XmlTextReader(sr);

       System.Xml.XmlDocument save = new System.Xml.XmlDocument();

        save.Load(xr);

       string name = lstSave.SelectedItem.ToString();

       XmlNodeList saveItems = save.SelectNodes("Storage/Save[@Name = name]");

       XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");
       sec = Int32.Parse(seconds.InnerText);

       XmlNode minutes = saveItems.Item(0).SelectSingleNode("Minutes");
       min = Int32.Parse(minutes.InnerText);

       XmlNode hours = saveItems.Item(0).SelectSingleNode("Hours");
       hour = Int32.Parse(hours.InnerText);

       XmlNode days = saveItems.Item(0).SelectSingleNode("Days");
       day = Int32.Parse(days.InnerText);

       XmlNode months = saveItems.Item(0).SelectSingleNode("Months");
       month = Int32.Parse(months.InnerText);

        XmlNode years = saveItems.Item(0).SelectSingleNode("Years");
        year = Int32.Parse(years.InnerText);

       XmlNode health_ = saveItems.Item(0).SelectSingleNode("Health");
       health = Int32.Parse(health_.InnerText);

       XmlNode mood_ = saveItems.Item(0).SelectSingleNode("Mood");
       mood = Int32.Parse(mood_.InnerText);

When I try to run the application the compiler gives NullReferenceException was unhandled "Object reference not set to an instance of an object" on

XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");

So my question is what's wrong and what should I do?

Edit: I've even tried this

foreach (XmlNode xn in saveItems)
{
 sec = Int32.Parse(xn["Seconds"].InnerText);
 min = Int32.Parse(xn["Minutes"].InnerText);
 hour = Int32.Parse(xn["Hours"].InnerText);
 day = Int32.Parse(xn["Days"].InnerText);
 month = Int32.Parse(xn["Months"].InnerText);
 year = Int32.Parse(xn["Years"].InnerText);
 health = Int32.Parse(xn["Health"].InnerText);
 mood = Int32.Parse(xn["Mood"].InnerText);
}

but nothing loads at all

===================================================================

just to get this quetion easier to understand. Here is the code which works and loads all needed data for application, BUT it loads only from "Lifeline" node. While compiling, there are no exception and all works pretty fine.

System.IO.StreamReader sr = new System.IO.StreamReader(@"Saves.xml");

System.Xml.XmlTextReader xr = new System.Xml.XmlTextReader(sr);

System.Xml.XmlDocument save = new System.Xml.XmlDocument();

save.Load(xr);



XmlNodeList saveItems = save.SelectNodes("Storage/Save");

XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");
sec = Int32.Parse(seconds.InnerText);

XmlNode minutes = saveItems.Item(0).SelectSingleNode("Minutes");
min = Int32.Parse(minutes.InnerText);

XmlNode hours = saveItems.Item(0).SelectSingleNode("Hours");
hour = Int32.Parse(hours.InnerText);

XmlNode days = saveItems.Item(0).SelectSingleNode("Days");
day = Int32.Parse(days.InnerText);

XmlNode months = saveItems.Item(0).SelectSingleNode("Months");
month = Int32.Parse(months.InnerText);

XmlNode years = saveItems.Item(0).SelectSingleNode("Years");
year = Int32.Parse(years.InnerText);

XmlNode health_ = saveItems.Item(0).SelectSingleNode("Health");
health = Int32.Parse(health_.InnerText);

XmlNode mood_ = saveItems.Item(0).SelectSingleNode("Mood");
mood = Int32.Parse(mood_.InnerText);

The problem is that I want to have an ability to choose nodes by "Name" attribute, and I don't know hot to do it using the listbox. Those "Lifeline" and "Hellcode" are like account names, and the user should choose which account data to load.


回答1:


To answer your followup on your other duplicate question:

I've tried to take string from listbox item content and then use such a line

XmlNodeList saveItems = save.SelectNodes(string.Format("storage/Save[@Name = '{0}']", name));

variable "name" is a string from listboxe's item. While compiled this code gives exception. Do somebody knows a way how to select by attribute and load nedeed data from that XML?

I suspect that you're not getting the right values off of your ListBox. It all depends on how you populated it. If you just used the designer to fill your ListBox with your string names, you should be using the SelectedItem property to get the selected name. If on the other hand you populated your ListBox setting the DataSource, set the ValueMember property to the appropriate property name and use SelectedValue to get the value.

Though since you never mentioned what the exception was, this is all just an educated guess.


Your problem initially was that you were using the wrong XPath. Since it was wrong, all following accesses to the first result returned null yielding a NullReferenceException. BrokenGlass' answer covers the using the correct XPath.

If the XML you are showing us is indeed what is in the contents of the file and you are getting an appropriate name value, then everything should work here.

Overall, the code could be simplified much more. I use some LINQ here just to make dealing with invalid names easier. You should find that this is working code.

var xmlStr = @"<?xml version=""1.0"" encoding=""utf-8"" ?> 
<storage>   
  <Save Name =""Lifeline"">
    <!-- etc... (trimmed off for brevity) -->
  </Save> 
  <Save Name =""Hellcode"">
    <!-- etc... -->
  </Save> 
</storage>
";
var doc = new XmlDocument();
doc.LoadXml(xmlStr);
var name = "Hellcode";
var settings =
    doc.SelectNodes(String.Format("/storage/Save[@Name='{0}']", name))
       .Cast<XmlElement>()
       .Select(e => new
       {
           Seconds = Convert.ToInt32(e["Seconds"].InnerText),
           Minutes = Convert.ToInt32(e["Minutes"].InnerText),
           Hours   = Convert.ToInt32(e["Hours"].InnerText),
           Days    = Convert.ToInt32(e["Days"].InnerText),
           Months  = Convert.ToInt32(e["Months"].InnerText),
           Years   = Convert.ToInt32(e["Years"].InnerText),
           Health  = Convert.ToInt32(e["Health"].InnerText),
           Mood    = Convert.ToInt32(e["Mood"].InnerText),
       })
       .SingleOrDefault();
Trace.WriteLine("settings: " + settings);

I would prefer LINQ to XML here as it is much more cleaner. This should also work with the xmlStr provided above.

var doc = XDocument.Parse(xmlStr);
var name = "Hellcode";
var settings =
    doc.Element("storage")
       .Elements("Save")
       .Where(e => e.Attribute("Name").Value == name)
       // or if using XPath, the above could be replaced with:
//  doc.XPathSelectElements(String.Format("/storage/Save[@Name='{0}']", name))
       .Select(e => new
       {
           Seconds = (int)e.Element("Seconds"),
           Minutes = (int)e.Element("Minutes"),
           Hours   = (int)e.Element("Hours"),
           Days    = (int)e.Element("Days"),
           Months  = (int)e.Element("Months"),
           Years   = (int)e.Element("Years"),
           Health  = (int)e.Element("Health"),
           Mood    = (int)e.Element("Mood"),
       })
       .SingleOrDefault();
Trace.WriteLine("settings: " + settings);



回答2:


Your XPath query is off - you are currently using name as a literal and not its value, also you need single quotes around it - so replace this

XmlNodeList saveItems = save.SelectNodes("Storage/Save[@Name = name]");

with:

XmlNodeList saveItems = save.SelectNodes(string.Format("storage/Save[@Name = '{0}']", name));

Edit: also needed lowercase storage in Xpath query - fixed

This sample code works for me - this should help you find where your other problems are:

System.IO.StreamReader sr = new System.IO.StreamReader(@"test.xml");
System.Xml.XmlTextReader xr = new System.Xml.XmlTextReader(sr);
System.Xml.XmlDocument save = new System.Xml.XmlDocument();
save.Load(xr);
string name = "Hellcode";
XmlNodeList saveItems = save.SelectNodes(string.Format("storage/Save[@Name = '{0}']", name));

XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");
int sec = Int32.Parse(seconds.InnerText);

Also I would suggest you replace your SelectNodes() with SelectSingleNode():

XmlNode saveItem = save.SelectSingleNode(string.Format("storage/Save[@Name = '{0}']", name));
XmlNode seconds = saveItem.SelectSingleNode("Seconds");

Edit:

As suggested an alternative fro XML parsing is Linq to XML - this is pretty much the standard now, don't use anything else if you don't have to. This makes this sample much shorter:

XDocument doc = XDocument.Load("test.xml");
var saveItem = doc.Descendants("Save")
                  .Where(x => (string)x.Attribute("Name") == name)
                  .Single();
int sec = Convert.ToInt32(saveItem.Element("Seconds").Value);



回答3:


  1. make sure you are giving the right xml file name,
  2. make sure your xml file is not empty
  3. and try this XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds").Value;
  4. check here
  5. and check here


来源:https://stackoverflow.com/questions/5606075/problem-with-reading-from-an-xml-with-attributes-in-c-sharp

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!