Linq XML NullReference Exception on reading list of elements - c#

I'm trying to read a list of Text nodes from my Xml file. When I try to read the children nodes of 1 of the listed text elements I get the NullReference exception, even though the XDocument nodes are seemingly filled.
The NullReference exception is thrown on the line foreach (XElement textElement in textElements) this means that the list textElements has a Count > 1 so it shouldn't be null. That's as far as the debugger is willing to go.
Am I reading the Xml in the wrong way, or am I not allowed to / shouldn't build the XElement lists the way I am now?
This is my Xml file
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Book SYSTEM "Book.dtd"[]>
<album version="0.2" settingsVersion="1">
<size />
<cover />
<inner>
<page>
<pagenumber>1</pagenumber>
<image>1.jpg</image>
<text>
<value>Test </value>
<font>arial</font>
<size>18pt</size>
<style>normal</style>
<weight>normal</weight>
<color>#77DD44</color>
<rotation>0</rotation>
<alignment>start</alignment>
<position>
<x>50</x>
<y>50</y>
</position>
</text>
<text>
<value>Test 2 </value>
<font>arial</font>
<size>18pt</size>
<style>normal</style>
<weight>normal</weight>
<color>#77DD44</color>
<rotation>0</rotation>
<alignment>start</alignment>
<position>
<x>50</x>
<y>50</y>
</position>
</text>
</page>
</inner>
</album>
Element inner can hold multiple page elements and page can hold multiple text elements.
I read the Xml in the following method.
List<XElement> pageElements = doc.Root.Element("inner").Elements("page").ToList();
foreach (XElement pageElement in pageElements)
{
string pageNumberString = pageElement.Element("pagenumber").Value;
int pageNumberValue = Convert.ToInt32(pageNumberString);
string fileNameValue = pageElement.Element("image").Value;
// Verify if the currently looped page is the same as the one selected by the user.
if (pageNumberValue == pageNumber)
{
// Get all text nodes from the found page.
List<XElement> textElements = pageElement.Elements("text").ToList();
// If no text nodes found return the page with an empty TextInfo array.
if (textElements.Count == 0)
{
PageInfo pageInfoNoText = new PageInfo { PageNumber = pageNumberValue, FileName = fileNameValue, Text = new TextInfo[0] };
Logger.log("PageInfo found for collection {0}. Info {1}", collectionId, pageInfoNoText);
return pageInfoNoText;
}
// If text nodes are found build a list of TextInfo objects and build a new PageInfo object.
else
{
// All text elements in the XML under the found page.
List<TextInfo> textInfoList = new List<TextInfo>();
TextInfo[] textArray = new TextInfo[0];
#region Load all required text data from the XML file and build the textList.
foreach (XElement textElement in textElements)
{
string textValue = textElement.Element("value").Value;
string fontValue = textElement.Element("font").Value;
string fontSizeValue = textElement.Element("size").Value;
string styleValue = textElement.Element("style").Value;
string weightValue = textElement.Element("weight").Value;
string colorValue = textElement.Element("color").Value;
string rotationString = textElement.Element("rotation").Value;
int rotationValue = Convert.ToInt32(rotationString);
string alignmentValue = textElement.Element("alignment").Value;
string positionXString = textElement.Element("x").Value;
int positionXValue = Convert.ToInt32(positionXString);
string positionYString = textElement.Element("y").Value;
int positionYValue = Convert.ToInt32(positionYString);
// Build Info objects.
PositionInfo tempPositionInfo = new PositionInfo
{
X = positionXValue,
Y = positionYValue
};
TextInfo tempTextInfo = new TextInfo
{
Value = textValue,
Font = fontValue,
Size = fontSizeValue,
Style = styleValue,
Weight = weightValue,
Color = colorValue,
Rotation = rotationValue,
Alignment = alignmentValue,
Position = tempPositionInfo
};
textInfoList.Add(tempTextInfo);
}
textArray = textInfoList.ToArray();
#endregion
PageInfo pageInfo = new PageInfo
{
PageNumber = pageNumberValue,
FileName = fileNameValue,
Text = textArray
};
Logger.log("PageInfo found for collection {0}. Info: {1}", collectionId, pageInfo);
return pageInfo;
}
}
}
Any help / insight is appreciated!

your code is fine but there is a mistake when you get x and y values, you need to get x and y values of position element as below
string positionXString = (string)textElement.Element("position").Element("x").Value;
int positionXValue = Convert.ToInt32(positionXString);
string positionYString = (string)textElement.Element("position").Element("y").Value;
int positionYValue = Convert.ToInt32(positionYString);

your text element does not contain an element called x, so trying to access the value of x results in a NullReferenceException:
string positionXString = textElement.Element("x").Value;
// textElement.Element("x") is null, "x" is in textElement.Element("position")
int positionXValue = Convert.ToInt32(positionXString);
You might be better using Descendants (Descendants("x").FirstOrDefault()?.Value) instead of Element for this, but overall you may want to restructure this altogether.

Related

Repeat loop does not iterate over all elements

I'm using a loop of repetition in a collection of nodes, however the output and always the first input of the foreach ...
var all_accounts_elements = content.GetCollectionNode(
"//div[#class='box-list-item text-c js-list-item']");
foreach (HtmlNode account_element in all_accounts_elements)
{
//for debug only
var copy = account_element;
var account = new InstagramAccount();
//for debug only
var debug = copy.SelectSingleNode("//*[#class='title']");
Debug.WriteLine(debug.InnerText);
account.AccountName = account_element.SelectSingleNode("//*[#class='title']")
.InnerText.ToTilteCase().Trim();
account.AddedData = account_element.SelectSingleNode("//*[#class='sub']")
.InnerText.Trim();
account.Edit = account_element.SelectSingleNode("//*[#class='context-menu']")
.InnerText.Trim();
account.Message = account_element.SelectSingleNode("//*[#class='quick-info']")
.InnerText.Trim();
accountsList.Add(account);
}
Colection:
1st element:
2st element:
Output:
jonisbarcelos
jonisbarcelos
Your XPath selector is telling the HTMLAgilityPack to look into the entire document. Try looking at the descendants instead:
account.AccountName = account_element.SelectSingleNode("descendant::div[#class='title']")
.InnerText.ToTilteCase().Trim();
account.AddedData = account_element.SelectSingleNode("descendant::div[#class='sub']")
.InnerText.Trim();
account.Edit = account_element.SelectSingleNode("descendant::div[#class='context-menu']")
.InnerText.Trim();
account.Message = account_element.SelectSingleNode("descendant::div[#class='quick-info']")
.InnerText.Trim();

C# (Xamarin): looping through XML

I have a Xamarin (C#) project, where I am trying to loop through some XML, but for some reason my code is not working.
This is what I have now:
DeviceList = new List<DeviceInfo>();
string ResultStatus = "";
string ResultDevice = "";
var result = Encoding.Default.GetString(e.Result);
result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + result;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(result);
string xPathStatus = "ed_listdevices";
var nodes = xmlDoc.SelectNodes(xPathStatus);
foreach (XmlNode xNode in nodes) {
ResultStatus = xNode.SelectSingleNode("//status").InnerText;
ResultDevice = xNode.SelectSingleNode("//device").InnerText;
}
if (ResultStatus.ToLower() == "ok") {
XmlDocument deviceDoc = new XmlDocument();
deviceDoc.LoadXml(result);
var deviceNodes = deviceDoc.SelectNodes(xPathStatus + "/device");
//foreach(XmlNode dNode in deviceNodes) {
for (int i = 0; i < deviceNodes.Count; i++) {
DeviceList.Add(new DeviceInfo() {
DeviceID = deviceNodes[i].SelectSingleNode("//id").InnerXml,
DeviceName = deviceNodes[i].SelectSingleNode("//name").InnerXml,
DeviceExtraName = "",
DeviceOnlineStatus = deviceNodes[i].SelectSingleNode("//status").InnerXml,
Location = deviceNodes[i].SelectSingleNode("//address").InnerXml,
Time = deviceNodes[i].SelectSingleNode("//time").InnerXml
});
}
}
When I step though the code I get the "ResultStatus" and "ResultDevice" correctly, and when I get to the
for (int i = 0; i < deviceNodes.Count; i++)
the "deviceNodes" variable have a count of 91, and I can see all the individual xml elements that I am suppose to get from the webservice I am calling.
However, when I loop through deviceNodes[i] I only get values from the very first XML element (yes, the value of "i" does change). In other words, my DeviceList is filled with 91 entries with the same values.
Am I missing something obvious?? What am I doing wrong since this isn't working??
PS: I have also tried using a foreach (XmlNode node in deviceNodes) but the result was the same.
"//" in the selector tells it to search from the root node of the document. If you want to search locally under the "current" node, remove the "//"

XmlDocument sort nodes by the inner text value of a child

i want to sort the nodes called ImageInfo by the number in the pos node because i have buttons that change the position up or down and i need to sort the ImageInfo node in the correct order when the pos has changed.
i apologise ahead for not having any c# code but i assure you that i have tried so many different things and im in need of help.
here is my xml:
<?xml version="1.0" encoding="utf-8"?>
<MplAndSiImages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MplImages>
<ImageInfo>
<pos>1</pos>
<Name>1.png</Name>
<ParentObjectId>b66a23a8-6268-e611-80e2-c4346bad02e8</ParentObjectId>
<Url>http://localhost:8080/b66a23a8-6268-e611-80e2-c4346bad02e8/1.png</Url>
</ImageInfo>
<ImageInfo>
<pos>2</pos>
<Name>2.png</Name>
<ParentObjectId>b66a23a8-6268-e611-80e2-c4346bad02e8</ParentObjectId>
<Url>http://localhost:8080/b66a23a8-6268-e611-80e2-c4346bad02e8/2.png</Url>
</ImageInfo>
<ImageInfo>
<pos>3</pos>
<Name>3.png</Name>
<ParentObjectId>b66a23a8-6268-e611-80e2-c4346bad02e8</ParentObjectId>
<Url>http://localhost:8080/b66a23a8-6268-e611-80e2-c4346bad02e8/3.png</Url>
</ImageInfo>
</MplImages>
<SiImages />
</MplAndSiImages>
here is my c# code:
it is called on the click of an action link button and i need it to change the poition to 1 less to move it up in the list and i have the number change but the xml need s to be sorted so it has the ImageInfo nodes in the correct order.
public ActionResult MoveUp(string name, string id)
{
var pathConfig = WebConfigurationManager.AppSettings["ProductImageFolderPath"];
var url = pathConfig + id + "\\" + "ModelConfig.xml";
XmlDocument doc = new XmlDocument();
doc.Load(url);
XmlNode root = doc.DocumentElement;
XmlNode upNode = root.SelectSingleNode("/MplAndSiImages/MplImages/ImageInfo[Name/text() = '" + name + "']/pos");
string upNodeValue = upNode.InnerText;
int upNodeInt = Int32.Parse(upNodeValue);
upNodeInt = upNodeInt - 1;
var upNodeString = upNodeInt.ToString();
upNode.InnerText = upNodeString;
XmlNode downNode = root.SelectSingleNode("/MplAndSiImages/MplImages/ImageInfo/pos[text() = '" + upNodeString + "']");
string downNodeValue = downNode.InnerText;
int downNodeInt = Int32.Parse(downNodeValue);
downNodeInt = downNodeInt + 1;
var downNodeString = downNodeInt.ToString();
downNode.InnerText = downNodeString;
Func<string, int> ParseIntOrDefault = (string input) =>
{
int output;
int.TryParse(input, out output);
return output;
};
var result = doc.SelectNodes("MplAndSiImages/MplImages/*")
.Cast<XmlNode>()
.OrderBy(element => element.SelectSingleNode("pos").InnerText)
.ToList();
doc.Save(url);
return RedirectToAction("UploadAnImage", new { id = id });
}
I have seen this and tried it but is there any way of doing this with xmldocument:
XElement root = XElement.Load(xmlfile);
var orderedtabs = root.Elements("Tab")
.OrderBy(xtab => (int)xtab.Element("Order"))
.ToArray();
root.RemoveAll();
foreach(XElement tab in orderedtabs)
root.Add(tab);
root.Save(xmlfile);
I am ordering the images to display on a web page.
and when the move up button is pressed the image will be moved up in the list and swap places with the image above it.
Using linq to xml you can:
var result = XDocument.Load("data.xml")
.Descendants("ImageInfo")
.OrderBy(element => element.Element("pos")?.Value)
.ToList();
And in order to order it by the int value of it you can:
Func<string,int> ParseIntOrDefault = (string input) =>
{
int output;
int.TryParse(input, out output);
return output;
};
var result = XDocument.Load("data.xml")
.Descendants("ImageInfo")
.OrderBy(element => ParseIntOrDefault(element.Element("pos")?.Value))
.ToList();
Using XmlDocument to read the xml you can:
var doc = new XmlDocument();
doc.Load("data.xml");
var result = doc.SelectNodes("MplAndSiImages/MplImages/*")
.Cast<XmlNode>()
.OrderBy(element => element.SelectSingleNode("pos").InnerText)
.ToList();
Here too you can use the ParseIntOrDefault from above

Read XML file from URL, comes up empty

I am trying to read a XML file from a URL.
The URL and file are fine, they hold currency rates.
When running the code 9 out of 10 times, no content comes back.
Here is the code:
XDocument doc = XDocument.Load("http://www.boi.org.il/currency.xml");
int currID = 0;
Dictionary<int, Currency> curr; // declares the dictionary
curr = new Dictionary<int, Currency>();
var data = from item in doc.Descendants("CURRENCY") // LINQ the informartion from the xml to data variable
select new
{
name = item.Element("NAME").Value,
country = item.Element("COUNTRY").Value,
currencyCode = item.Element("CURRENCYCODE").Value,
rate = Convert.ToDouble(item.Element("RATE").Value),
unit = Convert.ToDouble(item.Element("UNIT").Value),
change = Convert.ToDouble(item.Element("CHANGE").Value),
};
foreach (var xn in data) // run in foreach on the data that we read from the xml and put it in a currency variable into the dictionary
{
Currency currency = new Currency();
currency.Name = xn.name;
currency.Country = xn.country;
currency.CurrencyCode = xn.currencyCode;
currency.Rate = Convert.ToDouble(xn.rate);
currency.Unit = Convert.ToDouble(xn.unit);
currency.Change = Convert.ToDouble(xn.change);
curr.Add(currID, currency);
currID++;
}
foreach (KeyValuePair<int, Currency> entry in curr)
{
Console.WriteLine(entry.Value.CurrencyCode);
}
I have edited the code to see the output, I get nothing.
What am I doing wrong?
Thanks in advance.
#David Faiz It Works!
XmlDocument xDoc = new XmlDocument();
xDoc.Load(#"http://www.boi.org.il//currency.xml");
XmlNodeList xmllist = xDoc.GetElementsByTagName("CURRENCIES");
Console.WriteLine(xmllist.Count);
You have must add // slashes in the URL. That is why u got the 'xmllist.Count' as Zero.
Here's a quick refactor of your code..
XDocument doc = XDocument.Load(#"http://www.boi.org.il/currency.xml");
foreach (XElement elm in doc.Elements)
{
Currency currency = new Currency();
currency.Name = elm.Element("NAME").Value;
currency.Country = elm.Element("COUNTRY").Value;
currency.CurrencyCode = elm.Element("CURRENCYCODE").Value;
currency.Rate = Convert.ToDouble(elm.Element("RATE").Value);
currency.Unit = Convert.ToDouble(elm.Element("UNIT").Value);
currency.Change = Convert.ToDouble(elm.Element("CHANGE").Value);
MessageBox.Show(elm.Element("CURRENCYCODE").Value);
curr.Add(currID, currency);
currID++;
}
However, I'm not sure this addresses the underlying issue you're having..
You could include the System.Net namespace and initialize a XMLHttpRequest object and use the Response stream with the static XDocument.Load() method..

Using C# XmlDocument, Iterating into the tags of the Complex Xml, and displaying a Value inside inner tag

I am new to Xml, I have to Iterate into the tags(Loop into all Rack, all Servers, all Model Details, all Power supplies and display the sum of all quantity of all the Power supplies through Pop up message)
I am able to Load the Xml in C# like this:-
XmlDocument xworkload = new XmlDocument();
XmlDocument doc = new XmlDocument();
private void button1_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".xml"; // Default file extension
dlg.Filter = "Xml documents (.xml)|*.xml"; // Filter files by extension
var result = dlg.ShowDialog();
if (result == true)
{
xworkload.Load(dlg.FileName);
string xmlcontents = xworkload.InnerXml; // to get xml string, This is working
XmlNode xnd =xworkload.DocumentElement.SelectSingleNode("PowerSupply/quantity");
foreach(XmlNode _node in xnd)
{
String _nodeValue =_node.InnerText.ToString(); //This is not working
MessageBox.Show(_nodeValue.ToString());
}
}
}
How to display the Final sum of Power supply Quantity?
Here is correct XPath you should use:
XmlNode xnd = xworkload.SelectSingleNode("//PowerSupply/Item/quantity");
You had two problems:
quantity is not direct child of PowerSupply element - there is Item between them.
Also PowerSupply is not direct child of root element, so you need to use // to search anywhere in document.
Consider also using Linq to Xml for parsing xml. You still able to use XPath:
var xdoc = XDocument.Load(dlg.FileName);
int quantity = (int)xdoc.XPathSelectElement("//PowerSupply/Item/quantity");
That's it - you have integer quantity value in two lines.
EDIT: If you have many PowerSupply elements and you want to calculate sum of their quantities
int sum = xdoc.XPathSelectElements("//PowerSupply/Item/quantity")
.Sum(q => (int)q);
Or with your original approach:
XmlNodeList quantities = xworkload.SelectNodes("//PowerSupply/Item/quantity");
int sum = 0;
foreach (XmlNode quantity in quantities)
sum += Int32.Parse(quantity.InnerText);
You should be using .SelectNodes and an XmlNodeList. Also, the .DocumentElement part is extraneous and unnecessary, and there was an issue with your XPath.
There's also no need to call .ToString() on a string and then call it again on that string:
XmlNodeList xnd = xworkload.SelectNodes("//PowerSupply/Item/quantity");
foreach(XmlNode _node in xnd)
{
string _nodeValue =_node.InnerText;
MessageBox.Show(_nodeValue);
}
You could get the total quantity of power supplies like this:
XmlNodeList xnd = xworkload.SelectNodes("//PowerSupply/Item/quantity");
int powerSupplyCount = 0;
foreach(XmlNode _node in xnd)
{
int count;
if(int.TryParse(_node.InnerText, out count))
{
powerSupplyCount += count;
}
}
MessageBox.Show(powerSupplyCount.ToString());

Categories

Resources