I'm parsing XML. I normally parse it the way I show in the code below which is straightforward The problem is that I don't own the XML I'm parsing and I can't change it. Sometimes there is no thumbnail element (there are no tags) and I get an Exception.
Is there a way to maintain this simplicity and check if the element exists? Or do I have to get first an XElement list with LINQ, to then check it and fill only the existing object properties?
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
XDocument dataDoc = XDocument.Load(new StringReader(e.Result));
var listitems = from noticia in dataDoc.Descendants("Noticia")
select new News()
{
id = noticia.Element("IdNoticia").Value,
published = noticia.Element("Data").Value,
title = noticia.Element("Titol").Value,
subtitle = noticia.Element("Subtitol").Value,
thumbnail = noticia.Element("Thumbnail").Value
};
itemList.ItemsSource = listitems;
}
[Edit]Jon Skeet's answer should be the accepted answer. It is far more readable and easier to apply.[/edit]
Create an extension method like this :
public static string TryGetElementValue(this XElement parentEl, string elementName, string defaultValue = null)
{
var foundEl = parentEl.Element(elementName);
if (foundEl != null)
{
return foundEl.Value;
}
return defaultValue;
}
then, change your code like this :
select new News()
{
id = noticia.TryGetElementValue("IdNoticia"),
published = noticia.TryGetElementValue("Data"),
title = noticia.TryGetElementValue("Titol"),
subtitle = noticia.TryGetElementValue("Subtitol"),
thumbnail = noticia.TryGetElementValue("Thumbnail", "http://server/images/empty.png")
};
This approach allows you to keep a clean code with isolating the check of element presence. It also allow you to define a default value, which can be helpful
Instead of using the Value property, if you cast to string you'll just get a null reference instead:
void wc_DownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs e)
{
XDocument dataDoc = XDocument.Load(new StringReader(e.Result));
var listitems = from noticia in dataDoc.Descendants("Noticia")
select new News()
{
id = (string) noticia.Element("IdNoticia"),
published = (string) noticia.Element("Data"),
title = (string) noticia.Element("Titol"),
subtitle = (string) noticia.Element("Subtitol"),
thumbnail = (string) noticia.Element("Thumbnail")
};
itemList.ItemsSource = listitems;
}
That uses the explicit conversion from XElement to string, which handles a null input by returning a null output. The same is true for all explicit conversions on XAttribute and XElement to nullable types, including nullable value types such as int? - you just need to be careful if you're using nested elements. For example:
string text = (string) foo.Element("outer").Element("inner");
will give a null reference if inner is missing, but will still throw an exception if outer is missing.
If you want a "default" value, you can use the null coalescing operator (??):
string text = (string) foo.Element("Text") ?? "Default value";
You could just use the System.Xml.Serialization.XmlSerializer to deserialize it from xml to an object. Then if the element doesn't exist the property of the object will just get it's default value.
Have a look here: http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx
or the new path
https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer
You may use the code below:
string content = item.Element("Content") == null ? "" : item.Element("Content").Value;
Related
I have a foreach loop that is pulling data from an XML file, however some fields are blank. When the loop tries to pull a specific value it will sometimes get a null reference exception. Is there a way to single out the variable that has the null value and set it to an empty string while displaying all the other values in an efficient way? For the sake of the example lets say the address field is returning the null value.
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(id);
XmlNodeList person = xmldoc.SelectNodes("//parent/child");
foreach (XmlNode node in person)
{
try
{
var name = node["name"].InnerText;
var phone = node["phone"].InnerText;
var email = node["email"].InnerText;
var address = node["address"].InnerText;
lblPopulate2.Text = name;
lblPopulate7.Text = address;
lblPopulate5.Text = phone;
lblPopulate6.Text = email;
}
catch(NullReferenceException ex)
{
???
}
finally
{
}
You could use the null conditional operator which would return null if the address node is not present, otherwise the InnerText.
var address = node["address"]?.InnerText;
And then the null coalescing operator for setting your Text property:
lblPopulate7.Text = address ?? string.Empty;
I'm getting a text string from a website and parsing it into an XDocument. I'm looking to feed the value of certain elements into a very simple object (named NWSevent). My problem is that the original string changes and the XML tree varies; sometimes there are numerous events, up to 40, sometimes there is only one, and sometimes there is only one that does not have all the characteristics. If there are no alerts, then the "event" element has a title, but no areaDesc, summary, or severity.
I have two constructors for NWSevent, one takes in a single string, the other takes in four string arguments. I'm having trouble getting around a NullReferenceException. The if statement below can't do it because there is no value to compare. I'd appreciate any help.
public static void ParseWeatherData(String xmlString)
{
String ticker = string.Empty;
XDocument root = XDocument.Parse(xmlString);
XNamespace ns = XNamespace.Get("http://www.w3.org/2005/Atom");
XNamespace nsCap = XNamespace.Get("urn:oasis:names:tc:emergency:cap:1.1");
//get list of entry elements, set conditions for title, areaDesc, etc
var xlist = root.Descendants(ns + "entry").Select(elem => new
{ //use first or default to deal with possiblity of null return
Title = elem.Descendants(ns + "title").FirstOrDefault(),
AreaDesc = elem.Descendants(nsCap + "areaDesc").FirstOrDefault(),
Severity = elem.Descendants(nsCap + "severity").FirstOrDefault(),
Summary = elem.Descendants(ns + "summary").FirstOrDefault()
});
foreach (var el in xlist) //need to address null values when no alerts
{
if (el.AreaDesc.Value != null) //causes yellow null ERROR; no value exists for el.areaDesc.value
{
String titleIn = el.Title.Value;
String areaIn = el.AreaDesc.Value;
String severityIn = el.Severity.Value;
String summaryIn = el.Summary.Value;
new Models.NWSevent(titleIn, areaIn, severityIn, summaryIn);
}
else
{
String titleIn = el.Title.Value;
new Models.NWSevent(titleIn);
}
}
Embarassing! Props to Dweeberly for pointing it out. I just need to change the if statement from
if (el.AreaDesc.Value != null){}
to if (el.AreaDesc != null){}
I have an XElement that looks like this:
<User ID="11" Name="Juan Diaz" LoginName="DN1\jdiaz" xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/" />
How can I use XML to extract the value of the LoginName attribute? I tried the following, but the q2 "Enumeration yielded no results".
var q2 = from node in el.Descendants("User")
let loginName = node.Attribute(ns + "LoginName")
select new { LoginName = (loginName != null) };
foreach (var node in q2)
{
Console.WriteLine("LoginName={0}", node.LoginName);
}
var xml = #"<User ID=""11""
Name=""Juan Diaz""
LoginName=""DN1\jdiaz""
xmlns=""http://schemas.microsoft.com/sharepoint/soap/directory/"" />";
var user = XElement.Parse(xml);
var login = user.Attribute("LoginName").Value; // "DN1\jdiaz"
XmlDocument doc = new XmlDocument();
doc.Load("myFile.xml"); //load your xml file
XmlNode user = doc.getElementByTagName("User"); //find node by tag name
string login = user.Attributes["LoginName"] != null ? user.Attributes["LoginName"].Value : "unknown login";
The last line of code, where it's setting the string login, the format looks like this...
var variable = condition ? A : B;
It's basically saying that if condition is true, variable equals A, otherwise variable equals B.
from the docs for XAttribute.Value:
If you are getting the value and the attribute might not exist, it is more convenient to use the explicit conversion operators, and assign the attribute to a nullable type such as string or Nullable<T> of Int32. If the attribute does not exist, then the nullable type is set to null.
I ended up using string manipulation to get the value, so I'll post that code, but I would still like to see an XML approach if there is one.
string strEl = el.ToString();
string[] words = strEl.Split(' ');
foreach (string word in words)
{
if (word.StartsWith("LoginName"))
{
strEl = word;
int first = strEl.IndexOf("\"");
int last = strEl.LastIndexOf("\"");
string str2 = strEl.Substring(first + 1, last - first - 1);
//str2 = "dn1\jdiaz"
}
}
I have wrote a c# function in order to parse an XML Stream.
My XML can have several nodes.
Example :
<Stream>
<One>nnn</One>
<Two>iii</Two>
<Three>jjj</Three>
</Stream>
But sometimes, it is :
<Stream>
<Two>iii</Two>
</Stream>
Here is my c# code :
var XML = from item in XElement.Parse(strXMLStream).Descendants("Stream") select item;
string strOne = string.Empty;
string strTwo = string.Empty;
string strThree = string.Empty;
if ((item.Element("One").Value != "")
{
strOne = item.Element("One").Value;
}
if ((item.Element("Two").Value != "")
{
strTwo = item.Element("Two").Value;
}
if ((item.Element("Three").Value != "")
{
strThree = item.Element("Three").Value;
}
With this code, if my Stream is full ( Node On, Two and three), there's no problem! But, if my Stream has only the node "Two", I get a NullReferenceException.
Is there a way to avoid this exception (I cannot change my Stream).
Thanks a lot :)
You should check if item.Element("anything") is null before accessing it's Value property.
if (item.Element("Three") != null && item.Element("Three").Value != "")
You need to do:
if (item.Element("One") != null)
{
strOne = item.Element("One").Value;
}
.Element(String) returns null if an element of the name you requested does not exist.
Checking if value != "" is pointless, because all you are preventing is the reassignment of an empty string to the strOne variable, which is already an empty string. Also, if you really needed to do the empty string check, using String.IsNullOrEmpty(String) method is the preferred way.
Instead of accessing Value property (which raises NullReferenceException if element not exist, as you already know) cast elements to strings. You can use ?? to provide default value for non-existing elements:
string strOne = (string)item.Element("One") ?? String.Empty;
string strTwo = (string)item.Element("Two") ?? String.Empty;
string strThree = (string)item.Element("Three") ?? String.Empty;
Let's say I have the following XML:
<Account>
<AccountExpirationDate>6/1/2009</AccountExpirationDate>
</Account>
I want to use LINQ to XML to parse this into an object I'll call Account:
public class Account {
public DateTime? AccountExpirationDate { get; set; }
}
This is the C# code I've tried, but it won't let me use null:
var accountSettings =
from settings in templateXML.Descendants("Account")
select new Account {
AccountExpirationDate =
string.IsNullOrEmpty(settings.Element("AccountExpirationDate").Value)
? DateTime.Parse(settings.Element("AccountExpirationDate").Value)
: null
};
Is there a way for me to only assign AccountExpiration a date if the element exists in the XML? In my business logic it is acceptable for the value to be null. Thanks!
var accountSettings =
from settings in templateXML.Descendants("Account")
select new Account {
AccountExpirationDate =
string.IsNullOrEmpty((string)settings.Element("AccountExpirationDate"))
? (DateTime?)null
: DateTime.Parse(settings.Element("AccountExpirationDate").Value)
};
You can just use:
from settings in templateXML.Descendants("Account")
let el = settings.Element("AccountExpirationDate")
let el2 = (el == null || string.IsNullOrEmpty(el.Value)) ? null : el
select new Account {
AccountExpirationDate = (DateTime?)el2
};
there is a conversion operator that works this magic using standard xml datetime formatting, and which returns null if the element doesn't exist (note I don't read .Value).
Try:
var accountSettings = from settings in templateXML.Descendants("Account")
where settings.Element("AccountExpriationDate") != null
&& !String.IsNullOrEmpty(settings.Element("AccountExpriationDate").Value)
select new Account
{
AccountExpirationDate = DateTime.Parse(settings.Element("AccountExpirationDate").Value)
};
For a more readable syntax and if you need such a check multiple times, you could use extension methods:
public static DateTime? ToDateTime(this Element e)
{
if (e == null)
return null;
if (string.IsNullOrEmpty(e.Value))
return null;
else
return DateTime.Parse(e.Value);
}