C# reading values of elements from an XML tree - c#

I am trying to read some values of specific elements in an XML document, such as the values of <main><alpha>, <main><beta><epsilon> and <main><gama><delta>.
<?xml version="1.0" ?>
<main>
<alpha>One</alpha>
<beta>
<delta>DeltaValueFromBeta</delta>
<epsilon>EpsilonValueFromBeta</epsilon>
<phi>PhiValueFromBeta</phi>
</beta>
<gamma>
<delta>DeltaValueFromGamma</delta>
<epsilon>EpsilonValueFromGamma</epsilon>
<phi>PhiValueFromGamma</phi>
</gamma>
</main>
I can get the values using code like this:
XDocument doc = XDocument.Load("Sample.xml");
var quiz = from elements in doc.Elements("main").Elements("beta").Elements("epsilon")
select elements;
foreach (var item in quiz)
{
string sValue = (string) item.Value;
textBox1.AppendText(sValue);
}
Is there a more direct way to select the element values I need, without having to use a foreach loop?
Thanks
Nick

XPath is another option for direct access to a node. This reference may assist you.
How to get values from an XML file matching XPath query in C#
Edit to add to text box without foreach where result = XPath query result collection:
textbox1.AppendText(result.Select(x => x.Value));
XPath allows for dynamic path building a little easier than LINQ queries.

You can do it in your linq query:
var quiz = (from elements in doc.Elements("main").Elements("beta").Elements("epsilon")
select (string)elements).ToList();
It will give you value list.And you can append your text without using foreach:
StringBuilder sb = new StringBuilder();
var values = quiz.Select(x => sb.Append(x));
textBox1.AppendText(sb.ToString());
Or better way:
var text = doc.Descendants("epsilon")
.Select(x => (string)x)
.Aggregate((x,y) => x + y);
textBox1.AppendText(text)

textBox1.AppendText(quiz.Select(x => x.Value).Aggregate((s, s1) => s + s1));

Use the following so you do not have to use a foreach:
IEnumerable<string> values = quiz.Select(x => x.Value);
Or all in one:
IEnumerable<string> values = doc.Elements("main").Elements("beta").Elements("epsilon").Select(x => x.Value);
textBox1.AppendText(values.Aggregate((i, j) => i + j));

Related

C# parsing multiple elements

I have below xml structure.
<Bd>
<Det AccNo="380619034" Zip="344000"></Det>
<Det AccNo="380619022" Zip="345000"></Det>
</Bd>
It's known that there are always 2 elements under <Bd> tag.
I am able to retrieve first element using below code;
string soapResult = rd.ReadToEnd();
var xdoc = XDocument.Parse(soapResult);
var y = xdoc.Descendants("Bd");
foreach (var x in y) {
var AccNo = x.Element("Bd")?.Element("Det")?.Attribute("AccNo")?.Value;
}
However this code is only giving me first element. I want to get the second element as well but not able to do so. What am i missing?
You can use Linq without loop, like the follwing code :
XDocument xDocument = XDocument.Parse(soapResult);
IEnumerable<string> accNoList = xDocument.Descendants("Bd")
.Descendants()
.Select(x => x.Attribute("AccNo").Value);
demo
Console.WriteLine(string.Join(", ", accNoList));
Outcome
"380619034, 380619022"
For your code, you can change it to:
var xdoc = XDocument.Parse(soapResult);
var y = xdoc.Descendants("Bd")
.Descendants();
foreach (var x in y)
{
var AccNo = x.Attribute("AccNo")?.Value;
Console.WriteLine(AccNo);
}
I hope this will help you out.

Extract values from XML with linq query

New to linq and XML and looking for a way to return values of
<AddtlInf></AddtlInf> as a list which I will then concatenate the values to a single string.
XML below:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Document xmlns='urn:iso:std:iso:20022:tech:xsd:pain.002.001.03' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<CstmrPmtStsRpt>
<GrpHdr>
<MsgId>21233519</MsgId>
<CreDtTm>2018-11-29T09:28:00</CreDtTm>
<InitgPty>
<Nm>Standard Bank SA</Nm>
<Id>
<OrgId>
<BICOrBEI>SBZAZAJJXXX</BICOrBEI>
</OrgId>
</Id>
</InitgPty>
</GrpHdr>
<OrgnlGrpInfAndSts>
<OrgnlMsgId>Domestic Base-612742-300749</OrgnlMsgId>
<OrgnlMsgNmId>PAIN.001.001.03</OrgnlMsgNmId>
<OrgnlCreDtTm>2018-11-29T12:23:06</OrgnlCreDtTm>
<OrgnlNbOfTxs>1</OrgnlNbOfTxs>
<OrgnlCtrlSum>17500.00</OrgnlCtrlSum>
<GrpSts>RJCT</GrpSts>
<StsRsnInf>
<Rsn>
<Cd>NARR</Cd>
</Rsn>
<AddtlInf>Duplicate File</AddtlInf>
<AddtlInf>Error: 6789</AddtlInf>
<AddtlInf>Not Processed</AddtlInf>
</StsRsnInf>
</OrgnlGrpInfAndSts>
</CstmrPmtStsRpt>
</Document>
So far I have:
var info = doc.Descendants(ns + "CstmrPmtStsRpt")
.Descendants(ns + "OrgnlGrpInfAndSts")
.Descendants(ns + "StsRsnInf")
.Select(r => new
{
Info = r.Element(ns + "AddtlInf").Value
}).ToList();
But this only returns "Duplicate File", the first value. How do I fix this?
You need to access the sub-elements of StsRsnInf:
var info = doc.Descendants(ns + "CstmrPmtStsRpt")
.Descendants(ns + "OrgnlGrpInfAndSts")
.Descendants(ns + "StsRsnInf")
.SelectMany(r => r.Elements(ns + "AddtlInf").Select(s => new
{
Info = s.Value
})).ToList();
By using Element(without trailing s), you only access a single element instead of several, hence your result does only contain the value of the first element.
By using SelectMany as opposed to Select you can return several elements from sub-enumerations.
In your code, r.Element will always return you the first element of the collection.
Instead try to use r.Elements, which will return you the collection of "AddtlInf" elements.
For detailed information about this, have a look at this link!
You may use SelectMany in Linq.
var result = (string)xdoc.Descendants("member")
.FirstOrDefault(x => (string)x.Element("name") == "responseCode")
?.Element("value");
var info = xdoc.Descendants("CstmrPmtStsRpt")
.Descendants("OrgnlGrpInfAndSts")
.Descendants("StsRsnInf")
.SelectMany(r=> r.Descendants("AddtlInf")).ToList();
Console.WriteLine(info.Count); // prints 3.
Dotnet fiddle here: https://dotnetfiddle.net/IKd7go
As an alternative this seems easier to me:
var list = doc.XPathSelectElements
("Document/CstmrPmtStsRpt/OrgnlGrpInfAndSts/StsRsnInf/AddtlInf")
.Select(n => n.Value);

Linq statement in C# to extract data from XElement

I have a List containing elements like this:
{<d:element m:type="SP.KeyValue" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:Key>Path</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>}
I'd like help to discern the Linq statement required to extract only the "https://my.home.site.com" values from said List<>. The catch here is that we cannot only use the <d:Value> because only XElements in this list that has a <d:Key> value of Path, like in the example above, actually contain URLs in the <d:Value> key.
Does anyone know the magic Linq statement that would perform said data extract?
Assuming your data is coming from an XML file similar to this:
<?xml version="1.0"?>
<root xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:element m:type="SP.KeyValue">
<d:Key>Path</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>
<d:element m:type="SP.KeyValue">
<d:Key>NotPath</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>
</root>
The following code:
XElement root = XElement.Load("Some file");
List<string> urls;
//Query Syntax
urls = (from e in root.Elements(d + "element")
where e.Element(d + "Key").Value == "Path"
select e.Element(d + "Value").Value);
//Or
//Method Syntax
urls = (from e in root.Elements(d + "element")
where e.Element(d + "Key").Value == "Path"
select e.Element(d + "Value").Value).ToList();
Console.WriteLine(string.Join(",", urls));
Will result in (note that it ignores the "NotPath" key):
https://my.home.site.com
You can check out a live example here and check out this for more XElement information.
if you actually have a List of XElement:
var list = new List<XElement>(); //however you get your XElement collection
var values = list.Where(x => x.Elements().First(e => e.Name.LocalName == "Key").Value == "Path")
.Select(x => x.Elements().First(e => e.Name.LocalName == "Value").Value)
if you have an XDocument, you'd just modify the beginning of the query slightly.
I think that problem if with naespace declaration. Try this:
string xml = "<d:element m:type=\"SP.KeyValue\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\">"+
"<d:Key>Path</d:Key>"+
"<d:Value>https://my.home.site.com</d:Value>"+
"<d:ValueType>Edm.String</d:ValueType>"+
"</d:element>";
XDocument xmlObj = XDocument.Parse(xml);
XNamespace ns_d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
var result = xmlObj.Descendants(ns_d + "Value").Select(x => x.Value);

Parsing xml into anonymous type

I am trying to parse the xml below to load the id_name/rel_no pairs into an anonymous type collection. I am having a problem when looping through the collection and when element is missing in one of the elements. Is there a way not to load a particular pair when one of the elements id_name or rel_no is missing?
I get InvalidOperationException (sequence contains no elements) when the loop gets to that particular pair with missing element.
Thanks for any suggestions.
XDocument xdata = XDocument.Parse(data);
var query = from dox in xdata.Descendants("Inc")
select new
{
IDName= dox.Element("id_name").Value,
RelNo= dox.Descendants("rel_no").First().Value
};
XML
<Data>
<Inc>
<id_name>test</id_name>
<Relationships>
<Relationship>
<rel_no>004</rel_no>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test2</id_name>
<Relationships>
<Relationship>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test3</id_name>
<Relationships>
<Relationship>
<rel_no>006</rel_no>
</Relationship>
</Relationships>
</Inc>
</Data>
Accessing in a loop
foreach (var record in query)
{
}
var xdata = XDocument.Parse(data);
var items = xdata.Descendants("Inc")
.Select(d => new
{
DName = (string)d.Element("id_name"),
RelNo = ((string)d.Descendants("rel_no").FirstOrDefault() ?? "")
})
.ToList();

Best way to query XDocument with LINQ?

I have an XML document that contains a series of item nodes that look like this:
<data>
<item>
<label>XYZ</label>
<description>lorem ipsum</description>
<parameter type="id">123</parameter>
<parameter type="name">Adam Savage</parameter>
<parameter type="zip">90210</parameter>
</item>
</data>
and I want to LINQ it into an anonymous type like this:
var mydata =
(from root in document.Root.Elements("item")
select new {
label = (string)root.Element("label"),
description = (string)root.Element("description"),
id = ...,
name = ...,
zip = ...
});
What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?
(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:
If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
You may want to pull all the values at once and convert them into a dictionary
If you're using different data types for the contents, you might want to cast the element instead of using .Value
You may want to create a method to return the matching XElement for a given type, instead of having several queries.
Personally I don't think I'd even use a query expression for this. For example:
static XElement FindParameter(XElement element, string type)
{
return element.Elements("parameter")
.SingleOrDefault(p => (string) p.Attribute("type") == type);
}
Then:
var mydata = from item in document.Root.Elements("item")
select new {
Label = (string) item.Element("label"),
Description = (string) item.Element("description"),
Id = (int) FindParameter(item, "id"),
Name = (string) FindParameter(item, "name"),
Zip = (string) FindParameter(item, "zip")
};
I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.
use XPATH - it is very fast ( except xmlreader - but a lot of if's)
using (var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[#type='id']");
foreach (var x in query.Cast<XElement>())
{
Console.WriteLine( x.Value );
}
}

Categories

Resources