Linq to XML read method throws NullReferenceException using C# in VS2017 - c#

This is my XML structure:
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
</results>
</report>
</root>
I want to read the information within result and create an object to set it's attribute to the value. This is my code to do this:
Read from XML:
XDocument rootDocument = XDocument.Load(filePath)
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name").Value
};
Create new object:
foreach(var r in vul)
{
Object a = new Object()
{
Name = r.Name
};
}
The Problem is that this produces a NullReferenceException when calling
rootDocument.Descendants("result")
Thank you in advance!

I am guessing that your select is trying to access an element that does not exist.
For example there is a missing name element from your original xml:
e.g. This xml will fail
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
<result>
<surname> name 2 </surname> //will fail when loop gets here
</result>
</results>
</report>
</root>
If it is possible that name element will not always exist you can update the code to handle null
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name")?.Value, //this will return null
};
..as r.Name can be null you need to account for this in the for loop.
foreach (var r in vul)
{
string Name = r.Name ?? string.empty;
}

I found the cause of this Error. I don't know why but calling the value of an element causes it. I changed
Name = r.Element("name").Value
to
Name= r.Element("name")

Related

C# How to select all nodes of xml that have matching values

Below is an example of my starting xml and the xml result i'm trying to achieve. What i'm trying to figure out is not how to create the desired xml but how to grab all matches nodes that have the same starting value. For example below, you'll see the xml has apples, there are two nodes with apples. I want to find all those nodes and then create a custom xml afterwards.
How do you loop for all nodes of an xml and then find all results on that same node level that have matching values?
<?xml version="1.0"?>
<results>
<result>
<fruit>apples</fruit>
<price>0</price>
</result>
<result>
<fruit>pears</fruit>
<price>1</price>
</result>
<result>
<fruit>apples</fruit>
<price>2</price>
</result>
</results>
<?xml version="1.0"?>
<results>
<result>
<fruit>apples</fruit>
<prices>
<price>0</price>
<price>2</price>
</prices>
</result>
<result>
<fruit>pears</fruit>
<prices>
<price>1</price>
</prices>
</result>
</results>
This VB code creates the desired output. It selects as results and orders them by fruit value, and then a simple loop to create the desired output.
Dim xe As XElement
' to load from a file
' Dim yourpath As String = "your path here"
'xe = XElement.Load(yourpath)
' for testing
xe = <results>
<result>
<fruit>apples</fruit>
<price>0</price>
</result>
<result>
<fruit>pears</fruit>
<price>1</price>
</result>
<result>
<fruit>apples</fruit>
<price>2</price>
</result>
</results>
Dim oxe As XElement = <results></results>
Dim rsltproto As XElement = <result>
<fruit></fruit>
<prices></prices>
</result>
Dim rslt As XElement
Dim fruit As String
'select all <result> order by fruit values
Dim allfruits As IEnumerable(Of XElement) = xe...<result>.OrderBy(Function(el) el.<fruit>.Value)
'simple loop to create new XML
For Each el As XElement In allfruits
If el.<fruit>.Value <> fruit Then
If rslt IsNot Nothing Then
oxe.Add(rslt)
End If
fruit = el.<fruit>.Value
rslt = New XElement(rsltproto)
rslt.<fruit>.Value = fruit
End If
rslt.<prices>.LastOrDefault.Add(el.<price>)
Next
If rslt IsNot Nothing Then
oxe.Add(rslt)
End If
' to save file
' xe.Save(yourpath)
This is a simple change to Timon's answer above
var doc = XDocument.Load(#"<path>\test.xml");
var fruitPrices = doc.XPathSelectElements("results/result")
.Select(d => new {
Fruit = d.Element("fruit").Value,
// TODO: parse to int if required
Price = d.Element("price").Value
})
.GroupBy(f => f.Fruit)
.Select(g => new {Fruit = g.Key, Prices = g.Select(x => x.Price)})
.ToList();
In LinqPad this gives
You can accomplish this by using XDocument. Do not forget to import.
using System.Xml.Linq;
using System.Xml.XPath;
load document
XDocument doc = XDocument.Load("C:\\t\\My File2.txt");
Select all result elements
Group by fruit value for example 3 apples 1 pears
Put the result elements from group and add them into an array
Create list that contains the array with the result elements
List<XElement[]> multipleElements = doc
.XPathSelectElements("results/result")
.GroupBy(result => result.Element("fruit").Value)
.Select(groupContent => groupContent.ToArray())
.ToList();
=========== result ===================
List [0] { apples,apples}
List [1] { pear }
XML I tested on:
<?xml version="1.0"?>
<results>
<result>
<fruit>apples</fruit>
<price>0</price>
</result>
<result>
<fruit>pears</fruit>
<price>1</price>
</result>
<result>
<fruit>apples</fruit>
<price>2</price>
</result>
</results>

Check xml element value C#

I have an xml file like:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Session TimeStamp="2016-12-21T17:01:01.8642453+02:00">
<Message>
<Content>test1</Content>
<ID>1</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Debug</EventType>
<Priority>High</Priority>
</Message>
<Message>
<Content>test2</Content>
<ID>2</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Exception</EventType>
<Priority>Low</Priority>
</Message>
<Message>
<Content>test3</Content>
<ID>3</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Info</EventType>
<Priority>Medium</Priority>
</Message>
<Message>
<Content>test4</Content>
<ID>4</ID>
<Timestamp>12/21/2016 17:01:01</Timestamp>
<EventType>Warn</EventType>
<Priority>None</Priority>
</Message>
</Session>
</Root>
I want to check the value of element Content in every message i have try with this method:
Assert.IsTrue(xDocument.Root.Elements("Session").Last().Elements("Message").First().Element("Content").Value.Contains("test1"));
exception: System.InvalidOperationException: Sequence contains no elements
The method fail, cant find the element value, how can i do it using xdocument?
Are you looking for this since you say
I want to check the value of element Content in every message
xDocument.Root.Elements("Session")
.Elements("Message")
.Elements("Content")
.Select(x => x.Value.Contains("test1"));
It would return which node contains test1 so the result would be true,false,false,false
Edit
as per your comment "i want only to verify if message 1 content contains string "test1" "
xDocument.Root.Elements("Session")
.Elements("Message")
.Elements("Content")
.FirstOrDefault().Value.Contains("test1");
XmlDocument advDoc=new XmlDocument();
advDoc.Load("test.xml");
XmlNodeList _ngroups = advDoc.GetElementsByTagName("Content");
foreach(XmlNode nd in _ngroups)
{
if(nd.InnerText.ToString()=="test1")
Console.WriteLine("true");
}
i want only to verify if message 1 content contains string "test1"
string pathToXmlFile = ""; // point to your xml file ...
using (StreamReader reader = File.OpenText(pathToXmlFile))
{
XDocument doc = XDocument.Load(reader); // load into XDocument
XElement idElement = doc.Root.Element("Session").Elements("Message").Elements("ID").First( item => item.Value == "1"); // since you need the message id = 1
string content = idElement.Parent.Element("Content").Value; // get the parent of this message id which is message element then navigate to its content element.
}
Hope this helps..

Grouping in LINQ to XML with "any" or first" aggregation

I need to group following XML using LINQ to XML or XPath by param/Type. With grouping I would like to get any (or first, doesn't matter) value of id tag.
<list>
<item>
<id>1</id>
<param>
<type>A</type>
</param>
</item>
<item>
<id>2</id>
<param>
<type>A</type>
</param>
</item>
<item>
<id>3</id>
<param>
<type>B</type>
</param>
</item>
<item>
<id>4</id>
<param>
<type>B</type>
</param>
</item>
Desirable results is
A - 1
B - 3
I've tried
var content = from item in doc.Descendants("item").Descendants("param")
group item by new
{
mType = (String)item.Element("type"),
} into g
select new
{
mType = g.Key.mType,
};
but I cannot figure out how to reference ID that is higher in hierarchy , or how to reference PARAM/TYPE when selecting ID.
I would suggest using System.Xml.Linq (XDocument)
If my understanding is good, here is what I've done:
var xml = "<list><item><id>1</id><param><type>A</type></param></item><item><id>2</id><param><type>A</type></param></item><item><id>3</id><param><type>B</type></param></item><item><id>4</id><param><type>B</type></param></item></list>";
var document = XDocument.Parse(xml);
foreach (var param in document.Root.Elements("item").GroupBy(i => i.Element("param").Element("type").Value))
{
var firstId = param.First().Element("id").Value;
Console.WriteLine ("The first of {0} = {1}", param.Key, firstId);
}
the output is :
The first of A = 1
The first of B = 3
In addition to what was suggested by Cedric, you could accomplish the same thing in pure XPath:
var xml = "<list><item><id>1</id><param><type>A</type></param></item><item><id>2</id><param><type>A</type></param></item><item><id>3</id><param><type>B</type></param></item><item><id>4</id><param><type>B</type></param></item></list>";
var document = XDocument.Parse(xml);
// start at the root, then grab the first item element which has a param/type element whose value is equal to 'B'
var answer = document.Root.XPathSelectElement("./item[./param/type='B'][1]").Element("id").Value;
In XPath, the square brackets function effectively like a where clause. My first set of square brackets qualify that I want an item that contains a matching param/type element. The second set of square brackets limit this down to the first match.

How retrieve deeper siblings using LINQ to XML?

I have an XML structure as follows. I need to extract "Value" and "String" by matching the command attributes? How to write LINQ for this?
<Root>
<Command val="1001" type="sync">
<Status>
<DataList>
<Info>
<Value>1</Value>
<String>Sample String 1 is set</String>
</Info>
<Info>
<Value>2</Value>
<String>Sample String 2 is set</String>
</Info>
<Info>
<Value>3</Value>
<String>Sample String 3 is set</String>
</Info>
</DataList>
</Status>
<Command>
</Root>
I tried something as below but exception occurred while running.
lst = (
from command in xmlDoc.Descendants("Command")
.Descendants("Status")
.Descendants("DataList")
select new EnumList
{
val = command.Element("Value").Value,
stringVal = command.Element("String").Value,
})
.ToList();
Try
lst = (
from command in xmlDoc.Descendants("Info")
select new EnumList
{
val = command.Element("Value").Value,
stringVal = command.Element("String").Value,
})
.ToList();
and you have error in xml sample (no close tag Command), change it to
</Command>
</Root>

Accessing child node value with parent reference with LINQ to XML

I am stuck with accessing child node value. Below is the XML structure and the code to create new contact.
<Order xmlns="http://example.com">
<MiscContact>
<MiscContact>
<ContactType>MailingContact</ContactType>
<Contact>
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
<IsValid>false</IsValid>
</Contact>
</MiscContact>
</MiscContact>
<ExportForm>
<Contact>
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
<IsValid>false</IsValid>
</Contact>
</ExportForm>
</Order>
Code to create new contact only for <MiscContact>:
XNamespace Namespace = "http://online.us.com";
var MiscContact = from mc in xmlDoc.Descendants(Namespace + "Contact")
where mc.Parent.Name.Equals("MiscContact")
select new Contact
{ Name = ob.Element(Namespace + "Name").Value }
Issue i encountered is that even though i have where clause to select only contact whose parent is MiscContact, but contact section both from <MiscContact> and <ExportForm> are getting loaded.
Any idea how to resolve this issue?
Your code appears to be working correctly with the where clause and pulls the Name element from MiscContact -> Contact -> Name. I think your problem is that you're using .Value in the end, which concatenates all these values together:
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
The result is "JIMRONRON JIM". If you need the "FullName" then you should use:
mc.Element(Namespace + "Name")
.Element(Namespace + "FullName").Value
Replace "FullName" with "First" or "Last" as needed.

Categories