Linq statement in C# to extract data from XElement - c#

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);

Related

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);

XElement.Element("Leve1/Level2") instead of XElement.Element("Leve1").Element("Level2")

If I have the following XML in XElement myXML variable in C#,
<Example>
<Level1>
<Level2>myvalue</Level2>
</Level1>
</Example>
To get "myvalue" I need to do as below:
myXML.Element("Leve1").Element("Level2").Value;
Is there any shortcut to do it like:
myXML.Element("Leve1/Level2").Value
Thanks...
You can use xpath with exactly the same syntax you are after:
var myValue = myXML.XPathSelectElement("Level1/Level2").Value;
XPathSelectElement is extension method, so you need to add using System.Xml.XPath; to be able to use it.
Try below code snippet,
//Load xml
XDocument xdoc = XDocument.Parse("<Example><Level1><Level2>myvalue</Level2</Level1></Example>");
//Run query
var lv2s = (from lv2 in xdoc.Descendants("Level2") select lv2.Value).ToList();
I would personally recommend you to use Descendants. If you want to apply certain path using Level1, you could filter by Parent on Level1;
var elementValue = doc.Descendants("Level2")
.Where(x => x.Parent.Name == "Level1")
.Select(x => x.Value).FirstOrDefault();

C# Read a specific element which is in a XML Node

I searched a long time in order to get an answer but as i can see is not working.
I have an XML File and I would like to read a specific element from a node.
For example, this is the XML:
<Root>
<TV>
<ID>2</ID>
<Company>Samsung</Company>
<Series>13523dffvc</Series>
<Dimesions>108</Dimesions>
<Type>LED</Type>
<SmartTV>Yes</SmartTV>
<OS>WebOS</OS>
<Price>1993</Price>
</TV>
</Root>
I want to get the ID element in the code as a variable so i can increment it for the next item which i will add.
This is the code at this moment, but i can not find a way to select something from the item itself.
XDocument doc = XDocument.Load("C:TVList.XML");
XElement TV = doc.Root;
var lastElement = TV.Elements("TV").Last()
A query for the last TV's id (this will return 0 if there are no elements):
var lastId = (int) doc.Descendants("TV")
.Elements("ID")
.LastOrDefault();
You might also want the highest id (in case they're not in order):
var maxId = doc.Descendants("TV")
.Select(x => (int)x.Element("ID"))
.DefaultIfEmpty(0)
.Max();
See this fiddle for a working demo.
Use like this to get id value
XDocument doc = XDocument.Load(#"C:\TVList.XML");
XElement root = doc.Element("Root");
XElement tv = root.Element("TV");
XElement id = tv.Element("ID");
string idvalue = id.Value;
also make your <Type>LED</Tip> tag of xml to <Type>LED</Type> for match

C# reading values of elements from an XML tree

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));

remove sections of XML document with Linq

How would i using Linq remove all section where their element contains parameter with {} ? In my example i want to remove section with {SecName1}
Source document:
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>Test Layout1</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
<ReceiptLayout>
<Name>{SecName1}</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
</ReceiptLayoutMaintenanceRequest>
Wanted output
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>AAA</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
thanks
This removes any ReceiptLayout node which has a child Name that starts and ends with brackets and produces your desired output:
XDocument doc = XDocument.Load(#"test.xml"); //load xml
var nodesToRemove = doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.ToList();
foreach (var node in nodesToRemove)
node.Remove();
This can be shortened into one Linq statement, personally I prefer to keep Linq query and modification (removal) separate though:
doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.Remove();
var doc = XDocument.Parse(xml);
doc.Descendants()
.Where(n => !n.HasElements && Regex.IsMatch(n.Value, "[{].*?[}]"))
.Select(n=>n.Parent) // because you want to remove the section not the node
.Remove();
xml = doc.ToString();
A variant of BrokenGlass code using the let keyword
var doc = XDocument.Load(#"test.xml");
var list = from p in doc.Descendants("ReceiptLayout")
let q = p.Element("Name")
let r = q != null ? q.Value : string.Empty
where r.StartsWith("{") && r.EndsWith("}")
select p;
list.Remove();
This is "premature optimization" :-) I "cache" the p.Element("Name").Value. Ah... And I check if really there is a Name Element so that everything doesn't crash if there isn't one :-)

Categories

Resources