I'm trying to read some XML:
<?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='2015-02-23'>
<Cube currency='USD' rate='1.1298'/>
<Cube currency='JPY' rate='134.50'/>
<Cube currency='BGN' rate='1.9558'/>
<Cube currency='CZK' rate='27.444'/>
</Cube>
</Cube>
</gesmes:Envelope>
I'm parsing that stuff with following code:
var path = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
string xml;
using (var wc = new WebClient())
{
xml = wc.DownloadString(path);
}
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDoc.NameTable);
mgr.AddNamespace("n", "http://www.gesmes.org/xml/2002-08-01");
1.) XmlNodeList cubes = xmlDoc.SelectNodes("/n:Envelope", mgr); //WORKS
2.) XmlNodeList cubes = xmlDoc.SelectNodes("/n:Envelope/Cube", mgr); //DOES NOT WORK
foreach (XmlNode c in cubes)
{
// whatever
}
When I open the Envelope-node (1.), it works.
But I have no idea, how to access the sub-nodes within the namespace-node (2.). That code runs, but returns no result. How to access that?
There's a default namespace declared in the Envelope-element. Any descendant of that element that doesn't have an explicit namespace declared will have the default namespace.
XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDoc.NameTable);
mgr.AddNamespace("n", "http://www.gesmes.org/xml/2002-08-01");
mgr.AddNamespace("d", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
XmlNodeList cubes = xmlDoc.SelectNodes("/n:Envelope/d:Cube", mgr);
will work. Note the added namespace to stand in for the default one, both in the manager and in the xpath. If you inspect the result of that, you will find
<Cube xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<Cube time="2015-02-23">
<Cube currency="USD" rate="1.1298" />
<Cube currency="JPY" rate="134.50" />
<Cube currency="BGN" rate="1.9558" />
<Cube currency="CZK" rate="27.444" />
</Cube>
</Cube>
which clearly states there's a default namespace belonging to the outermost Cube element which is, in this xml fragment, the root.
Related
This is my xml
<DataSet xmlns="http://www.bnr.ro/xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bnr.ro/xsd nbrfxrates.xsd">
<Header>
<Publisher>National Bank of Romania</Publisher>
<PublishingDate>2016-03-24</PublishingDate>
<MessageType>DR</MessageType>
</Header>
<Body>
<Subject>Reference rates</Subject>
<OrigCurrency>RON</OrigCurrency>
<Cube date="2016-03-24">
<Rate currency="EUR">4.4655</Rate>
</Cube>
<Cube date="2016-03-23">
Rate currency="EUR">4.4641</Rate>
</Cube>
</Body>
</DataSet>
I want to verify the Cube Attribute date to receive the EUR value from yesterday date.
For example if today is 2016-03-24 I want to receive the value 4.4641 from 2016-03-23.
I tried with LINQ to XML
string date_yesterday = DateTime.Now.AddDays(-1).Date.ToString("yyyy-MM-dd");
XElement root = XElement.Parse(sbXmlText.ToString());
IEnumerable<XElement> adress =
from el in root.Descendants("Cube")
let z = el.ElementsAfterSelf().FirstOrDefault()
where z != null && (string)el.Attribute("date") == date_yesterday
select el;
foreach (XElement el in adress)
Console.WriteLine(el);
And tried
string date_yesterday = DateTime.Now.AddDays(-1).Date.ToString("yyyy-MM-dd");
XElement root = XElement.Parse(sbXmlText.ToString());
IEnumerable<XElement> adress =
root.Descendants("Cube").Where(r => r.Attribute("date").Value == date_yesterday);
foreach (XElement el in adress)
Console.WriteLine(el);
And it returns everytime null
Your XML has default namespace. You can use "XNamespace+element's local-name" to reference element in namespace, for example :
var xml = #"<DataSet xmlns='http://www.bnr.ro/xsd'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation='http://www.bnr.ro/xsd nbrfxrates.xsd'>
<Header>
<Publisher>National Bank of Romania</Publisher>
<PublishingDate>2016-03-24</PublishingDate>
<MessageType>DR</MessageType>
</Header>
<Body>
<Subject>Reference rates</Subject>
<OrigCurrency>RON</OrigCurrency>
<Cube date='2016-03-24'>
<Rate currency='IDR'>1.1111</Rate>
<Rate currency='EUR'>4.4655</Rate>
</Cube>
<Cube date='2016-03-23'>
<Rate currency='EUR'>4.4641</Rate>
</Cube>
</Body>
</DataSet>";
var doc = XDocument.Parse(xml);
//XNamespace that reference default namespace URI:
XNamespace d = "http://www.bnr.ro/xsd";
var yesterday = DateTime.Now.AddDays(-1).Date;
//Use `XNamespace+element's local-name` to reference element in namespace:
var result = (from cube in doc.Descendants(d+"Cube")
from rate in cube.Elements(d+"Rate")
where
((DateTime)cube.Attribute("date")).Date == yesterday
&&
(string)rate.Attribute("currency") == "EUR"
select (decimal)rate
).FirstOrDefault();
Console.WriteLine(result);
output :
4.4641
my Code looks something like this, I want to print the value of the currency but i donot know where i am wrong
XmlDocument xdoc = new XmlDocument();
xdoc.Load("filepath");
XmlNodeList nodes = xdoc.SelectNodes("//gesmes/gesmes");
foreach (XmlNode node in nodes)
{
Console.WriteLine(node["currency"]);
}
My Xml document looks like this
<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="2014-07-21">
<Cube currency="USD" rate="1.3518"/>
<Cube currency="JPY" rate="136.97"/>
<Cube currency="BGN" rate="1.9558"/>
</Cube>
</Cube>
</gesmes:Envelope>
there are multiple things wrong with your code:
Namespaces
you have to add a namespace manager and add the namespaces defined in your xml
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
nsmgr.AddNamespace("lo", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
notice how I added the default namespace with the lo alias to be able to query it using XPath later on
XPath
what are you trying to select?
gesmes is a namespace in your document not a node you can select. From your question I guess you want to select Cubes containing the currency attribute like so:
XmlNodeList nodes = xdoc.SelectNodes("//lo:Cube[#currency]", nsmgr);
notice that you need to include the namespace manager
Value
the value you are looking for is not a Node Value like
<Cube currency="USD">1.3518</Cube>
would be but an attribute value
select it using
node.Attributes["currency"].Value;
put together
XmlDocument xdoc = new XmlDocument();
xdoc.Load("filepath");
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
nsmgr.AddNamespace("lo", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
XmlNodeList nodes = xdoc.SelectNodes("//lo:Cube[#currency]", nsmgr);
foreach (XmlNode node in nodes)
{
Console.WriteLine(node.Attributes["rate"].Value);
}
Console.ReadKey();
This question already has answers here:
XDocument or XmlDocument
(7 answers)
Closed 10 years ago.
Unable to read this xml in c#. How do I get the Currency and rate from this xml
I used this code
XmlTextReader xmlTextReader = new XmlTextReader(new StringReader(xmlfile));
XmlDocument doc = new XmlDocument();
XmlNode node = doc.ReadNode(xmlTextReader);
but did not get the values.
<?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='2013-02-14'>
<Cube currency='USD' rate='1.3327'/>
<Cube currency='JPY' rate='124.39'/>
<Cube currency='BGN' rate='1.9558'/>
<Cube currency='CZK' rate='25.383'/>
<Cube currency='DKK' rate='7.4604'/>
<Cube currency='GBP' rate='0.85940'/>
<Cube currency='HUF' rate='292.52'/>
<Cube currency='LTL' rate='3.4528'/>
<Cube currency='LVL' rate='0.6997'/>
<Cube currency='PLN' rate='4.1765'/>
<Cube currency='RON' rate='4.3871'/>
<Cube currency='SEK' rate='8.4492'/>
<Cube currency='CHF' rate='1.2293'/>
<Cube currency='NOK' rate='7.3605'/>
<Cube currency='HRK' rate='7.5863'/>
<Cube currency='RUB' rate='40.1712'/>
<Cube currency='TRY' rate='2.3605'/>
<Cube currency='AUD' rate='1.2879'/>
<Cube currency='BRL' rate='2.6220'/>
<Cube currency='CAD' rate='1.3343'/>
<Cube currency='CNY' rate='8.3062'/>
<Cube currency='HKD' rate='10.3352'/>
<Cube currency='IDR' rate='12873.40'/>
<Cube currency='ILS' rate='4.9036'/>
<Cube currency='INR' rate='71.8730'/>
<Cube currency='KRW' rate='1445.93'/>
<Cube currency='MXN' rate='16.9523'/>
<Cube currency='MYR' rate='4.1170'/>
<Cube currency='NZD' rate='1.5715'/>
<Cube currency='PHP' rate='54.251'/>
<Cube currency='SGD' rate='1.6491'/>
<Cube currency='THB' rate='39.728'/>
<Cube currency='ZAR' rate='11.8588'/>
</Cube>
</Cube>
You can always do the XML read using XPath but I strongly recommend you using LINQ to XML (.NET 3.5) if at all possible:
XDocument doc = XDocument.Load(url);
XNamespace gesmes = "http://www.gesmes.org/xml/2002-08-01";
XNamespace ns = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";
var cubes = doc.Descendants(ns + "Cube")
.Where(x => x.Attribute("currency") != null)
.Select(x => new { Currency = (string) x.Attribute("currency"),
Rate = (decimal) x.Attribute("rate") });
foreach (var result in cubes)
{
Console.WriteLine("{0}: {1}", result.Currency, result.Rate);
}
cubes stores your required values viz. Currency and Rate
In order to get the value or attribute of one node, you need to find this node first.
A traditional approach:
string query = "//Cube[#currency and #rate]";//select the cube node that has "currency" and "rate" attributes.
XmlNodeList nodes = doc.SelectNodes(query);
foreach (XmlNode node in nodes)
{
string currency = node.Attributes["currency"].Value;
string rate = node.Attributes["rate"].Value;
Console.WriteLine("currency: {0}, rate: {1}", currency, rate);
}
Hope it's helpful.
In P.S. of this posting you can find a C# code snippet, which uses XSLT transformation + RegEx matching to get currency rate value from an XML document. Please read my question inline quoted by
//+
...
//-
comment lines group.
Thank you.
P.S. Code:
string currencyCode = "RUB";
var xml = new StringReader(
#"<gesmes:Envelope
xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01'
xmlns{{EmptyNameSpace}} ='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='2012-06-27'>
<Cube currency='USD' rate='1.2478' />
<Cube currency='RUB' rate='41.1252' />
<Cube currency='ZAR' rate='10.4601' />
</Cube>
</Cube>
</gesmes:Envelope>"
.Replace("{{EmptyNameSpace}}", ":ns1")
//+
// I'd like to get this code working the same way as it does now
// producing
//
// Result: EUR/RUB => 41.1252
//
// output but when the above code line will be commented
// and the below code line uncommented
//-
//.Replace("{{EmptyNameSpace}}", "")
);
var xslt = new XmlTextReader(new StringReader(
#"<xsl:stylesheet version='2.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01'
xmlns:ns1 ='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'
>
<xsl:variable name='x1' select='gesmes:Envelope/Cube/Cube/Cube[#currency=""{0}""]' />
<xsl:template match='gesmes:Envelope'>
[<xsl:apply-templates select='$x1' />]
</xsl:template>
<xsl:template match='Cube'>
<xsl:value-of select='#rate' />
</xsl:template>
</xsl:stylesheet>".Replace("{0}", currencyCode)
));
var xDoc = new XPathDocument(xml);
var xTr = new System.Xml.Xsl.XslCompiledTransform();
xTr.Load(xslt) ;
StringBuilder sb = new StringBuilder();
StringWriter writer = new StringWriter(sb);
xTr.Transform(xDoc, null, writer);
string pattern = #"\[(?'name'[0-9]*\.[0-9]*)\]";
System.Console.WriteLine(
"Result: EUR/{0} => {1}",
currencyCode,
Regex.Match(sb.ToString(), pattern)
.Groups["name"].Value);
// Result: EUR/RUB => 41.1252
As far as I can tell from looking at that code, your problems is handling the default namespace in XPath:
As long as {{EmptyNameSpace}} is :ns1, everything's fine - in your Xml document, the namespaces http://www.gesmes.org/xml/2002-08-01 and http://www.ecb.int/vocabulary/2002-08-01/eurofxref with the prefixes gesmes and ns1 are defined; your Xml document uses identifiers such as <gesmes:Envelope> from gesmes and others, namely <Cube>, that are not associated with any namespace. In the XSLT code, you are using XPath expressions, such as (here shortened) gesmes:Envelope/Cube/Cube/Cube, which is fine, as this expression refers to an <Envelope> element from the namespace declared with the gesmes prefix, as well as to <Cube> elements without a namespace.
Once you set {{EmptyNameSpace}} to an empty string, this changes: Now, http://www.ecb.int/vocabulary/2002-08-01/eurofxref is the default namespace of your document. Hence, the <Cube> elements in your document are considered to belong to that namespace. Your XPath expression, however, still considers the <Cube> elements that appear in the XPath as having no namespace - they do not have any namespace prefix and XPath doesn't know about default namespaces.
As a solution, you should add namespace prefixes to your XPath expression - if the <Cube> elements belong to the http://www.ecb.int/vocabulary/2002-08-01/eurofxref namespace, your XPath should read like this:
gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube
(Of course, the other XPath expressions in your XSLT have to be adapted accordingly.)
Please let me know if you need any further explanations on this - or whether I've gotten on the wrong track with my assumptions.
Update: Here's what the XSLT should look like in order to work if the Xml document has the default namespace:
<xsl:stylesheet version="2.0" xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01' xmlns:ns1='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'>
<xsl:variable name='x1' select='gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube[#currency="RUB"]'/>
<xsl:template match='gesmes:Envelope'>
[<xsl:apply-templates select='$x1'/>]
</xsl:template>
<xsl:template match='ns1:Cube'>
<xsl:value-of select='#rate'/>
</xsl:template>
</xsl:stylesheet>
I'm reading a remote XML file and once the XML is loaded into an XMLDocument object I need to traverse through it and extract the values that my application requires. My code is as follows:
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load("http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml");
XmlNamespaceManager nsMan = new XmlNamespaceManager(xmlDocument.NameTable);
nsMan.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
nsMan.AddNamespace("", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
XmlNodeList xmlNodeList = xmlDocument.DocumentElement.SelectNodes("/gesmes:Envelope/Cube/Cube/Cube", nsMan);
HttpContext.Current.Response.Write("The numner of nodes is " + xmlNodeList.Count); //it's always zero
However the problem I'm getting is that XmlNodeList always returns zero nodes, whereas if I evaluate the XPath expression in XMLSpy I get the nodes I require.
For reference the XML looks like:
<?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='2011-07-27'>
<Cube currency='USD' rate='1.4446'/>
<Cube currency='GBP' rate='0.88310'/>
</Cube>
</Cube>
</gesmes:Envelope>
I want to return the Cube nodes for USD and GBP.
Any ideas you clever people?
Thanks
Al
While you definitely can work with namespaces and XPath in the XmlDocument API, I would strongly advise you to use LINQ to XML (.NET 3.5) if at all possible:
string url = "http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml";
XDocument doc = XDocument.Load(url);
XNamespace gesmes = "http://www.gesmes.org/xml/2002-08-01";
XNamespace ns = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";
var cubes = doc.Descendants(ns + "Cube")
.Where(x => x.Attribute("currency") != null)
.Select(x => new { Currency = (string) x.Attribute("currency"),
Rate = (decimal) x.Attribute("rate") });
foreach (var result in cubes)
{
Console.WriteLine("{0}: {1}", result.Currency, result.Rate);
}