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());
}
}
}
Related
I am trying to parse the XML from this url, sampled below, in C#:
<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time='2020-01-16'>
<Cube currency='USD' rate='1.1169'/>
<Cube currency='JPY' rate='122.80'/>
<Cube currency='BGN' rate='1.9558'/>
</Cube>
</Cube>
</gesmes:Envelope>
This is the code I am using to get currencies:
xml.Load(#"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
XmlNamespaceManager ns = new XmlNamespaceManager(xml.NameTable);
ns.AddNamespace("gesmes", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
XmlNodeList nodes = xml.DocumentElement.SelectNodes("/gesmes:Envelope/Cube/Cube/Cube", ns);
foreach (XmlNode node in nodes)
{
// some code here
}
However, nodes is always null. I have tried a lot of options, and the unique option it worked for me was removing namespace from the original XML. But I would like to parse the source directly without modifications.
There are three issues to correct:
You misdefine the namespace associated with gesmes.
Change
ns.AddNamespace("gesmes", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
to
ns.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
Your XPath doesn't take into account that Cube and its descendants are in the default namespace.
Create a prefix for the default namespace:
ns.AddNamespace("eu", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
Update your XPath with the namespace prefix from #2:
/gesmes:Envelope/eu:Cube/eu:Cube/eu:Cube
^^^ ^^^ ^^^
(Cube cubed? 🙂 )
After fixing the above issues, your code should work as expected.
Linq XML has always given me less headaches:
var doc = XDocument.Load("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
string ns = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";
var outerCube = doc.Root.Element(XName.Get("Cube", ns));
var timeCube = outerCube.Element(XName.Get("Cube", ns));
Console.WriteLine("Time: " + timeCube.Attribute("time").Value);
foreach (var cube in timeCube.Elements())
{
Console.WriteLine(cube.Attribute("currency").Value + " => " + cube.Attribute("rate"));
}
I just wrote the same code for somebody last month. Use a dictionary
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 URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(URL);
XNamespace ns = doc.Root.GetDefaultNamespace();
Dictionary<string, decimal> dict = doc.Descendants(ns + "Cube").Where(x => x.Attribute("currency") != null)
.GroupBy(x => (string)x.Attribute("currency"), y => (decimal)y.Attribute("rate"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
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());
}
}
}
I have an XML Documnet consisting parent nodes and child nodes,
<?xml version='1.0' encoding='UTF-8'?>
<response>
<system_timestamp>2016-10-21 13:40:28</system_timestamp>
<response_data>
<status>Active</status>
<profile>
<first_name>John</first_name>
<last_name>Abraham</last_name>
<ship_to_address>
<address_1>null</address_1>
<address_2>null</address_2>
<city>null</city>
<state>null</state>
<postal_code>null</postal_code>
</ship_to_address>
</profile>
</response_data>
</response>
I am having few null valued child nodes like <address_1> and <address_2>. So, now how would I remove those null values of my child nodes. I tried
doc.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
But this is not working . And i am using this
XmlDocument doc = new XmlDocument();
doc.LoadXml(_value);
code to parse xml document. Do we have any other methods to remove using XMLDocument instead of XElement.
e.Value isn't a null reference or an empty string - it's the string "null" because that's the value in your element.
You want:
doc.Descendants().Where(e => (string) e == "null").Remove();
When removing an item from a list you must removed from last item to first item otherwise the indexing gets screwed up and not all the items get removed. Try this
sing 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);
List<XElement> nulls = doc.Descendants().Where(x => (string)x == "null").ToList();
for (int i = nulls.Count - 1; i >= 0; i--)
{
nulls[i].Remove();
}
}
}
}
i wish you a happy new year!
I got following XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<SW.CodeBlock ID="0">
<SW.CompileUnit ID="1">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.TEST.com">
<Parts> </Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.CompileUnit>
<SW.CompileUnit ID="2">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.TEST.COM">
<Parts> </Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.CompileUnit>
</SW.CodeBlock>
How can i add a Child in "Parts" from SW.CompileUnit ID = 1 and SW.CompileUnit ID = 2 etc.?
I would like to create a loop (for-loop), which creates a child in "Parts" for every "SW.CompileUnit"-Node
Could you please help me?
PS: I use VS2015, C#, not using Linq or XPath etc.
Until now I add a child like this:
XmlNode xPiece = xdoc.SelectSingleNode("//NS2:Parts",nsmgr);
xPiece.AppendChild(myXMLElement);
but it only adds a child in the first SW.CompileUnit Node (with the ID=1)
...
Thanks in advance
SelectSingleNode() returns only the first matched elements. To get all matched elements, you're supposed to use SelectNodes() instead :
var nodes = xdoc.SelectNodes("//NS2:Parts",nsmgr);
foreach(XmlNode node in nodes)
{
//create new myXMLElement
....
//and then append it to current <Parts>
node.AppendChild(myXMLElement);
}
Btw, parameter of SelectNodes() and SelectSingleNode() are XPath expressions (just saying, because you wrote "I use VS2015, C#, not using Linq or XPath etc").
Use XML Linq. Not sure if I got your request exactly correct.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<SW.CodeBlock ID=\"0\">" +
"<SW.CompileUnit ID=\"1\">" +
"<AttributeList>" +
"<NetworkSource>" +
"<FlgNet xmlns=\"http://www.TEST.com\">" +
"<Parts> </Parts>" +
"</FlgNet>" +
"</NetworkSource>" +
"</AttributeList>" +
"</SW.CompileUnit>" +
"<SW.CompileUnit ID=\"2\">" +
"<AttributeList>" +
"<NetworkSource>" +
"<FlgNet xmlns=\"http://www.TEST.COM\">" +
"<Parts> </Parts>" +
"</FlgNet>" +
"</NetworkSource>" +
"</AttributeList>" +
"</SW.CompileUnit>" +
"</SW.CodeBlock>";
XDocument doc = XDocument.Parse(xml);
var compileUnits = doc.Descendants("SW.CompileUnit").Select(x => new {
ID = (string)x.Attribute("ID"),
parts = x.Descendants().Where(y => y.Name.LocalName == "Parts").FirstOrDefault()
}).ToList();
foreach (var compileUnit in compileUnits)
{
compileUnit.parts.Add(new XElement(compileUnit.parts.Name.Namespace + "ID", compileUnit.ID));
}
}
}
}
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