I am trying to make an XML parser for XML documents, where there are some optional attributes in nodes and I am looking for an elegant way, how to solve the problem with "Object reference not set to an instance".
I have read this topic, which is very similar and the following code seemed very promising:
string text = (string) foo.Element("Text") ?? "Default value";
However, when I tried to implement it, the "Object reference not set to an instance" still occurred and so I am looking for another solution. Here is a piece of my desperate effort.
XML file 1:
...
<Message id ="1" appendix = "abc" ></Message>
...
XML file 2:
...
<Message id ="2" ></Message>
...
My parser (called in a cycle for each file in the folder):
public MyNode Parse(string file)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(file);
MyNode node = new MyNode();
node.messageID = (string)xDoc.GetElementsByTagName("Message")[0].Attributes["id"].Value ?? "NULL";
node.appendix = (string)xDoc.GetElementsByTagName("Message")[0].Attributes["appendix"].Value ?? "NULL";
return node;
}
Could you help me out?
Following LINQ to XML query will return MyNode object filled with values of message id and appendix attributes. If some attribute not found, then defalult value "NULL" is used:
var xDoc = XDocument.Load(file);
var node = xDoc.Descendants("Message")
.Select(m => new MyNode {
messageID = (string)m.Attribute("id") ?? "NULL",
appendix = (string)m.Attribute("appendix") ?? "NULL"
}).FirstOrDefault();
If there is no Message elements in your xml document, then null will be returned.
BTW when you are using LINQ to XML its better to use casting node to (string) than accessing its Value property. Because if node not found in document, then you'll get NullReferenceException if you will try to get Value property of null. But when you cast node to string, you simply get null value instead of exception. That allows you to provide default value with null-coalescing operator.
Unfortunately this only works if the Value is null, not if the actual XAttribute is null.
You'll have to do something like this:
XAttribute temp = xDoc.GetElementsByTagName("Message")[0].Attributes["appendix"];
if (temp == null)
{
node.appendix = temp.Value;
}
else
{
node.appendix = "NULL";
}
Related
In C#, I am trying to change an option of a feature in an XML file which presents a Print Ticket and loaded as an XElement by the following code:
XElement ticketRootXElement = null;
using (Stream ticketReadStream = displayedPrintTicket.GetReadStream())
{
ticketRootXElement = XElement.Load(ticketReadStream);
}
The partial XML is something like following:
<?xml version="1.0"?>
<psf:Feature xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" name="psk:PageMediaSize">
<psf:Option name="psk:ISOA4">
<psf:ScoredProperty name="psk:MediaSizeWidth">
<psf:Value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:integer">210000</psf:Value>
</psf:ScoredProperty>
<psf:ScoredProperty name="psk:MediaSizeHeight">
<psf:Value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:integer">297000</psf:Value>
</psf:ScoredProperty>
</psf:Option>
</psf:Feature>
How can I access the "Option" of a specific "Feature" and change it to something like <psf:Option name="psk:ISOA3">?
I tried the following code, but it fails.
foreach (XAttribute xAttr in ticketRootXElement.Descendants(xns_psf + "Feature").Attributes())
{
if (xAttr.Value.Equals("psk:PageMediaSize"))
{
foreach(XAttribute xSubAttr in ticketRootXElement.Element("PageMediaSize").Descendants(xns_psf + "Option").Attributes())
{
if (xAttr.NextAttribute.Name.LocalName.Equals("name"))
{
xAttr.NextAttribute.SetValue("psk:ISO" + cmb_PaperSize.SelectedValue.ToString());
}
}
}
}
You can can modify the option value of your selected feature as as follows:
var featureName = "psk:PageMediaSize";
var newOptionValue = "psk:ISOA3"; // Your modified value here
XNamespace xns_psf = #"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework";
var query = from f in ticketRootXElement.DescendantsAndSelf(xns_psf + "Feature")
where (string)f.Attribute("name") == featureName
select f;
foreach (var f in query)
{
// TODO: handle the situation were a child <psf:Option> element is missing.
f.Element(xns_psf + "Option").SetAttributeValue("name", newOptionValue);
}
Notes:
XElement.Attribute(XName) can be used to look up an attribute by name, and XElement.SetAttributeValue(XName, Object) can be used to set or add an attribute value by name.
Casting an XAttribute to a string returns the value of the attribute, or null if the attribute was missing, and so is convenient to use when filtering by attribute value in a where statement.
If the selected <psf:Feature> element does not have a child <psf:Option> element, the above code will throw an exception. You will need to check your XML schema to determine whether this is possible, and if so, how to handle it.
Demo fiddle here.
Actually I did it using the following code. But "bdc" solution (above answer) sounds much better:
var element = ticketRootXElement.Descendants(xns_psf + "Feature")
.Where(arg => arg.Attribute("name").Value == "psk:PageMediaSize")
.Single();
var subelement = element.Descendants(xns_psf + "Option")
.Single();
subelement.FirstAttribute.SetValue("psk:ISOA3");
I am parsing the XML in C# this XML:
<Resident Type="R">
<Payment>1218</Payment>
</Resident>
I am parsing this way(please answer this same way, not other methods)
XmlDocument parsed_xml = new XmlDocument();
parsed_xml.LoadXml(dto.xml);
XmlNodeList test = parsed_xml.SelectNodes("/IER/Credit/Loan/LoanApp/Applicant/Personal/Individuals/Individual/Resident/Peyment");
if (xnList != null)
PAYMENT = xnList.Item(0).InnerText;
with this code I can get the Payment value that is 1218 but how I can get the attribute value of Type that is "R" ?
You'll want to look at the ParentNode to get the attribute.
string residentType = xnList[0].ParentNode.Attributes["Type"].Value;
I'm using LinqToXml to get the value of notes with the name GUID. So XML can look like this:
<xml>
<node1>
<GUID>123948390</GUID>
</node1>
<xml>
It may also be nested into more nodes. There is only one GUID per possible XML string.
What I am trying to do is parse the XML to collect the GUID element. The XML I am parsing may or may not contain a GUID element. If it does not contain an GUID element I want it to return an empty string.
What I tried is:
doc.Elements().Where(e => e.Name.LocalName == "GUID").Single().Value;
where doc is an XDocument, but this does not find the value and throws an exception if nothing is found. How can I get the value of the GUID element and make sure no exceptions are thrown if nothing is found?
/edit
var doc = XDocument.Parse(xmlString);
this.RequestId = (string)doc.Descendants("GUID").SingleOrDefault() ??
String.Empty;
always returns an empty string. Even if there is a GUID.
xmlString looks as follows: <?xml version=\"1.0\" encoding=\"utf-8\"?><MethodName xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\><GUID>blabla</GUID></MethodName>.
You should use Descendants if you want to search over all document. You can cast element to string to get its value without having NullReferenceException if there is no such element in document:
var guid = (string)doc.Descendants("GUID").SingleOrDefault() ?? String.Empty;
Or
var guid = doc.Descendants("GUID")
.Select(g => (string)g)
.SingleOrDefault(String.Empty);
Or with XPath
var guid = (string)doc.XPathSelectElement("//GUID") ?? String.Empty;
NOTE: If you have default xml namespace defined in your real xml document, then you should provide namespace to get element name:
var ns = doc.Root.GetDefaultNamespace();
var guid = (string)doc.Descendants(ns + "GUID").SingleOrDefault() ?? "";
If your element is not found it will be returning null. Single does not allow null's.
Use SingleOrDefault to return a null and then test that null condition...
var element = doc.Elements()
.Where(e => e.Name.LocalName == "GUID")
.SingleOrDefault();
if(element != null) {
return element.Value;
}
In my XML file i want to check if i have and device element in my xml file
i try this code but give me Null Reference Exception if it not found a device element
public bool HaveAnyDevice()
{
XDocument doc = XDocument.Load(path);
return !doc.Element("Settings").Elements("Device").Any();
}
If you are getting a NRE then your doc.Element("Settings") is null. You may check it before checking the next element.
return doc.Element("Settings") != null &&
doc.Element("Settings").Elements("Device").Any();
Your code should work. I think you don't have element Settings in your xml. So, just verify if it exists before trying to get it's elements:
public bool HaveAnyDevice()
{
XDocument doc = XDocument.Load(path);
var settings = doc.Element("Settings");
return (settings != null) && settings.Elements("Device").Any();
}
Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct