How to exclude NULL blocks from XML using LINQ-to-XML? - c#

Consider this XML file. Note the first Tutorial has an Author child element, and the second Tutorial does not:
<?xml version="1.0" encoding="utf-8" ?>
<Tutorials>
<Tutorial>
<Author>The Tallest</Author>
<Title>
WPF Tutorial - Creating A Custom Panel Control
</Title>
<Date>2/18/2008</Date>
</Tutorial>
<Tutorial>
<Title>
2nd WPF Tutorial - Creating A Custom Panel Control
</Title>
<Date>2/18/2008</Date>
</Tutorial>
</Tutorials>
How do I use LINQ-to-XML to load the data that is present? The code below blows up when it gets to the Tutorial section that lacks an author. I cannot figure out how to write the where statement to exclude the block that lacks an author, or how to make the code elegantly skip over the missing data. I have tried this:
where tutorial.Element("Title") != null
But the above has no effect.... Here is the problem code:
XDocument xmlDoc = XDocument.Load("C:\\xml\\2.xml");
var tutorials = from tutorial in xmlDoc.Descendants("Tutorial")
select new
{
Author = tutorial.Element("Author").Value,
Title = tutorial.Element("Title").Value,
Date = tutorial.Element("Date").Value,
};
foreach (var tutorial in tutorials)
{
Console.WriteLine("author: " + tutorial.Author);
Console.ReadKey();
}

Use the XElement to String conversion operator instead of the Value property:
var tutorials = from tutorial in xmlDoc.Root.Elements("Tutorial")
select new
{
Author = (string)tutorial.Element("Author"),
Title = (string)tutorial.Element("Title"),
Date = (DateTime)tutorial.Element("Date"),
};

Instead of referencing the Value property of the XElement that might be null, you can do an explicit cast to string instead, like this:
Author = (string) tutorial.Element("Author")
Check out this article for more info:
Improving LINQ Code Smell with Explicit and Implicit Conversion Operators

Related

Reading specific element from xml

I feel this is really stupid but can't seem to figure it out.
I have a Combobox that populates from an XML file and works great.
Now I want to display a specific element (Description) when an item is selected, but the string always wants to return null.
Xml:
<?xml version="1.0" encoding="utf-8" ?>
<Barbarian>
<Special id="Lesser Ancestor Totem">
<SpecialName>Lesser Ancestor Totem</SpecialName>
<Description>Gain a +2 Insight Bonus to a skill (that you can use while raging) while raging</Description>
</Special>
</Barbarian>
Code to get the description and put into a RichTextBox:
public void LoadFeatDescription()
{
string Class = CharClassSelector.Text;
string Feat = FeatPicker.Text;
XDocument doc = XDocument.Load($"\\Xml\\Classes\\{Class}.xml");
string description = doc.XPathSelectElement($"//Special[#id='{Feat}']/Description").Value;
//the string description wants to stay null despite my efforts
DescriptionBox.Text = description;
}
The idea is that this will load a specific file and get the description element based on the id.
Am I missing something stupid?
Thanks!
Edit: added in the entire XML contents
I got it.
Changed string description = doc.XPathSelectElement($"//Special[#id='{Feat}']/Description").Value;
to
string description = (string)doc.XPathSelectElement($"//Special[#id='{Feat}']/Description").Value;
I knew it was something stupid...

Search for attribute value in xaml file using linq in C#?

I know basic about getting node values from a xml file, but in a complicated xaml file I don't know how to write my linq query.
This is a sample of xaml I'm using:
<Activity something:something = " 2010" x:class = "example"..........>
<x:Members>
<x:Property Name= "aaaaa" Type = "Inargument" />
</x:Members>
<sap:abcdef>1322,2222</sap:abcdef>
<prwab:work sap2010:Annotation.AnnotationText = " I need this value" Active = "False" CreatedOn = "2014-07-09" ID = "123456" DisplayName = "theNameIneed">
<prwab:work.activity>
<prwais:collectingActivity sap2010:Annotation.AnnotationText = "I also need this value" > CreatedOn = "2014-07-09" ID = "1234232" DisplayName = "Ineed2">
<prwais .....>
</prwais>
</prwis:collectingActivity>
</prwab:work.activity>
</prwab:work>
</Activity>
I need to get the data in the lines that contain attribute "sap2010:Annotation.AnnotationText", and the value is going to be the content of sap2010:Annotation.AnnotationText, and key is going to be the attribute "ID" and attribute "displayName" in that line.
Here is the query I have right now, I know it's wrong but I don't know the proper way to write it:
var dataNodes = XElement.Load(file, LoadOptions.None);
Console.WriteLine("Loaded xaml file: " + file);
var dataNodesDictionary = from dataRecord in (dataNodes.Elements("prwab") && dataNodes.Elements("prwais"))//this line is wrong, but I dont know how to write it, since annotation may appears in different elements, and even if I only use "prwab" for testing, i still get nothing
where dataRecord.Attributes("Annotation.AnnotationText") != null
select new DictionaryEntry
{
Key = dataRecord.Attribute("DisplayName").Value.ToString() + "|" + dataRecord.Attribute("ID").Value.ToString(),
Value = dataRecord.Attribute("Annotation.AnnotationText").Value.ToString(),
};
Can some one help me please, thanks.
First find all elements that have the attribute. Then pull the attribute values.
// Name of the attributes we are looking
string ns = "http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation";
XName name = XName.Get("Annotation.AnnotationText", ns);
XDocument doc = XDocument.Load("XMLFile1.xml");
var q = doc.Descendants().Where(e => e.Attribute(name) != null)
.Select(e => new DictionaryEntry { Key = e.Attribute("ID").Value, Value = e.Attribute(name).Value });
SIDE NOTE: In the future please paste in valid XML so it is easier for others to reproduce the issue.
Since this is XAML, you'll have a problem:
The attribute you're looking for can be written using a tag:
<prwab:work sap2010:Annotation.AnnotationText="I need this value">
...
</prwab:work>
This is equivalent to:
<prwab:work>
<sap2010:Annotation.AnnotationText>
I need this value
</sap2010:Annotation.AnnotationText>
...
</prwab:work>
So if you need a reliable way to read that, you should use the XamlReader class (the one from the System.Xaml namespace - not the one from System.Windows.Markup). It works in a similar way to XmlReader, but normalizes the XAML it presents to you.
It won't be as straightforward as a linq query, but will be more reliable.

Reading XML to get value of a tag c#

I have my XML as
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<recordsDirectory>F:/model_RCCMREC/</recordsDirectory>
<transferDirectory>F:/model_RCCMrecTransfered/</transferDirectory>
<logDirectory>F:/model_RCCMLOG/</logDirectory>
<connectionstring>Data Source=192.168.1.7;Initial Catalog=RCCMdb;User ID=genesys;Password=genesys</connectionstring>
<table>RCCMrec</table>
<DBdestination>
<val1>ANI</val1>
<val2>DNIS</val2>
<val3>Date</val3>
<val4>Time</val4>
<val5>ConnId</val5>
<val6>UUID</val6>
<val7>EmployeeId</val7>
<val8>AgentDN</val8>
</DBdestination>
</configuration>
I need the value of the recordsDirectory tag.
I tried this,
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("C:/Users/yachna/Desktop/RCCM_TOOL/configRCCM.xml");
string bvalue = xmldoc.SelectSingleNode("recordsDirectory").InnerText.ToString();
But got an error saying
Object reference not set to an instance of an object.
Yes, SelectSingleNode("recordsDirectory") will return null, because you're applying that XPath to the document itself - which doesn't have a recordsDirectory element at the top level, it has a configuration element. You want:
xmldoc.SelectSingleNode("configuration/recordsDirectory")
Or go via the root element:
xmldoc.DocumentElement.SelectSingleNode("recordsDirectory")
(Or you can fetch all descendant elements call recordsDirectory, etc. There are plenty of options here.)
Personally I'd suggest changing to use LINQ to XML if you can, as it's a simpler way of using XML, IMO. It's not too bad in the code you've given so far, but as you do more things with XmlDocument you'll run into it being a bit of a pain - relatively speaking, anyway.
You should also consider separating the "fetching the node" from getting the text, so you can validate that you've found the one you want:
XmlNode node = xmldoc.DocumentElement.SelectSingleNode("recordsDirectory");
if (node != null)
{
// Use it
}
else
{
// No such node. What do you want to do?
}
Try this one in your SelectSingleNode
XmlNode node = doc.SelectSingleNode("/configuration/recordsDirectory");
string s = node.InnerText.ToString();
Hi To read the recordsDirectory tag you need to do :
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("C:/Users/yachna/Desktop/RCCM_TOOL/configRCCM.xml");
string bvalue = xmldoc.SelectSingleNode("configuration/recordsDirectory").InnerText.ToString();
It will work perfactly

LINQ TO XML attribute tag give no object reference error

Hi i am doing as follow
XDocument xmlDoc = XDocument.Load(#"F:\test2.xml");
var q = from c in xmlDoc.Descendants("autoivr.ok")
where c.Element("LS_CZIP4").Value == "1234"
select new
{
name = c.Element("LS_LIN").Value,
state = c.Element("LS_STATE").Value
};
When i use
where c.attribute("LS_CZIP4").Value == "1234"
i get error of object reference not set but when i use c.element there is no such error.
Following is the xml i made which is actually a table in sql converted to xml file
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<autoivr.ok>
<LS_LIN>abc</LS_LIN>
<LS_STATE>def</LS_STATE>
<LS_TYPE>5</LS_TYPE>
<LS_CZIP4>1234</LS_CZIP4>
<priority>0</priority>
</autoivr.ok>
Can someone let me know the problem and how can i resolve and can i work with element tag only instead of attribute . Thank You
Use casting instead of accessing Value property. Casting to string will return null for non-existing elements. Getting Value will throw an exception
XDocument xmlDoc = XDocument.Load(#"F:\test2.xml");
var q = from c in xmlDoc.Descendants("autoivr.ok")
where (string)c.Element("LS_CZIP4") == "1234"
select new
{
name = (string)c.Element("LS_LIN"),
state = (string)c.Element("LS_STATE")
};
BTW you need closing tag for <DocumentElement>. Also LS_CZIP4 is element, not attribute. See the difference here XML Elements vs. Attributes.
Element: <LS_LIN>abc</LS_LIN>
Attribute: <autoivr.ok LS_LIN="abc">

Can I avoid having to use fully-qualified element names in LINQ to XML?

Say I call XElement.Parse() with the following XML string:
var xml = XElement.Parse(#"
<?xml version="1.0" encoding="UTF-8"?>
<AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner>
<ID>7c75442509c41100b6a413b88b523bd6f46554cdbee5b6cbe27bc08cb3f6a865</ID>
<DisplayName>me</DisplayName>
</Owner>
<AccessControlList>
<Grant>
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Group">
...
");
When it comes time to query the element, I'm forced to use fully-qualified element names because that XML document contains an xmlns attribute in its root. This requires cumbersome creations of XName instances:
var AWS_XMLNS = "http://s3.amazonaws.com/doc/2006-03-01/";
var ownerElement = xml.Element(XName.Get("AccessControlPolicy", AWS_XMLNS)).Element(XName.Get("Owner", AWS_XMLNS));
When what I really want is simply,
var ownerElement = xml.Element("AccessControlPolicy").Element("Owner");
Is there a way to make LINQ to XML assume a specific namespace so I don't have to keep specifying it?
You could simplify by using
XNamespace ns = "http://s3.amazonaws.com/doc/2006-03-01/";
var ownerElement = xml.Element(ns + "AccessControlPolicy").Element(ns + "Owner");
I don't think you can (see Jon Skeet's comment), but there are a few tricks you can do.
1) create an extension method that appends the XNamespace to your string
2) Use VB?!?

Categories