I'm trying to make a loader from xml to create menu. I have been having problem with the button. It always gives an error of null pointer.
Here is the code:
title.xml
<?xml version="1.0" encoding="utf-8" ?>
<title>
<background>Assets/background</background>
<song>Assets/title</song>
<button>
<texture>Assets/background</texture>
<position>10,10</position>
<buttonaction>exit</buttonaction>
</button>
</title>
xmlManager
static public class xmlManager
{
static public titleData makeTitle(ContentManager content)
{
titleData title = new titleData();
System.IO.Stream stream = TitleContainer.OpenStream("Content/title.xml");
XDocument doc = XDocument.Load(stream);
var titleXML = doc.Descendants("title").First();
title.background = titleXML.Element("background").Value;
title.song = titleXML.Element("song").Value;
title.button = new List<Button>();
title.button = (from button in doc.Element("title").Elements("button")
select new Button()
{
texture = button.Element("texture").Value,
position = StringToVector(button.Element("Position").Value),
buttonAction = button.Element("buttonAction").Value
}).ToList();
return title;
}
static private Vector2 StringToVector(string str)
{
//convert a string to a point
Vector2 vector;
vector.X = Convert.ToInt32(str.Split(',')[0]);
vector.Y = Convert.ToInt32(str.Split(',')[1]);
return vector;
}
}
It always stops inside the xml manager at the select new button().
XML element names are case sensitive. You have buttonaction in your XML, but buttonAction in your C# code.
I would also recommend using string casts instead of .Value, as .Value will produce a NullReferenceException if an element is not found, and these can be hard to track down:
select new Button()
{
texture = (string)button.Element("texture"),
position = StringToVector((string)button.Element("position")),
buttonAction = (string)button.Element("buttonaction")
}
You would also need to modify your StringToVector() method to be able to handle null values. This will make your code more resilient against NullReferenceExceptions.
Related
If the data is on a single line the
index=int.Parse(logDataReader.ReadElementContentAsString());
and
value=double.Parse(logDataReader.ReadElementContentAsString(),
cause the cursor to move forward. If I take those calls out I see it loop 6 times in debug.
In the following only 3 <data> are read (and they are wrong as the value is for the next index) on the first (<logData id="Bravo">). On the second (<logData id="Bravo">) all <data> are read.
It is not an option to edit the xml and put in line breaks as that file is created dynamically (by XMLwriter). The NewLineChars setting is a line feed. From XMLwriter it is actually just one line - I broke it down to figure out where it was breaking. In the browser it is displayed properly.
How to fix this?
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<log>
<logData id="Alpha">
<data><index>100</index><value>150</value></data>
<data><index>110</index><value>750</value></data>
<data><index>120</index><value>750</value></data>
<data><index>130</index><value>150</value></data>
<data><index>140</index><value>0</value></data>
<data><index>150</index><value>222</value></data>
</logData>
<logData id="Bravo">
<data>
<index>100</index>
<value>25</value>
</data>
<data>
<index>110</index>
<value>11</value>
</data>
<data>
<index>120</index>
<value>1</value>
</data>
<data>
<index>130</index>
<value>25</value></data>
<data>
<index>140</index>
<value>0</value>
</data>
<data>
<index>150</index>
<value>1</value>
</data>
</logData>
</log>
And my code:
static void Main(string[] args)
{
List<LogData> logDatas = GetLogDatasFromFile("singleVersusMultLine.xml");
Debug.WriteLine("Main");
Debug.WriteLine("logData");
foreach (LogData logData in logDatas)
{
Debug.WriteLine($" logData.ID {logData.ID}");
foreach(LogPoint logPoint in logData.LogPoints)
{
Debug.WriteLine($" logData.Index {logPoint.Index} logData.Value {logPoint.Value}");
}
}
Debug.WriteLine("end");
}
public static List<LogData> GetLogDatasFromFile(string xmlFile)
{
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile))
{
// move to next "logData"
while (reader.ReadToFollowing("logData"))
{
var logData = new LogData(reader.GetAttribute("id"));
using (var logDataReader = reader.ReadSubtree())
{
// inside "logData" subtree, move to next "data"
while (logDataReader.ReadToFollowing("data"))
{
// move to index
logDataReader.ReadToFollowing("index");
// read index
var index = int.Parse(logDataReader.ReadElementContentAsString());
// move to value
logDataReader.ReadToFollowing("value");
// read value
var value = double.Parse(logDataReader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
logData.LogPoints.Add(new LogPoint(index, value));
}
}
logDatas.Add(logData);
}
}
return logDatas;
}
public class LogData
{
public string ID { get; }
public List<LogPoint> LogPoints { get; } = new List<LogPoint>();
public LogData (string id)
{
ID = id;
}
}
public class LogPoint
{
public int Index { get; }
public double Value { get; }
public LogPoint ( int index, double value)
{
Index = index;
Value = value;
}
}
Your problem is as follows. According to the documentation for XmlReader.ReadElementContentAsString():
This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
And from the documentation for XmlReader.ReadToFollowing(String):
It advances the reader to the next following element that matches the specified name and returns true if a matching element is found.
Thus, after the call to ReadElementContentAsString(), since the reader has been advanced to the next node, it might already be positioned on the next <value> or <data> node. Then when you call ReadToFollowing() this element node is skipped because the method unconditionally moves on to the next node with the correct name. But if the XML is indented then the next node immediately after the call to ReadElementContentAsString() will be an XmlNodeType.Whitespace node, protecting against this bug.
The solution is to check whether the reader is already positioned correctly after the call to ReadElementContentAsString(). First, introduce the following extension method:
public static class XmlReaderExtensions
{
public static bool ReadToFollowingOrCurrent(this XmlReader reader, string localName, string namespaceURI)
{
if (reader == null)
throw new ArgumentNullException(nameof(reader));
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == localName && reader.NamespaceURI == namespaceURI)
return true;
return reader.ReadToFollowing(localName, namespaceURI);
}
}
Then modify your code as follows:
public static List<LogData> GetLogDatasFromFile(string xmlFile)
{
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile))
{
// move to next "logData"
while (reader.ReadToFollowing("logData", ""))
{
var logData = new LogData(reader.GetAttribute("id"));
using (var logDataReader = reader.ReadSubtree())
{
// inside "logData" subtree, move to next "data"
while (logDataReader.ReadToFollowing("data", ""))
{
// move to index
logDataReader.ReadToFollowing("index", "");
// read index
var index = XmlConvert.ToInt32(logDataReader.ReadElementContentAsString());
// move to value
logDataReader.ReadToFollowingOrCurrent("value", "");
// read value
var value = XmlConvert.ToDouble(logDataReader.ReadElementContentAsString());
logData.LogPoints.Add(new LogPoint(index, value));
}
}
logDatas.Add(logData);
}
}
return logDatas;
}
Notes:
Always prefer to use XmlReader methods in which the local name and namespace are specified separately, such as XmlReader.ReadToFollowing (String, String). When you use a method such as XmlReader.ReadToFollowing(String) which accepts a single qualified name, you are implicitly hardcoding the choice of XML prefix, which is generally not a good idea. XML parsing should be independent of prefix choice.
While you correctly parsed your double using the CultureInfo.InvariantCulture locale, it's even easier to use the methods from the XmlConvert class to handle parsing and formatting correctly.
XmlReader.ReadSubtree() leaves the XmlReader positioned on the EndElement node of the element being read, so you shouldn't need to call ReadToFollowingOrCurrent() afterwards. (Nice use of ReadSubtree() to avoid reading too little or too much by the way; by using this method one can avoid several frequent mistakes with XmlReader.)
As you have found, code that manually reads XML using XmlReader should always be unit-tested with both formatted and unformatted XML, because certain bugs will only arise with one or the other. (See e.g. this answer, this one and this one also for other examples of such.)
Working sample .Net fiddle here.
Indeed that code (which I provided to you in your another question) is wrong. ReadToFollowing will read to the next element with this name even if it's cursor is already positioned on element with this name. When there is a whitespace - after you read index, cursor moves to that whitespace and ReadToFollowing("value") works as you expect. However, if there is no whitespace, cursor is already on value node and so ReadToFollowing("value") reads to the next "value" in subsequent "data" node.
I think the following would be a safer approach:
public static List<LogData> GetLogDatasFromFile(string xmlFile) {
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile)) {
LogData currentData = null;
while (reader.Read()) {
if (reader.IsStartElement("logData")) {
// we are positioned on start of logData
if (currentData != null)
logDatas.Add(currentData);
currentData = new LogData(reader.GetAttribute("id"));
}
else if (reader.IsStartElement("data")) {
// we are on start of "data"
// we always have "currentData" at this point
Debug.Assert(currentData != null);
reader.ReadToFollowing("index");
var index = int.Parse(reader.ReadElementContentAsString());
// check if we are not already on "value"
if (!reader.IsStartElement("value"))
reader.ReadToFollowing("value");
var value = double.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
currentData.LogPoints.Add(new LogPoint(index, value));
}
}
if (currentData != null)
logDatas.Add(currentData);
}
return logDatas;
}
I found a fix but to me not an acceptable answer. XMLreader should not behave differently with line breaks.
In XmlWriter this will put line breaks in the text:
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.NewLineOnAttributes = true;
xmlWriterSettings.Indent = true;
using (XmlWriter xmlWriter = XmlWriter.Create(fileNameXML, xmlWriterSettings))
{
I found this here.
I have elements stored in a config.xml file as part of my project, currently I have a method to 'setData' which will find the element by the id and then set its value to the user input (using a webdriver instance called FireFoxBrowser)
I want to add a type attribute to the xml to differentiate between 'inputs' which will use the current code and 'button' to add code that will click anything with this type. How can I use webdriver to write this code?
public void setData(string elementName, string elementValue)
{
XmlDocument docXml = null;
try
{
docXml = new XmlDocument();
string xmlPath = new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.FullName + #"\config.xml";
docXml.Load(xmlPath);
XmlNode nd = docXml.SelectSingleNode(string.Format(#"//page[#url='{0}']", FireFoxBrowser.Url.ToString()));
if (nd != null)
{
var id = nd.SelectSingleNode(string.Format(#"element[#name='{0}']", elementName)).Attributes["id"].Value;
FireFoxBrowser.FindElement(By.Id(id)).Clear();
FireFoxBrowser.FindElement(By.Id(id)).SendKeys(elementValue);
}
}
finally
{
if (docXml != null)
docXml = null;
}
I was able to achieve this using the following line of code which differentiates between type attribute set:
var id = nd.SelectSingleNode(string.Format(#"element[#name='{0}']", elementName)).Attributes["id"].Value;
I have an issue with databinding a XML document to my WP8 app. The databinding part works (I think) but only displays the top element.
XML:
<Application>
<AppID>1</AppID>
<AppID>2</AppID>
<AppID>3</AppID>
<AppID>4</AppID>
</Application>
My current c# Code:
private void AddFromXML()
{
XDocument xdoc = XDocument.Load("Resources/20Sept.xml");
var data = from query in xdoc.Descendants("Application")
select new AppToDownload
{
AppID = query.Element("AppID").Value
};
applist.itemssource = data;
}
Where am I going wrong?
Do this way:-
var data = from query in xdoc.Descendants("AppID")
select new AppToDownload
{
AppID = query.Value
};
Ok, this may be impossible but I want to use a string taken as an argument to a function and use that string to name a new instance of a class. Using visual studios 2010 with C# XNA, I tried searching for solutions using google and here but maybe I'm not using the right keywords or something.
public void createRace(string raceName, Element element1, Element element2, AbilityScore stat1, AbilityScore stat2, AbilityScore stat3, AbilityScore stat4)
{
Race temp = new Race();
temp.raceName = raceName;
temp.primaryElement = element1;
temp.secondaryElement = element2;
temp.primaryAbility = stat1;
temp.secondaryAbility1 = stat2;
temp.secondaryAbility2 = stat3;
temp.weakAbility = stat4;
}
I want Race temp to use raceName instead of temp when naming the new instance of Race, if it's not possible let me know and I'll find a workaround.
I would use a dictionary to solve this. The following code will allow you "raceName" based access to your instances
Dictionary<string,Race> races = new Dictionary<string,Race>();
public void createRace(string raceName, Element element1, Element element2, AbilityScore stat1, AbilityScore stat2, AbilityScore stat3, AbilityScore stat4)
{
Race temp = new Race();
temp.raceName = raceName;
temp.primaryElement = element1;
temp.secondaryElement = element2;
temp.primaryAbility = stat1;
temp.secondaryAbility1 = stat2;
temp.secondaryAbility2 = stat3;
temp.weakAbility = stat4;
races.Add(raceName,temp);
}
update
I'm writing a silverlight application and I have the following Class "Home", in this class a read a .xml file a write these to a ListBox. In a other class Overview I will show the same .xml file. I know it is stupid to write the same code as in the class "Home".
The problem is, how to reach these data.
My question is how can I reuse the method LoadXMLFile() from another class?
The code.
// Read the .xml file in the class "Home"
public void LoadXMLFile()
{
WebClient xmlClient = new WebClient();
xmlClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(XMLFileLoaded);
xmlClient.DownloadStringAsync(new Uri("codeFragments.xml", UriKind.RelativeOrAbsolute));
}
private void XMLFileLoaded(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
string xmlData = e.Result;
XDocument xDoc = XDocument.Parse(xmlData);
var tagsXml = from c in xDoc.Descendants("Tag") select c.Attribute("name");
List<Tag> lsTags = new List<Tag>();
foreach (string tagName in tagsXml)
{
Tag oTag = new Tag();
oTag.name = tagName;
var tags = from d in xDoc.Descendants("Tag")
where d.Attribute("name").Value == tagName
select d.Elements("oFragments");
var tagXml = tags.ToArray()[0];
foreach (var tag in tagXml)
{
CodeFragments oFragments = new CodeFragments();
oFragments.tagURL = tag.Attribute("tagURL").Value;
//Tags.tags.Add(oFragments);
oTag.lsTags.Add(oFragments);
}
lsTags.Add(oTag);
}
//List<string> test = new List<string> { "a","b","c" };
lsBox.ItemsSource = lsTags;
}
}
Create a class to read the XML file, make references to this from your other classes in order to use it. Say you call it XmlFileLoader, you would use it like this in the other classes:
var xfl = new XmlFileLoader();
var data = xfl.LoadXMLFile();
If I were you, I would make the LoadXMLFile function take a Uri parameter to make it more reusable:
var data = xfl.LoadXMLFile(uriToDownload);
You could create a class whose single responsibility is loading XML and returning it, leaving the class that calls your LoadXmlFile method to determine how to handle the resulting XML.