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());
}
}
}
Related
I am parsing below XML. I need value 'x2#email.com'.I am able to get the list of nodes successfully, but the problem is that with each iteration I am still getting 'x1#email.com' from the first group of 'Info' element.
XML:
<ClaimAdminContact xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Services.Models">
<claimAdminID>T1</claimAdminID>
<contactInfo>
<Info>
<desc>Level 1 Notifications</desc>
<emailAddress>x1#email.com</emailAddress>
<orgNum>1234</orgNum>
<type>T2</type>
</Info>
<Info>
<desc>Level 2 Notifications</desc>
<emailAddress>x2#email.com</emailAddress>
<orgNum i:nil="true"/>
<type>T2</type>
</Info>
<Info>
<desc>Level 3 Notifications</desc>
<emailAddress>x3#email.com</emailAddress>
<orgNum i:nil="true"/>
<type>T2</type>
</Info>
</contactInfo>
</ClaimAdminContact>
I have tried full xpath but still not able to get the next set of values. Below is the code that I am using to parse xml.
Code:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlEmail.NameTable);
nsmgr.AddNamespace("MsBuild", xmlns);
var contactInfo = xmlEmail.SelectNodes("/MsBuild:ClaimAdminContact/MsBuild:contactInfo/*", nsmgr);
foreach (XmlNode item in contactInfo)
{
_notificationDesc = item.SelectSingleNode("//MsBuild:desc", nsmgr).InnerText;
_reviewEmail = item.SelectSingleNode("//MsBuild:emailAddress", nsmgr).InnerText;
_orgNum = item.SelectSingleNode("//MsBuild:orgNum", nsmgr).InnerText;
}
Please
Use 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);
XNamespace ns = doc.Root.GetDefaultNamespace();
var results = doc.Descendants(ns + "Info").Select(x => new
{
desc = (string)x.Element(ns + "desc"),
email = (string)x.Element(ns + "emailAddress"),
orgNum = (string)x.Element(ns + "orgNum"),
type = (string)x.Element(ns + "type")
}).ToList();
}
}
}
Using System.Xml.Linq:
var xmlFile = #"myxml.xml";
var xDoc = XDocument.Load(xmlFile);
var infos = xDoc.Descendants("Info");
foreach (var info in infos)
{
var email = info.Element("emailAddress").Value;
}
EDIT: How to work with namespaces
If you have multiple namespaces and want to work with that, then you must specify the namespaces like below. Removing namespaces from the file is hardly a good idea.
var xmlFile = #"C:\Users\gurudeniyas\Desktop\myxml.xml";
XNamespace ns = "http://schemas.datacontract.org/2004/07/Services.Models";
XNamespace nsi = "http://www.w3.org/2001/XMLSchema-instance";
var xDoc = XDocument.Load(xmlFile);
var infos = xDoc.Descendants(ns + "Info");
foreach (var info in infos)
{
var email = info.Descendants(ns + "emailAddress").FirstOrDefault().Value;
Console.WriteLine(email);
}
The main challenge I was facing in parsing xml was the namespace attribute in the root element. It was preventing my code to parse the usual way and that was the reason why I tried using 'XmlNamespaceManager'. I decided to remove the namespace from the xml.
I used below recursive method to remove namespace from xml and everything worked!! I am not sure if this is the optimal way, but I was able to achieve what I wanted.
public XElement RemoveAllNamespaces(XElement root)
{
return new XElement(
root.Name.LocalName,
root.HasElements ?
root.Elements().Select(x => RemoveAllNamespaces(x)) :
(object)root.Value
);
}
Calling Code:
XElement noNsDoc = RemoveAllNamespaces(XElement.Parse(xmlString));
var xDoc = XDocument.Parse(noNsDoc.ToString());
Background
This is my small XML file I made online.
<?xml version="1.0"?>
<movement>
<skill id = "2">
<cooldown>5</cooldown>
</skill>
<skill id = "3">
<cooldown>10</cooldown>
</skill>
</movement>
This is some code I have so far to try to parse it.
string dataPath = Application.dataPath + "/Resources/XML/Skills/";
DirectoryInfo xmlFolder = new DirectoryInfo (dataPath);
FileInfo[] files = xmlFolder.GetFiles ("*.xml");
// Loops through each XML file
foreach (FileInfo file in files) {
XmlDocument xdoc = new XmlDocument ();
xdoc.Load (file.ToString ());
XmlNodeList nodes = xdoc.DocumentElement.SelectNodes ("/movement");
foreach (XmlNode node in nodes) { // Movement Layer
foreach (XmlNode skillNode in node.ChildNodes) {
print (skillNode.Value);
}
}
}
Problem
I am able to access the 5 and 10 values for cooldown, but cannot get the "id" value of the skill. The reason I'm trying to do this is to read the skill IDs into my game and store the information. I pretty much exhausted almost all the methods denoting from XmlNode, such as value and name, but it only returns "skill", and not the value of skill, such as 2 or 3. I feel like I'm missing something really simple here, but I'm having difficulty finding the correct terminology or phrasing for this issue.
LINQ To XML would make this parsing simpler...
static void Main(string[] args)
{
var doc =
#"<?xml version=""1.0""?>
<movement>
<skill id = ""2"" >
<cooldown> 5 </cooldown>
</skill>
<skill id = ""3"" >
<cooldown> 10 </cooldown>
</skill>
</movement> ";
var root = XDocument.Parse(doc);
foreach (var skill in root.Descendants("skill"))
{
Console.WriteLine("Skill: {0} \t CoolDOwn: {1}",
(int)skill.Attribute("id"),
skill.Element("cooldown").Value);
}
Console.ReadLine();
}
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);
var results = doc.Descendants("skill").Select(x => new {
id = (int)x.Attribute("id"),
coolDown = (int)x.Element("cooldown")
}).ToList();
}
}
}
I would not use XMLDocument for querying purposes IMHO. I find XDocument to be an easier extension off of System.Xml.Linq;
EG:
using System;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleTester
{
class Program
{
static void Main(string[] args)
{
//Here I am mocking your file statically, you could iterate here through a file system.
var xml =
"<?xml version=\"1.0\"?><movement><skill id = \"2\"><cooldown>5</cooldown></skill><skill id = \"3\"><cooldown>10</cooldown></skill></movement>";
//Just parse the file from the text obtained from a StreamReader or similar.
var xdoc = XDocument.Parse(xml);
//Chain the events of finding the node(s) you want with 'Elements' then continuing on to more, then you want an Attribute and not a node. Then select it's value.
xdoc.Elements("movement").Elements("skill").Attributes("id")
.Select(x => x.Value)
.ToList()
.ForEach(x => Console.WriteLine(x));
Console.ReadLine();
}
}
}
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());
}
}
}
Sample XML:
<Response xmlns="http://tempuri.org/">
<Result xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:string>18c03787-9222-4c9b-8f39-44c2b39c788e</a:string>
<a:string>774d38d2-a350-4711-8674-b69404283448</a:string>
</Result>
</Response>
When I attempt to parse this code I'm getting back null, i.e:
XNamespace temp = "http://tempuri.org/";
XDocument xdoc = XDocument.Parse(xml);
All the following situations return null:
xdoc.Descendants(temp + "Result")
xdoc.Descendants();
xdoc.Element(temp + "Result");
What am I misunderstanding?
****** EDIT **********
Sorry for wasting everyone's time.
It appears I was using http://www.tempuri.org instead of http://tempuri.org and incorrectly listed the proper one in my question.
Here is how you can pull out the values in a couple of clear steps:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace WaitForIt
{
class Program
{
static void Main(string[] args)
{
string thexml = #"<Response xmlns=""http://tempuri.org/""><Result xmlns:a=""http://schemas.microsoft.com/2003/10/Serialization/Arrays"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><a:string>18c03787-9222-4c9b-8f39-44c2b39c788e</a:string><a:string>774d38d2-a350-4711-8674-b69404283448</a:string></Result></Response>";
XDocument doc = XDocument.Parse(thexml);
XNamespace ns = "http://tempuri.org/";
var result = doc.Descendants(ns + "Result");
var resultStrings = result.Elements();
foreach (var el in resultStrings)
{
Debug.WriteLine(el.Value);
}
// output:
// 18c03787-9222-4c9b-8f39-44c2b39c788e
// 774d38d2-a350-4711-8674-b69404283448
}
}
}
I found some examples stackoverflow of using a xml to linq parser. My xml has a namespace, so I tried setting that as well (though I would of thought you could read that in a from the file?)
Anyway, when I run the c#/linq code it does not recognise the elements in the xml. Unless I remove the xmlns tag from the xml file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
XDocument document = XDocument.Load("C:\\NewCredential.xml");
XNamespace ns = "http://myworld.org/CredCentral.xsd";
var usernames = from r in document.Descendants(ns + "Credential")
select r.Element("Username").Value;
foreach (var r in usernames)
{
Console.WriteLine(r.ToString());
}
Console.ReadLine();
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<CredCentral xmlns="http://myworld.org/CredCentral.xsd">
<Credential>
<CredentialId>123456789</CredentialId>
<Username>johnsmith</Username>
<Password>password</Password>
</Credential>
</CredCentral>
Any help would be appreciated, thank you kindly.
You need to specify the namespace with element name as well:
from r in document.Descendants(ns + "Credential")
select (string)r.Element(ns + "Username");
Also I recommend you use an explicit cast instead of using Value property that will prevent the possible exceptions.
You're almost there, you just have to include the namespace identifier for every element in your query. With this slight adjustment, your code works:
var usernames = from r in doc.Descendants(ns + "Credential")
select r.Element(ns + "Username").Value;