Filter XML with dynamic where clause - c#

I have the following XML:
<Events>
<Event>
<EventID displayName="Event ID">1</EventID>
<EventName displayName="Event Name">Some event</EventName>
<OrgID displayName="Organization ID">8</OrgID>
</Event>
<Event>
<EventID displayName="Event ID">2</EventID>
<EventName displayName="Event Name">Another Event</EventName>
<OrgID displayName="Organization ID">10</OrgID>
</Event>
</Events>
I want to be able to filter them by a where clause constructed dynamically. For example:
Where("Event ID = 2 AND (Organization ID = 8 OR Organization ID = 10)")
Please note that I can only use the displayName to filter the data. Obviously I can get the tag name from the displayName and construct the where clause, but that means some added calculations on the clients machine, which if possible, I'd like to avoid.
I have explored the options to use Dynamic Linq or DataTable.Select() but with my limited knowledge on Linq, I cant seem to find an easy way to adopt those to filter XML data. Any help/hint is appreciated.

Using LINQ-to-XML dot notation to extract the elements:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string content = #"<Events>
<Event>
<EventID displayName=""Event ID"">1</EventID>
<EventName displayName=""Event Name"">Some event</EventName>
<OrgID displayName=""Organization ID"">8</OrgID>
</Event>
<Event>
<EventID displayName=""Event ID"">2</EventID>
<EventName displayName=""Event Name"">Another Event</EventName>
<OrgID displayName=""Organization ID"">10</OrgID>
</Event>
</Events>";
XDocument doc = XDocument.Load(new System.IO.StringReader(content));
var events = doc.Descendants("Event")
.Where(p => p.Elements("EventID").First().Value == "1")
.Where(p => p.Elements("OrgID").First().Value == "8" || p.Elements("OrgID").First().Value == "10");
}
}
}
The System.XML.Linq namespace is incredibly useful.

Parse the xml completely
var eventList=doc.Elements("Event")
.Select(x=>new
{
EventId=int.Parse(x.Element("EventId").Value),
EventName=x.Element("EventName").Value,
OrgID=int.Parse(x.Element("OrgID").Value)
});
You could now make a generalized method,
public string getEventName(int eventId,params int[] orgId)
{
return eventList.Where(x=>
x.EventId==eventId &&
orgId.Any(y=>y==x.OrgID))
.Select(x.EventName)
.Single();
}
Now you can do
getEventName(2);
getEventName(2,8);
getEventName(2,8,10);

So want you want to do is to use linq syntax to get data from your xml ?
Something like that?
XDocument loadedXML = XDocument.Load('Your xml file name');
var yourStrongTypeEventsList
= (from event in loadedXML.Descendants("Event")
where event.Element("EventID").Attribute("displayName").Value.Equals("Your value here")
select new
{
EventID = event.Element("EventID").Value,
EventName = event.Element("EventName").Value
}).ToList();

Related

XML parsing to get a particular tag value in a list of XElement

I have below XML and c# code.
I want to get the list of values like 333,382 in a list present in <Event >tag.
However with below code i am getting the list of whole Event tag like{ Event = "333" },{ Event = "382" }
I can code few more logic to get only the numeric part out of it but for code efficiency its better to get the required value like 333,382 in eventList itself through LINQ.
Can somebody please help on it ? Thanks in advance.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string eventListXML= #"<XYZ>
<Task>
<ABC>
<EvtLogResponse>
<LogInfo>
<CommID>8</CommID>
<UserId>2</UserId>
<Date>10/09/2020</Date>
<Time>06:24:01 PM</Time>
</LogInfo>
<EventLogAllRes>
<EventOrRestore>E</EventOrRestore>
<Event>333</Event>
</EventLogAllRes>
<EventLogAllRes>
<EventOrRestore>E</EventOrRestore>
<Event>382</Event>
</EventLogAllRes>
<FilePath>C:\Windows\test.txt</FilePath>
</EvtLogResponse>
<UserID>1</UserID>
</ABC>
</Task>
</XYZ>";
XDocument xDoc = XDocument.Parse(eventListXML);
XElement Xele = xDoc.Element("XYZ").Element("Task").Element("ABC").Element("EvtLogResponse");
List<XElement> Logs = Xele.Elements("EventLogAllRes").ToList<XElement>();
var eventList = (from Event in Logs.Descendants("Event")
select new
{
Event = Event.Value
}).ToList();
}
}
}
You want it to be like list of integers right??
Just remove make a new Event and code like below
var eventList = (from Event in Logs.Descendants("Event")
select
Regex.Replace(Event.Value.ToString(), "[^0-9]+", string.Empty)).ToList();
Further if you want to have comma separated values then add below code to the code above.
var result = String.Join(",", eventList);
Here result will have the comma separated string.
Let me know if more anything else is needed.

How to get the value of elements using XML and Linq based off a specific tag name

Essentially, I have tried everything and for some reason I can't get the value of the elements in my XML based off the parameter I need it to meet. I feel like I'm close but I just don't know where I'm going wrong. I'm trying to get the value of the elements and put them into a list to be used elsewhere. Currently it doesn't put anything in the list.
I've tried XML Reader so now I'm giving Linq to XML a try but this won't work either.
private List<string> outputPath = new List<string>();
var doc = XDocument.Load(Path.Combine(projectDirectory, "JobPaths.xml"));
foreach (var child in doc.Element("Jobs").Elements("Job").Where(x => x.Attribute("Name").ToString() == jobName).Elements())
{
outputPath.Add(child.Name.ToString());
}
return outputPath;
Here's the XML:
<?xml version="1.0" encoding="utf-8" ?>
<Jobs>
<Job Name="events_monitoring_c">
<Path>\\stadb4412\</Path>
</Job>
<Job Name="events_monitoring_d">
<Path>\\stadb4412\</Path>
<Path>\\stadb1111\</Path>
<Path>\\stadb2412\</Path>
</Job>
</Jobs>
The jobName comes from the XML File, so I'm trying to get all the path elements based on the job name, regardless of how many there are. I want to get all the paths in the list to be used elsewhere.
To find nodes of a specific type/tag from an XDocument or XElement you use .Descendants(name), then you have .Attribute(name) that returns an XAttribute. To get its value, you use .Value, not .ToString().
Your code gets the Job elements, but then it gets the children elements as an IEnumerable of nodes and for each of them adds the Name of the tags, which is always Path.
What you are looking for is doc.Descendants("Job").Where(job=>job.Attribute("Name")?.Value==jobName).SelectMany(job=>job.Elements()).Select(elem=>elem.Value).ToList();
I did it without compiling, so I may be wrong.
You parse into a dictionary using Xml Linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, List<string>> dict = doc.Descendants("Job")
.GroupBy(x => (string)x.Attribute("Name"), y => y)
.ToDictionary(x => x.Key, y => y.Elements("Path").Select(z => (string)z).ToList());
}
}
}

C# Evaluate multiple Xpaths against an XmlDocument

I am writing a C# program where I would like to store a series of XPath statements as strings and evaluate them against an XMLDocument (or some other C# XML structure if there's a better one for this purpose) and store the resulting values in a dictionary / object.
My challenge is that my XPaths are not being able to be evaluated.
As a very simplified example, suppose this is my XML:
<root>
<a>
<child1 Id="Id1" Name="Name1" />
<child2 Id="Id2" Name="Name2" />
</a>
</root>
and, for example, one of my XPaths is:
//a/*[#Id='Id1']/name()
(Get the name of a's child element with the Id attribute = "Id1")
The simplified version of the code I'm trying to write to do this would be:
var xpath = #"//a/*[#Id='Id1']/name()";
var xml = #"<root><a><child1 Id='Id1' Name='Name1' /><child2 Id='Id2' Name='Name2' /></a></root>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var navigator = doc.CreateNavigator();
string ChildName = (string)navigator.Evaluate(xpath);
but I am getting the error that my XPath has an invalid token - Which I'm assuming the be the name() portion.
Is there any way to accomplish this using direct XPath statements rather than traversing the tree?
Thanks!!
Pretty sure you just need to rearrange your XPath if I'm understanding you correctly. Try this:
name(//a/*[#Id='Id1'])
Using xml liinq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication68
{
class Program
{
static void Main(string[] args)
{
string xml = "<root>" +
"<a>" +
"<child1 Id=\"Id1\" Name=\"Name1\" />" +
"<child2 Id=\"Id2\" Name=\"Name2\" />" +
"</a>" +
"</root>";
XDocument doc = XDocument.Parse(xml);
Dictionary<string, string> dict = doc.Descendants("a").FirstOrDefault().Elements().Where(x => x.Name.LocalName.StartsWith("child"))
.GroupBy(x => (string)x.Attribute("Id"), y => (string)y.Attribute("Name"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}

Linq ebay XML Parsing ASP and C# using a query style?

It's been a while since I have worked with ASP.Net and C#. I am trying to parse an XML API using C# and I am in need of some help. My Problem is I am not quite sure how to do this. I keep seeing conflicting methods too. Some show like I did below. Some show pretty awesome queries that to me look way more better.
Example of query
IEnumerable<string> partNos =
from item in purchaseOrder.Descendants("Item")
select (string) item.Attribute("PartNumber");
Which method is better and how do I achieve parsing the XML just to a textbox for now?
Here is the XML format:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<findItemsByKeywordsResponse xmlns="http://www.ebay.com/marketplace/search/v1/services">
<ack>Success</ack>
<version>1.12.0</version>
<timestamp>2012-06-20T22:20:33.539Z</timestamp>
<searchResult count="1">
<item>
<itemId>390432965446</itemId>
<title>
Yamaha RX-V673 7.2 Channel 90 Watt Aventage Receiver {Brand New}
</title>
<globalId>EBAY-US</globalId>
<primaryCategory>
<categoryId>14981</categoryId>
<categoryName>Home Theater Receivers</categoryName>
</primaryCategory>
<galleryURL>
http://thumbs3.ebaystatic.com/pict/3904329654464040_1.jpg
</galleryURL>
<viewItemURL>
http://www.ebay.com/itm/Yamaha-RX-V673-7-2-Channel-90-Watt-Aventage-Receiver-Brand-New-/390432965446?pt=Receivers_Tuners
</viewItemURL>
<productId type="ReferenceID">114468754</productId>
<paymentMethod>PayPal</paymentMethod>
<autoPay>false</autoPay>
<postalCode>54143</postalCode>
<location>Marinette,WI,USA</location>
<country>US</country>
<shippingInfo>
<shippingServiceCost currencyId="USD">0.0</shippingServiceCost>
<shippingType>Free</shippingType>
<shipToLocations>US</shipToLocations>
<expeditedShipping>false</expeditedShipping>
<oneDayShippingAvailable>false</oneDayShippingAvailable>
<handlingTime>2</handlingTime>
</shippingInfo>
<sellingStatus>
<currentPrice currencyId="USD">519.0</currentPrice>
<convertedCurrentPrice currencyId="USD">519.0</convertedCurrentPrice>
<sellingState>Active</sellingState>
<timeLeft>P28DT23H32M35S</timeLeft>
</sellingStatus>
<listingInfo>
<bestOfferEnabled>false</bestOfferEnabled>
<buyItNowAvailable>false</buyItNowAvailable>
<startTime>2012-06-19T21:48:08.000Z</startTime>
<endTime>2012-07-19T21:53:08.000Z</endTime>
<listingType>StoreInventory</listingType>
<gift>false</gift>
</listingInfo>
<returnsAccepted>true</returnsAccepted>
<condition>
<conditionId>1000</conditionId>
<conditionDisplayName>New</conditionDisplayName>
</condition>
<isMultiVariationListing>false</isMultiVariationListing>
</item>
</searchResult>
<paginationOutput>
<pageNumber>1</pageNumber>
<entriesPerPage>1</entriesPerPage>
<totalPages>1121495</totalPages>
<totalEntries>1121495</totalEntries>
</paginationOutput>
<itemSearchURL>
http://www.ebay.com/sch/i.html?_nkw=yamaha&_ddo=1&_ipg=1&_pgn=1
</itemSearchURL>
</findItemsByKeywordsResponse>
My C# Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Linq;
namespace ebayLinq
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string myAppID = "hidden from stack overflow";
string ebayUrl = "http://svcs.ebay.com/services/search/FindingService/v1?";
string operationName = "OPERATION-NAME=getSearchKeywordsRecommendation&";
string serviceVersion = "SERVICE-VERSION=1.11.0&";
string securityAppName = "SECURITY-APPNAME="+ myAppID +"&";
string responseData = "RESPONSE-DATA-FORMAT=XML&";
string rest = "REST-PAYLOAD&";
string searchString = "macbook Pro";
string keywords ="keywords="+searchString+"&";
var xml = XDocument.Load(ebayUrl +
operationName +
serviceVersion +
securityAppName +
responseData);
//XNamespace ns = "http://www.ebay.com/marketplace/search/v1/services";
//XElement ack = xml.Root.Element(ns + "ack");
}
}
}
Okay so as you can I can get it to work with the code above, but I dont know how to go deeper than the ack so far. I also would rather do queries as opposed to the method used above.
Any input friends?
With your input I came up with this but it doesn't work right?
XElement convertedCurrentPrice = (from x in xml.Root.Descendants("title") select x).FirstOrDefault();
string item = Convert.ToString(convertedCurrentPrice);
TextBox1.Text = item;
I'm not personally familiar with older methods of parsing XML but the new LINQ to XML stuff is the query style that you are talking about and definitely makes things quick and easy when it comes to pulling information out of your XDocument.
If you give me an example of one or more of the nodes that you want to pull data from in particular I can help you out with that but the basic structure would be (say if you wanted to grab the current price value of 519.0)
XElement convertedCurrentPrice = (from x in xml.Root.Descendants("convertedCurrentPrice") select x).FirstOrDefault();
This will return the entire XML node (everything in between and
Then getting the value is as simple as:
double price = Convert.ToDecimal(convertedCurrentPrice.Value);
It's hard to answer which syntax is better. For reference, they are called Query Syntax and Method Syntax. Here is a the MSDN article on the differences (the article recommends query syntax due to readability). They are roughly equivalent, and I use both of them frequently.
For further reference, here is an SO question discussing the topic.

Query an XDocument for elements by name at any depth

I have an XDocument object. I want to query for elements with a particular name at any depth using LINQ.
When I use Descendants("element_name"), I only get elements that are direct children of the current level. I'm looking for the equivalent of "//element_name" in XPath...should I just use XPath, or is there a way to do it using LINQ methods?
Descendants should work absolutely fine. Here's an example:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #"
<root>
<child id='1'/>
<child id='2'>
<grandchild id='3' />
<grandchild id='4' />
</child>
</root>";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("grandchild"))
{
Console.WriteLine(element);
}
}
}
Results:
<grandchild id="3" />
<grandchild id="4" />
An example indicating the namespace:
String TheDocumentContent =
#"
<TheNamespace:root xmlns:TheNamespace = 'http://www.w3.org/2001/XMLSchema' >
<TheNamespace:GrandParent>
<TheNamespace:Parent>
<TheNamespace:Child theName = 'Fred' />
<TheNamespace:Child theName = 'Gabi' />
<TheNamespace:Child theName = 'George'/>
<TheNamespace:Child theName = 'Grace' />
<TheNamespace:Child theName = 'Sam' />
</TheNamespace:Parent>
</TheNamespace:GrandParent>
</TheNamespace:root>
";
XDocument TheDocument = XDocument.Parse( TheDocumentContent );
//Example 1:
var TheElements1 =
from
AnyElement
in
TheDocument.Descendants( "{http://www.w3.org/2001/XMLSchema}Child" )
select
AnyElement;
ResultsTxt.AppendText( TheElements1.Count().ToString() );
//Example 2:
var TheElements2 =
from
AnyElement
in
TheDocument.Descendants( "{http://www.w3.org/2001/XMLSchema}Child" )
where
AnyElement.Attribute( "theName" ).Value.StartsWith( "G" )
select
AnyElement;
foreach ( XElement CurrentElement in TheElements2 )
{
ResultsTxt.AppendText( "\r\n" + CurrentElement.Attribute( "theName" ).Value );
}
You can do it this way:
xml.Descendants().Where(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
Descendants will do exactly what you need, but be sure that you have included a namespace name together with element's name. If you omit it, you will probably get an empty list.
There are two ways to accomplish this,
LINQ to XML
XPath
The following are samples of using these approaches,
List<XElement> result = doc.Root.Element("emails").Elements("emailAddress").ToList();
If you use XPath, you need to do some manipulation with the IEnumerable:
IEnumerable<XElement> mails = ((IEnumerable)doc.XPathEvaluate("/emails/emailAddress")).Cast<XElement>();
Note that
var res = doc.XPathEvaluate("/emails/emailAddress");
results either a null pointer, or no results.
I am using XPathSelectElements extension method which works in the same way to XmlDocument.SelectNodes method:
using System;
using System.Xml.Linq;
using System.Xml.XPath; // for XPathSelectElements
namespace testconsoleApp
{
class Program
{
static void Main(string[] args)
{
XDocument xdoc = XDocument.Parse(
#"<root>
<child>
<name>john</name>
</child>
<child>
<name>fred</name>
</child>
<child>
<name>mark</name>
</child>
</root>");
foreach (var childElem in xdoc.XPathSelectElements("//child"))
{
string childName = childElem.Element("name").Value;
Console.WriteLine(childName);
}
}
}
}
Following #Francisco Goldenstein answer, I wrote an extension method
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Mediatel.Framework
{
public static class XDocumentHelper
{
public static IEnumerable<XElement> DescendantElements(this XDocument xDocument, string nodeName)
{
return xDocument.Descendants().Where(p => p.Name.LocalName == nodeName);
}
}
}
This my variant of the solution based on LINQ and the Descendants method of the XDocument class
using System;
using System.Linq;
using System.Xml.Linq;
class Test
{
static void Main()
{
XDocument xml = XDocument.Parse(#"
<root>
<child id='1'/>
<child id='2'>
<subChild id='3'>
<extChild id='5' />
<extChild id='6' />
</subChild>
<subChild id='4'>
<extChild id='7' />
</subChild>
</child>
</root>");
xml.Descendants().Where(p => p.Name.LocalName == "extChild")
.ToList()
.ForEach(e => Console.WriteLine(e));
Console.ReadLine();
}
}
Results:
For more details on the Desendants method take a look here.
We know the above is true. Jon is never wrong; real life wishes can go a little further.
<ota:OTA_AirAvailRQ
xmlns:ota="http://www.opentravel.org/OTA/2003/05" EchoToken="740" Target=" Test" TimeStamp="2012-07-19T14:42:55.198Z" Version="1.1">
<ota:OriginDestinationInformation>
<ota:DepartureDateTime>2012-07-20T00:00:00Z</ota:DepartureDateTime>
</ota:OriginDestinationInformation>
</ota:OTA_AirAvailRQ>
For example, usually the problem is, how can we get EchoToken in the above XML document? Or how to blur the element with the name attribute.
You can find them by accessing with the namespace and the name like below
doc.Descendants().Where(p => p.Name.LocalName == "OTA_AirAvailRQ").Attributes("EchoToken").FirstOrDefault().Value
You can find it by the attribute content value, like this one.
(Code and Instructions is for C# and may need to be slightly altered for other languages)
This example works perfect if you want to read from a Parent Node that has many children, for example look at the following XML;
<?xml version="1.0" encoding="UTF-8"?>
<emails>
<emailAddress>jdoe#set.ca</emailAddress>
<emailAddress>jsmith#hit.ca</emailAddress>
<emailAddress>rgreen#set_ig.ca</emailAddress>
</emails>
Now with this code below (keeping in mind that the XML File is stored in resources (See the links at end of snippet for help on resources) You can obtain each email address within the "emails" tag.
XDocument doc = XDocument.Parse(Properties.Resources.EmailAddresses);
var emailAddresses = (from emails in doc.Descendants("emailAddress")
select emails.Value);
foreach (var email in emailAddresses)
{
//Comment out if using WPF or Windows Form project
Console.WriteLine(email.ToString());
//Remove comment if using WPF or Windows Form project
//MessageBox.Show(email.ToString());
}
Results
jdoe#set.ca
jsmith#hit.ca
rgreen#set_ig.ca
Note: For Console Application and WPF or Windows Forms you must add the "using System.Xml.Linq;" Using directive at the top of your project, for Console you will also need to add a reference to this namespace before adding the Using directive. Also for Console there will be no Resource file by default under the "Properties folder" so you have to manually add the Resource file. The MSDN articles below, explain this in detail.
Adding and Editing Resources
How to: Add or Remove Resources

Categories