I'm trying to work with LINQ to XML to parse the notifications I'm getting from Google Checkout.
The response is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<authorization-amount-notification xmlns="http://checkout.google.com/schema/2" serial-number="153286076708098-00005-6">
<authorization-amount currency="USD">60.0</authorization-amount>
<authorization-expiration-date>2011-07-03T21:27:48.000Z</authorization-expiration-date>
<avs-response>Y</avs-response>
<cvn-response>M</cvn-response>
<timestamp>2011-06-26T21:28:48.741Z</timestamp>
<google-order-number>153286076708098</google-order-number>
<order-summary>
<total-chargeback-amount currency="USD">0.0</total-chargeback-amount>
<google-order-number>153286076708098</google-order-number>
<total-charge-amount currency="USD">0.0</total-charge-amount>
<total-refund-amount currency="USD">0.0</total-refund-amount>
<risk-information>
<ip-address>77.42.229.34</ip-address>
<billing-address>
<address1>somewhere in Beirut</address1>
<address2></address2>
<phone>70892555</phone>
<email>Technical#fisharwe.com</email>
<contact-name>Fisharwe User</contact-name>
<company-name></company-name>
<fax></fax>
<country-code>LB</country-code>
<city>Beirut</city>
<region></region>
<postal-code>1000</postal-code>
</billing-address>
<avs-response>Y</avs-response>
<cvn-response>M</cvn-response>
<eligible-for-protection>true</eligible-for-protection>
<partial-cc-number>1111</partial-cc-number>
<buyer-account-age>18</buyer-account-age>
</risk-information>
<authorization>
<authorization-amount currency="USD">60.0</authorization-amount>
<authorization-expiration-date>2011-07-03T21:27:48.000Z</authorization-expiration-date>
</authorization>
<purchase-date>2011-06-26T21:27:48.000Z</purchase-date>
<archived>false</archived>
<shopping-cart>
<items>
<item>
<item-name>Credits</item-name>
<item-description>Description</item-description>
<unit-price currency="USD">60.0</unit-price>
<quantity>1</quantity>
</item>
</items>
</shopping-cart>
<order-adjustment>
<merchant-codes />
<total-tax currency="USD">0.0</total-tax>
<adjustment-total currency="USD">0.0</adjustment-total>
</order-adjustment>
<promotions />
<buyer-id>975104325298289</buyer-id>
<buyer-marketing-preferences>
<email-allowed>false</email-allowed>
</buyer-marketing-preferences>
<buyer-shipping-address>
<address1>somewhere in Beirut</address1>
<address2></address2>
<phone>70892555</phone>
<email>Technical#fisharwe.com</email>
<contact-name>Fisharwe User</contact-name>
<company-name></company-name>
<fax></fax>
<structured-name>
<first-name>Fisharwe</first-name>
<last-name>User</last-name>
</structured-name>
<country-code>LB</country-code>
<city>Beirut</city>
<region></region>
<postal-code>1000</postal-code>
</buyer-shipping-address>
<order-total currency="USD">60.0</order-total>
<fulfillment-order-state>NEW</fulfillment-order-state>
<financial-order-state>CHARGEABLE</financial-order-state>
</order-summary>
</authorization-amount-notification>
Here's the code I'm using:
var serverResponse = _checkoutService.Post(data, GoogleCheckoutConstants.ReportsUri);
var xmlData = XDocument.Parse(serverResponse);
bool charged = false;
if(xmlData.Root.Name.Equals("authorization-amount-notification"))
{
var amount = (from c in xmlData.Elements()
where c.Name.Equals("authorization-amount")
select c).First().Value;
var googleNumber = (from c in xmlData.Elements()
where c.Name.Equals("google-order-number")
select c).First().Value;
_checkoutService.ChargeAndShip(googleNumber, amount);
charged = true;
}
This is the first time I use LINQ to XML, so I'm not really sure what's wrong with my code. But it's not even going inside the if statement. So when I replace the condition with:
if (serverResponse.IndexOf("authorization-amount-notification") > -1)
I end up getting errors telling me that the amount and googleNumber were not found.
Any suggestions?
You need to put the namespace in to the Xml, and you the Elements are SubElements of the Root Node.
You are only after one Element so doing Elements() then .First() is pointless. Just do Element() instead.
Also, you can match element names by passing in the Name of the Element + namespace to the Element() method.
var xmlData = XDocument.Parse(xml);
XNamespace ns = "http://checkout.google.com/schema/2";
if (xmlData.Root.Name == ns + "authorization-amount-notification")
{
var amount =
xmlData
.Root
.Element(ns + "authorization-amount")
.Value;
var googleNumber =
xmlData
.Root
.Element(ns + "google-order-number")
.Value;
_checkoutService.ChargeAndShip(googleNumber, amount);
charged = true;
}
What about...
if(xmlData.Root.Name.LocalName.Equals("new-order-notification")){
....
}
But the xml you posted doesn't seem to match the code your using.. The elements do not exist
Related
Here it gets the XML document and individual nodes, and inserts the nodes into a dictionary.
//create the xml document obj
XmlDocument inputXMLDoc = new XmlDocument();
fileref.isValid = false;
//load the xml document
#region
try
{
inputXMLDoc.XmlResolver = null;
inputXMLDoc.Load( strfile );//load the xml file
string input = inputXMLDoc.OuterXml;//get the string
Console.WriteLine( "success,loaded XML" );
logger.Log( "loaded xml:" + strfile );
fileref.importList = new Dictionary<string, XmlNode>();
nodeNames = new List<string> { "OrderId", "CustomerId", "CustomerName", "Addresses", "OrderStatus", "DateOrdered", "PaymentTime", "IncludeVAT", "OrderTotalIncVat", "OrderTotalVat", "Currency", "TypeOfSaleId" };
try
{
int i = 0;
foreach( string name in nodeNames )
{
Console.WriteLine( "Adding xml node " + name );
if( inputXMLDoc.GetElementsByTagName( name ) != null )
{
XmlNodeList xlist = inputXMLDoc.GetElementsByTagName( name );
foreach( XmlNode node in xlist )
{
fileref.importList.Add( name, node );
//add individual node within nodelist
Console.WriteLine( name );
}
} //add specified node from XML doc
else
{
nodeNames.RemoveAt( i );
}
i++;
}
}
}
Later, the nodes are accessed to save the information to a web service. However, nodes with child nodes within are not showing up this way.
Invoices.Address address = new Invoices.Address();
XmlNodeList oNodeList = fileref.importList["Addresses"].SelectNodes("/Delivery/Street");
foreach (XmlNode xn in oNodeList)
{
address.Street = xn.InnerText;
}
Sample XML document
<?xml version="1.0" encoding="utf-8"?>
<InvoiceOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OrderId xmlns="http://24sevenOffice.com/webservices">35</OrderId>
<CustomerId xmlns="http://24sevenOffice.com/webservices">21</CustomerId>
<CustomerName xmlns="http://24sevenOffice.com/webservices">James Bond</CustomerName>
<Addresses xmlns="http://24sevenOffice.com/webservices">
<Delivery>
<Street>11 Shewell Walk</Street>
<State />
<PostalCode>CO1 1WG</PostalCode>
<PostalArea>Essex</PostalArea>
<Name />
<City>Colchester</City>
<Country>UK</Country>
</Delivery>
<Invoice>
<Street>10 Shewell Walk</Street>
<State />
<PostalCode>CO1 1WG</PostalCode>
<PostalArea>Essex</PostalArea>
<Name />
<City>Colchester</City>
<Country>UK</Country>
</Invoice>
</Addresses>
<OrderStatus xmlns="http://24sevenOffice.com/webservices">Offer</OrderStatus>
<DateOrdered xmlns="http://24sevenOffice.com/webservices">2015-06-15T14:00:00Z</DateOrdered>
<PaymentTime xmlns="http://24sevenOffice.com/webservices">14</PaymentTime>
<IncludeVAT xsi:nil="true" xmlns="http://24sevenOffice.com/webservices" />
<OrderTotalIncVat xmlns="http://24sevenOffice.com/webservices">480.0000</OrderTotalIncVat>
<OrderTotalVat xmlns="http://24sevenOffice.com/webservices">80.0000</OrderTotalVat>
<Currency xmlns="http://24sevenOffice.com/webservices">
<Symbol>LOCAL</Symbol>
</Currency>
<TypeOfSaleId xmlns="http://24sevenOffice.com/webservices">-100</TypeOfSaleId>
<InvoiceRows xmlns="http://24sevenOffice.com/webservices">
<InvoiceRow>
<ProductId>18</ProductId>
<RowId>4665754</RowId>
<Price>400.0000</Price>
<Name>17" Laptop Screen</Name>
<DiscountRate>0.0000</DiscountRate>
<Quantity>7.0000</Quantity>
<Cost>0.0000</Cost>
<InPrice>0.0000</InPrice>
</InvoiceRow>
</InvoiceRows>
</InvoiceOrder>
The reason your code doesn't work is likely that you're ignoring the namespace of the elements you're looking for. There are many questions covering how to do that, such as this one.
That said, XmlDocument is a creaky old API and the newer LINQ to XML is a huge improvement - I'd suggest you look into that.
I'm also not sure the dictionary is pulling its weight for such a small number of elements. You can simply query what you need straight from the XML. For example, to get all your fields as typed values:
var doc = XDocument.Parse(strfile);
var order = doc.Elements("InvoiceOrder").Single();
XNamespace ns = "http://24sevenOffice.com/webservices";
var orderId = (int)order.Element(ns + "OrderId");
var customerId = (int)order.Element(ns + "CustomerId");
var customerName = (string)order.Element(ns + "CustomerName");
var orderStatus = (string)order.Element(ns + "OrderStatus");
var dateOrdered = (DateTime)order.Element(ns + "DateOrdered");
var paymentTime = (int)order.Element(ns + "PaymentTime");
var totalIncVat = (decimal)order.Element(ns + "OrderTotalIncVat");
var totalVat = (decimal)order.Element(ns + "OrderTotalVat");
var currency = (string)order.Elements(ns + "Currency").Elements(ns + "Symbol").SingleOrDefault();
var typeOfSaleId = (int)order.Element(ns + "TypeOfSaleId");
You can use a similar technique to get map your addresses to your strongly typed Address class:
var deliveryAddress = order.Elements(ns + "Addresses")
.Elements(ns + "Delivery")
.Select(e => new Invoice.Address
{
Street = (string)e.Element(ns + "Street"),
// ....
})
.Single();
The problem you have is with namespaces. If you specify the namespace for each of those elements then it seems to work. I came to this conclusion with a bit of googling and some experimentation so my explanation might not be spot on so I advise researching the issue further yourself to understand it correctly.
This code will work:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(inputXMLDoc.NameTable);
nsmgr.AddNamespace("ns", "http://24sevenOffice.com/webservices");
var oNodeList = importList["Addresses"].SelectNodes("//ns:Delivery/ns:Street",nsmgr);
The reason is (I think) that in your XML document you are specifying a default namespace for your elements (xmlns="http://24sevenOffice.com/webservices") and in your xpath you are not specifying that same namespace. In my code I create a namespace manager with that namespace in and prefix it to the two elements which it now considers to match the ones in your document that have these namespaces.
Can't get any result in feeds.
feedXML has the correct data.
XDocument feedXML = XDocument.Load(#"http://search.twitter.com/search.atom?q=twitter");
var feeds = from entry in feedXML.Descendants("entry")
select new
{
PublicationDate = entry.Element("published").Value,
Title = entry.Element("title").Value
};
What am I missing?
You need to specify the namespace:
// This is the default namespace within the feed, as specified
// xmlns="..."
XNamespace ns = "http://www.w3.org/2005/Atom";
var feeds = from entry in feedXML.Descendants(ns + "entry")
...
Namespace handling is beautifully easy in LINQ to XML compared with everything other XML API I've ever used :)
You need to specify a namespace on both the Descendents and Element methods.
XDocument feedXML = XDocument.Load(#"http://search.twitter.com/search.atom?q=twitter");
XNamespace ns = "http://www.w3.org/2005/Atom";
var feeds = from entry in feedXML.Descendants(ns + "entry")
select new
{
PublicationDate = entry.Element(ns + "published").Value,
Title = entry.Element(ns + "title").Value
};
If you look at the XML returned by the HTTP request, you will see that it has an XML namespace defined:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" ...>
<id>tag:search.twitter.com,2005:search/twitter</id>
...
</feed>
XML is just like C#, if you use an element name with the wrong namespace, it is not considered to be the same element! You need to add the required namepsace to your query:
private static string AtomNamespace = "http://www.w3.org/2005/Atom";
public static XName Entry = XName.Get("entry", AtomNamespace);
public static XName Published = XName.Get("published", AtomNamespace);
public static XName Title = XName.Get("title", AtomNamespace);
var items = doc.Descendants(AtomConst.Entry)
.Select(entryElement => new FeedItemViewModel()
new {
Title = entryElement.Descendants(AtomConst.Title).Single().Value,
...
});
The issue is in feedXML.Descendants("entry"). This is returning 0 results
According to the documentation you need to put in a fully qualified XName
Good Day,
I am trying to query an XML document and have the following query:
XElement root = XElement.Load(#"..\..\Data.xml");
var entries = root.Descendants()
.Where(x => x.Name.LocalName == "Entry")
.ToList();
Console.WriteLine("There are {0} nodes...", entries.Count());
foreach (XElement v in entries)
{
Console.WriteLine(v.Value);
}
and this code works because it pulls the correct number of Entry nodes. The Entry
nodes look like:
<?xml version="1.0" encoding="UTF-8"?>
<Database xmlns="http://www.someurl.org/schemas">
<InfoFromRecord>
<BaseData>
<Entry>
<Date>2006-03-08</Date>XD
<Time>09:20:00</Time>
<EnteredBy>DNS</EnteredBy>
<TextEntry>Record 1</TextEntry>
</Entry>
<Entry>
<Date>2006-03-08</Date>
<Time>09:33:00</Time>
<EnteredBy>MW</EnteredBy>
<TextEntry>Record 2</TextEntry>
</Entry>
<Entry>
<Date>2006-03-08</Date>
<Time>08:58:00</Time>
<EnteredBy>BH</EnteredBy>
<TextEntry>Record 3</TextEntry>
</Entry>
</BaseData>
</InfoFromRecord>
</Database>
The problem is, I want to extract only the Date and Time, not all four fields.
Let's assume your entire XML file looks like this for a clear example:
<Entries>
<Entry>
<Date>2006-03-08</Date>
<Time>09:33:00</Time>
<EnteredBy>XX</EnteredBy>
<TextEntry>Test Data</TextEntry>
</Entry>
</Entries>
You could then do something like this:
var document = XDocument.Load(#"..\..\Data.xml");
var dateAndTimes =
from d in document.Root.Descendants("Entry")
select new
{
Date = d.Element("Date").Value,
Time = d.Element("Time").Value
};
From there, the dateAndTimes type will select an anonymous type of the Date and Time. You can change the anonymous type to be your own type, or something else.
EDIT: The problem is your xmlns. Change your code like so:
XNamespace namespc = "http://www.someurl.org/schemas";
var document = XDocument.Parse(xml);
var dateAndTimes =
from d in document.Root.Descendants(namespc + "Entry")
select new
{
Date = d.Element(namespc + "Date").Value,
Time = d.Element(namespc + "Time").Value
};
I haven't had a chance to try it but something like the following may give you what you are looking for
var entries = from i in root.Descendants()
where Name=='entry'
let date = i.Element('Date').Value
let time = i.ELement('Time').Value
select new Tuple<string,string>(date,time);
foreach (XElement v in entries)
{
Console.WriteLine(v.Element("Date").Value);
Console.WriteLine(v.Element("Time").Value);
}
Do not forget that Descendants finds children at any level, i.e. children, grand-children, etc where Elements find only direct child. So i guess Elements is the safe option in most of the cases.
EDIT : After seeing the XML
You need to include the Namspace also when getting the data
XNamespace ns = "http://www.someurl.org/schemas";
var entries = elm.Descendants().Where(x => x.Name.LocalName == "Entry").ToList();
foreach (XElement v in entries)
{
Console.WriteLine(v.Element(ns+"Date").Value);
Console.WriteLine(v.Element(ns+"Time").Value);
}
IEnumerable<XElement> de = from el in xdoc.Descendants() select el;
foreach (XElement el in de)
{
if (string.Equals(el.Name.ToString(), "movie",
`StringComparison.InvariantCultureIgnoreCase))`enter code here`
StringComparison.InvariantCultureIgnoreCase))
{
date(el);
}
I'm trying to query out some information from a heavily namespaced XML document and am having some trouble finding attributes that are also namespaced.
The XML looks like:
<?xml version="1.0" encoding="utf-8" ?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:skos="http://www.w3.org/2004/02/skos/core#"
xmlns:geo="http://www.geonames.org/ontology#"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:void="http://rdfs.org/ns/void#">
<geo:Country rdf:about="http://ontologi.es/place/AD" skos:notation="AD" rdfs:label="Andorra" />
<geo:Country rdf:about="http://ontologi.es/place/AE" skos:notation="AE" rdfs:label="United Arab Emirates" />
<geo:Country rdf:about="http://ontologi.es/place/AF" skos:notation="AF" rdfs:label="Afghanistan" />
<geo:Country rdf:about="http://ontologi.es/place/AG" skos:notation="AG" rdfs:label="Antigua & Barbuda" />
<geo:Country rdf:about="http://ontologi.es/place/AI" skos:notation="AI" rdfs:label="Anguilla" />
<geo:Country rdf:about="http://ontologi.es/place/AL" skos:notation="AL" rdfs:label="Albania" />
...
</rdf:RDF>
My goal is to create a list of objects that have a country code and a country name. Here's what works for me now:
XmlReader reader = XmlReader.Create(#"path/to/xml.xml");
XDocument root = XDocument.Load(reader);
XmlNameTable nameTable = reader.NameTable;
XmlNamespaceManager nsManager = new XmlNamespaceManager(nameTable);
nsManager.AddNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
nsManager.AddNamespace("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
nsManager.AddNamespace("skos", "http://www.w3.org/2004/02/skos/core#");
nsManager.AddNamespace("geo", "http://www.geonames.org/ontology#");
var geoCountries =
from country in root.XPathSelectElements("./rdf:RDF/geo:Country", nsManager)
select new {
CountryCode = country.Attributes("{http://www.w3.org/2004/02/skos/core#}notation").First().Value,
CountryName = country.Attributes("{http://www.w3.org/2000/01/rdf-schema#}label").First().Value
};
This works fine, but I'd like to find the attributes using the namespace aliases, not the namespace URI (just because), or at least be able to lookup the URI using the alias. To try the latter idea, I eventually figured out I could do this:
country.Attributes(nsManager.LookupNamespace("skos") + "notation").First().Value
But I get an XmlException: The ':' character, hexadecimal value 0x3A, cannot be included in a name.
So then I tried:
country.Attributes("{" + nsManager.LookupNamespace("skos") + "}notation").First().Value
And then it works, but just seems like there could or should be an easier way, or rather, the {namespace}attribute syntax seems silly to me, like something that might be abstracted away in the framework.
So with all that, are there any shortcuts or easier ways to look up namespaced attributes?
I'd appreciate any feedback. Thanks!
using Linq to xml
XNamespace skos = XNamespace.Get("http://www.w3.org/2004/02/skos/core#");
XNamespace geo = XNamespace.Get("http://www.geonames.org/ontology#");
XNamespace rdfs = XNamespace.Get("http://www.w3.org/2000/01/rdf-schema#");
XDocument rdf = XDocument.Load(new StringReader(xmlstr));
foreach(var country in rdf.Descendants(geo + "Country"))
{
Console.WriteLine(
country.Attribute(skos + "notation").Value + " " +
country.Attribute(rdfs + "label").Value );
}
You can use System.Linq:
country.Attributes().Where(a => a.Name.LocalName == "notation")?.First()?.Value;
I'm getting the following block of xml back from a web service:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfItemResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ItemResponse>
<Item xmlns="http://www.xyz.com/ns/2006/05/01/webservices/abc/def">
<RequestKey Name="ESM.PA" Service="" />
<QoS>
<TimelinessInfo Timeliness="REALTIME" TimeInfo="0" />
<RateInfo Rate="TIME_CONFLATED" TimeInfo="10" />
</QoS>
<Status>
<StatusMsg>OK</StatusMsg>
<StatusCode>0</StatusCode>
</Status>
<Fields>
<Field DataType="Utf8String" Name="DSPLY_NAME">
<Utf8String>D15 |ASDFDSAA ETF </Utf8String>
</Field>
</Fields>
</Item>
</ItemResponse>
</ArrayOfItemResponse>
I'm trying to capture the Status element in an object as follows, but it's not working.
var _xml = XDocument.Parse(xmlFromService);
var stat = _xml
.Descendants("ArrayOfItemResponse")
.Descendants("ItemResponse")
.Descendants("Item")
.Descendants("Status");
What's the best way for me to get this element?
If you want to use System.Xml.Linq, you can use this expression:
var stat = (from n in _xml.Descendants() where n.Name.LocalName == "Status" select n).ToList();
You are not able to get the required results with your existing code because of the xmlns attribute in Item
<Item xmlns="http://www.xyz.com/ns/2006/05/01/webservices/abc/def">
This question addresses the problem you are actually facing. If you don't know the namespace then you should take a look at this question.
I don't know the best way, but you can read it like this
XmlDocument xdoc = new XmlDocument();
xdoc.Load(stream);
var statMsg = xdoc.GetElementsByTagName("StatusMsg")[0].InnerText;
var statCode = xdoc.GetElementsByTagName("StatusCode")[0].InnerText;
use xpath, something like
var stat = _xml.SelectSingleNode(#"ArrayOfItemResponse/ItemResponse/ItemStatus/StatusCode").Value;
that should put the value 0 into stat.
Your xml code use Namespace.
XNamespace ns = "http://www.xyz.com/ns/2006/05/01/webservices/abc/def";
var stat = _xml.Descendants(ns + "Status");