LINQ to XML without knowing the nodes - c#

I have this LINQ query:
XNamespace ns = NAMESPACE;
var items = (from c in doc.Descendants(ns +"Item")
select new Item
{
Title = c.Element(ns + "ItemAttributes").Element(ns + "Title").Value,
MFR = c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value,
Offer = c.Element(ns + "Offers").Element(ns + "TotalOffers").Value,
Amazon = c.Element(ns + "Offer").Element(ns + "Merchant").Elements(ns + "MerchantId"),
LowPrice = Convert.ToDouble(c.Element(ns + "FormattedPrice").Value),
SalesRank = Convert.ToInt32(c.Element(ns +"SalesRank").Value),
ASIN = c.Element(ns + "ASIN").Value
}).ToList<Item>();
It works great expect for when a node is not present. For example it my not have a MFR or a sales rank. How can I make it so if it does not have the node in question, it gives me a default value or at the very doesn't make me try catch my whole query for one item.

As far as I'm aware LINQ to XML doesn't support this. However I ran into this same mess in a project I was working on and created this extension for XElement to allow it. Maybe it could work for you:
public static XElement ElementOrDummy(this XElement parentElement,
XName name,
bool ignoreCase)
{
XElement existingElement = null;
if (ignoreCase)
{
string sName = name.LocalName.ToLower();
foreach (var child in parentElement.Elements())
{
if (child.Name.LocalName.ToLower() == sName)
{
existingElement = child;
break;
}
}
}
else
existingElement = parentElement.Element(name);
if (existingElement == null)
existingElement = new XElement(name, string.Empty);
return existingElement;
}
Basically it just checks to see if the element exists and if it doesn't it returns one with the same name and an empty value.

You can use XElement Explicit Conversion, e.g.:
(int?)c.Element(ns +"SalesRank")
Reference: http://msdn.microsoft.com/en-us/library/bb340386.aspx

if the problem that the XElement exists, but the value is blank? i.e.
<Item>
<ItemAttributes>
<Manufacturer></Manufacturer>
</ItemAttributes>
</Item>
then you can use the string.IsNullOrEmpty function
XNamespace ns = NAMESPACE;
var items = (from c in doc.Descendants(ns +"Item")
select new Item
{
MFR = if (string.IsNullOrEmpty(c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value)) ? "default value here" : c.Element(ns + "ItemAttributes").Element(ns + "Manufacturer").Value,
// omitted for brevity
}).ToList<Item>();

Related

Read XML File Using Linq is not reading element

I am not able to get the value from this xml response, I will appreciate any help.
<Response>
<Result>
<Item1>GREEN</Item1>
<Item2>05/19/2017 22:08:14</Item2>
</Result>
<Other>
<Id>xxxxxxxxxxxxc</Id>
</Other>
</Response>
What I tried so far but the results is empty
string responseXml = response.ToXML();
XElement doc = XElement.Load(new StringReader(responseXml));
var results = from p in
doc.Descendants("Result")
select new
{
item = p.Element("Item1").Value,
};
foreach (var elm in results)
{
Console.WriteLine(elm.item);
}
Use Parse instead of load. You may also be getting error due to extra characters in the string. In the string you postged there are single quotes. Not sure if the single quote is in the actual string you are using.
string responseXml = "<Response>" +
"<Result>" +
"<Item1>GREEN</Item1>" +
"<Item2>05/19/2017 22:08:14</Item2>" +
"</Result>" +
"<Other>" +
"<Id>xxxxxxxxxxxxc</Id>" +
"</Other>" +
"</Response>";
XElement doc = XElement.Parse(responseXml);
var results = from p in
doc.Descendants("Result")
select new
{
item = p.Element("Item1").Value,
};
foreach (var elm in results)
{
Console.WriteLine(elm.item);
}

C# XML - XPath Query Builder - Dynamically Create a Query

I am trying to build a XPath Query Builder in order to have a generic code as portable as possible.
So far, this is what I came up with :
private static string XpathQueryBuilder (string NodeName,string AttributeName = null, string AttributeValue = null)
{
string XpathAttr = "";
if (AttributeName != null)
if (AttributeValue != null)
XpathAttr = "[#" + AttributeName + "='" + AttributeValue + "']";
else
XpathAttr = "[#" + AttributeName + "='*']";
return "//" + NodeName + XpathAttr;
}
The problem I see with this method though is that if I have more than one attribute or node that I would like to look for, this function won't work. Is there a way to create an XPath Query dynamically that could theorically accept any number of attributes and/or Nodes.
My priority is on having a function that accepts more than one attribute and attribute value as this is the more likely case than more than one node.
Thank you for your time!
You can use Dictionary<string,string> to make the function capable of receiving multiple attributes parameter :
private static string XpathQueryBuilder(string nodeName, Dictionary<string,string> attributes = null)
{
string xpathAttr = "";
if (attributes != null)
{
xpathAttr =
"[" +
String.Join(" and ",
attributes.Select(o =>
{
var attrVal = o.Value ?? "*";
return "#" + o.Key + "='" + attrVal + "'";
})
) + "]";
}
return "//" + nodeName + xpathAttr;
}
example usage :
var node = "Root";
var attrs = new Dictionary<string, string>
{
{"foo", "bar"},
{"baz", null},
};
var result = XpathQueryBuilder(node, attrs);
Console.WriteLine(result);
dotnetfiddle demo
output :
//Root[#foo='bar' and #baz='*']
You can use LINQ to XML.
It will allow you select any data that you want.
Also, if you need more generic solution, you can try to implement your own LINQ Provider for that.
The second way is more complicated than the first one, but as a result you will have more generic solution that will provide access to your xml file by LINQ chains and expressions (lambda etc).
A few links with examples for help:
http://weblogs.asp.net/mehfuzh/writing-custom-linq-provider
http://fairwaytech.com/2013/03/writing-a-custom-linq-provider-with-re-linq/
http://jacopretorius.net/2010/01/implementing-a-custom-linq-provider.html
https://aashishkoirala.wordpress.com/2014/03/10/linq-provider-1/
Have you tried using LINQ to XML?
using System.Linq;
using System.Xml;
using System.Xml.Linq;
String GUID = "something";
XElement profilesXel = XElement.Load("your xml file path");
XElement currProfile = (from el in profilesXel
where (String)el.Element("GUID") == GUID
select el).First();
....

parsing an xmldocument from a webrequest

I'm having a heck of a time parsing this layout:
<string xmlns="http://www.namespaceuri.com/Admin/ws">
<CardTrxSummary>
<PaymentMethod>
<Payment_Type_ID>VISA </Payment_Type_ID>
<Authorization>0.0000</Authorization>
<Capture>0.0000</Capture> <ForceCapture>0.0000</ForceCapture>
<PostAuth>0.0000</PostAuth> <Return>0.0000</Return>
<Sale>3419.2700</Sale> <Receipt>0.0000</Receipt>
<RepeatSale>0.0000</RepeatSale>
<Activate>0.0000</Activate>
<Deactivate>0.0000</Deactivate>
<Reload>0.0000</Reload>
<Authorization_Cnt>0</Authorization_Cnt>
<Capture_Cnt>0</Capture_Cnt>
<ForceCapture_Cnt>0</ForceCapture_Cnt>
<PostAuth_Cnt>0</PostAuth_Cnt>
<Return_Cnt>0</Return_Cnt>
<Sale_Cnt>13</Sale_Cnt>
<Receipt_Cnt>0</Receipt_Cnt>
<RepeatSale_Cnt>0</RepeatSale_Cnt>
<Activate_Cnt>0</Activate_Cnt>
<Deactivate_Cnt>0</Deactivate_Cnt>
<Reload_Cnt>0</Reload_Cnt>
<Cnt>13</Cnt>
</PaymentMethod>
</CardTrxSummary>
</string>
I am trying with this code to get the a specific result:
private static string ReadValueFromXml(XmlDocument xmlDocument, string field)
{
var xdoc = xmlDocument.ToXDocument();
var ns = "http://www.namespaceuri.com/Admin/ws";
return xdoc.Descendants(ns + "PaymentMethod")
.Select(x => (string) x.Attribute("Cnt"))
.FirstOrDefault();
}
At this point, it's giving me this message:
The ':' character, hexadecimal value 0x3A, cannot be included in a
name.
I tried it this way:
XmlNodeList xnList = xmlDocument.SelectNodes("/CardTrxSummary/PaymentMethod");
foreach (XmlNode xn in xnList)
{
Console.WriteLine("Sale: " + xn["Sale"].InnerText);
Console.WriteLine("Sale_Cnt: " + xn["Sale_Cnt"].InnerText);
Console.WriteLine("Payment_Type_ID: " + xn["Payment_Type_ID"].InnerText);
}
And it never went inside the foreach.
How do I get the values within PaymentMethod?
EDIT
I looked at the xmldocument's innertext and this is how it's displayed:
"<?xml version=\"1.0\" encoding=\"utf-8\"?><string xmlns=\"http://www.namespaceuri.com/Admin/ws\"><CardTrxSummary>\r\n <PaymentMethod>\r\n <Payment_Type_ID>VISA </Payment_Type_ID>\r\n <Authorization>0.0000</Authorization>\r\n <Capture>0.0000</Capture>\r\n <ForceCapture>0.0000</ForceCapture>\r\n <PostAuth>0.0000</PostAuth>\r\n <Return>0.0000</Return>\r\n <Sale>3419.2700</Sale>\r\n <Receipt>0.0000</Receipt>\r\n <RepeatSale>0.0000</RepeatSale>\r\n <Activate>0.0000</Activate>\r\n <Deactivate>0.0000</Deactivate>\r\n <Reload>0.0000</Reload>\r\n <Authorization_Cnt>0</Authorization_Cnt>\r\n <Capture_Cnt>0</Capture_Cnt>\r\n <ForceCapture_Cnt>0</ForceCapture_Cnt>\r\n <PostAuth_Cnt>0</PostAuth_Cnt>\r\n <Return_Cnt>0</Return_Cnt>\r\n <Sale_Cnt>13</Sale_Cnt>\r\n <Receipt_Cnt>0</Receipt_Cnt>\r\n <RepeatSale_Cnt>0</RepeatSale_Cnt>\r\n <Activate_Cnt>0</Activate_Cnt>\r\n <Deactivate_Cnt>0</Deactivate_Cnt>\r\n <Reload_Cnt>0</Reload_Cnt>\r\n <Cnt>13</Cnt>\r\n </PaymentMethod>\r\n</CardTrxSummary></string>"
Which, I assume, is part of my problem?
EDIT#2
This is what I ended up doing to get it to work. I'm sure there is a better way:
var tst2 = tst.InnerText.Replace("<", "<").Replace(">", ">").Replace("\r\n", string.Empty);
Console.WriteLine("Cnt: " + ReadXmlValue1(tst2, "Cnt"));
and my method to parse it:
private static void ReadXmlValue1(string xmlDocument)
{
XDocument xdoc = XDocument.Parse(xmlDocument);
//XNamespace ns = "http://www.namespaceuri.com/Admin/ws";
var payments = from p in xdoc.Descendants("PaymentMethod")
select new
{
Sale = (decimal)p.Element("Sale"),
SaleCount = (int)p.Element("Sale_Cnt"),
PaymentType = (string)p.Element("Payment_Type_ID")
};
Console.WriteLine("Count: " + payments.Count());
foreach (var payment in payments)
{
Console.WriteLine("Sale: " + payment.Sale);
Console.WriteLine("Sale_Cnt: " + payment.SaleCount);
Console.WriteLine("Payment_Type_ID: " + payment.PaymentType);
}
}
EDIT#3
This is how I'm creating the xmldocument:
/// <summary>
/// Get Data in xml format by url
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static XmlDocument GetXmlDataFromUrl(string url)
{
//requesting the particular web page
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
//geting the response from the request url
var response = (HttpWebResponse)httpRequest.GetResponse();
//create a stream to hold the contents of the response (in this case it is the contents of the XML file
var receiveStream = response.GetResponseStream();
//creating XML document
var mySourceDoc = new XmlDocument();
//load the file from the stream
if (receiveStream != null)
{
mySourceDoc.Load(receiveStream);
//close the stream
receiveStream.Close();
return mySourceDoc;
}
return null;
}
You can use LINQ to XML to get list of strongly typed anonymous payment objects:
WebClient client = new WebClient();
string content = client.DownloadString(url);
XDocument xdoc = XDocument.Parse(content);
XNamespace ns = "http://www.namespaceuri.com/Admin/ws";
var payments = from p in xdoc.Descendants(ns + "PaymentMethod")
select new {
Sale = (decimal)p.Element(ns + "Sale"),
SaleCount = (int)p.Element(ns + "Sale_Cnt"),
PaymentType = (string)p.Element(ns + "Payment_Type_ID")
};
Keep in mind, that your xml has namespace declared, so you should provide it when specifying element names.
Usage:
foreach(var payment in payments)
{
Console.WriteLine("Sale: " + payment.Sale);
Console.WriteLine("Sale_Cnt: " + payment.SaleCount);
Console.WriteLine("Payment_Type_ID: " + payment.PaymentType);
}
XmlNode node = xmlDocument.SelectSingleNode("/string/CardTrxSummary/PaymentMethod");
Console.WriteLine("Sale: " + node.SelectSingleNode("Sale").InnerText);
Console.WriteLine("Sale_Cnt: " + node.SelectSingleNode("Sale_Cnt").InnerText);
Console.WriteLine("Payment_Type_ID: " + node.SelectSingleNode("Payment_Type_ID").InnerText);
or you can use getElementByTagName instead;
Console.WriteLine("Sale: " + xmlDocument.getElementByTagName("Sale").InnerText);
Console.WriteLine("Sale_Cnt: " + xmlDocument.getElementByTagName("Sale_Cnt").InnerText);
Console.WriteLine("Payment_Type_ID: " + xmlDocument.getElementByTagName("Payment_Type_ID").InnerText);
And just a note, the above 2 method is assuming that the tag will never return null.
If you want to handle possible null node, you can do something like this.
string text = xmlDocument.getElementByTagName("Sale") != null ? xmlDocument.getElementByTagName("Sale").InnerText : "unidentified";
The above line has the format like this:
var variable = condition ? A : B;
It's basically saying that if condition is true, variable equals A, otherwise variable equals B.

getting a specific value using linq to xml

I'm working on windows 8 metro apps and i need to get some information from a XML file.
i parse it with LINQ to XML but I've got a problem.
here is the XML:
<feed xmlns="http://www.allocine.net/v6/ns/">
<page>1</page>
<count>1</count>
<results type="movie">10</results>
<results type="person">0</results>
<totalResults>10</totalResults>
<movie code="61282">
<originalTitle>Avatar</originalTitle>
<title>Avatar</title>
<productionYear>2009</productionYear>
<release>
<releaseDate>2010-09-01</releaseDate>
</release>
<castingShort>
<directors>James Cameron</directors>
<actors>Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang, Michelle Rodriguez</actors>
</castingShort>
<statistics>
<pressRating>4.33333</pressRating>
<userRating>4.31338</userRating>
</statistics>
<poster path="/medias/nmedia/18/78/95/70/19485155.jpg"
href="http://images.allocine.fr/medias/nmedia/18/78/95/70/19485155.jpg"/>
<linkList>
<link rel="aco:web"
href="http://www.allocine.fr/film/fichefilm_gen_cfilm=61282.html"/>
</linkList>
</movie>
</feed>
I need to get "code" value of the "movie" node and the "href" value of the node "link" but all the things i tried failed ...
You can consider that the beginning of the XML is <movie> because I get the file and i parse it to keep the XML clean as I want. my file start with <movie code="">
For a classic value like "actors" i do :
Actors = (string)query.Element("castingShort").Element("actors")
It is working perfectly! My problem is for the specific value with a name.
edit :
that's what i did with your advices.
var group1 = new SampleDataGroup("Group-1", "Films", "", "Assets/icone_groupe_all_movies.jpg", "Films sur le DD");
movieName = "avatar";
Uri = "http://api.allocine.fr/rest/v3/search?partner=YW5kcm9pZC12M3M&filter=movie,person&count=1&page=1&q=" + movieName + "&format=xml";
var httpResponse = await new HttpClient().GetAsync(Uri);
string sourceCode = get_new_xml(await httpResponse.Content.ReadAsStringAsync());
XDocument doc = XDocument.Parse(sourceCode);
XNamespace ns = "http://www.allocine.net/v6/ns/";
XElement movie = doc.Root.Element(ns + "movie");
XElement castingShort = movie.Element(ns + "castingShort");
XElement statistics = movie.Element(ns + "statistics");
Data data = new Data
{
MovieCode = (string)movie.Attribute("code"),
OriginalTitle = (string)movie.Element(ns + "originalTitle"),
Title = (string)movie.Element(ns + "title"),
ProductionYear = (string)movie.Element(ns + "productionYear"),
Directors = (string)castingShort.Element(ns + "directors"),
Actors = (string)castingShort.Element(ns + "actors"),
PressRating = (string)statistics.Element(ns + "pressRating"),
UserRating = (string)statistics.Element(ns + "userRating"),
Cover = (string)movie.Element(ns + "linkList").Element(ns + "link").Attribute("href")
};
group1.Items.Add(new SampleDataItem("Group-1-Item-1", data.Title, data.Cover, data.ProductionYear, "", data.ReleaseDate, group1));
this.AllGroups.Add(group1);
but unfortunately it still doesent work ...
Thus you have namespace declared in your xml, you should declare and initialize an XNamespace object, and to use it when specifying XName objects (arguments to Element methods):
XDocument xdoc = XDocument.Load(path_to_xml);
XNamespace ns = "http://www.allocine.net/v6/ns/";
XElement movie = xdoc.Root.Element(ns + "movie");
var code = (int)movie.Attribute("code");
var href = (string)movie.Element(ns + "linkList")
.Element(ns + "link").Attribute("href");
If you have only one <movie> element, then you don't need to operate on sequence of movie elements and treat single movie as list. Simply get movie node and create new data object via parsing that node:
XNamespace ns = "http://www.allocine.net/v6/ns/";
XElement movie = xdoc.Root.Element(ns + "movie");
XElement castingShort = movie.Element(ns + "castingShort");
XElement statistics = movie.Element(ns + "statistics");
Data data = new Data
{
MovieCode = (int)movie.Attribute("code"),
OriginalTitle = (string)movie.Element(ns + "originalTitle"),
Title = (string)movie.Element(ns + "title"),
ProductionYear = (string)movie.Element(ns + "productionYear"),
Directors = (string)castingShort.Element(ns + "directors"),
Actors = (string)castingShort.Element(ns + "actors"),
PressRating = (string)statistics.Element(ns + "pressRating"),
UserRating = (string)statistics.Element(ns + "userRating"),
Cover = (string)movie.Element(ns + "linkList")
.Element(ns + "link").Attribute("href")
};

Build XML file from XPathExpressions

I have a bunch of XPathExpressions that I used to read an XML file. I now need go the other way. (Generate an XML file based on the values I have.)
Here is an example to illustrate. Say I have a bunch of code like this:
XPathExpression hl7Expr1 = navigator.Compile("/ORM_O01/MSH/MSH.6/HD.1");
var hl7Expr2 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.18/CX.1");
var hl7Expr3 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/ORM_O01.PATIENT_VISIT/PV1/PV1.19/CX.1");
var hl7Expr4 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.3[1]/CX.1");
var hl7Expr5 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.5[1]/XPN.1/FN.1");
var hl7Expr6 = navigator.Compile("/ORM_O01/ORM_O01.PATIENT/PID/PID.5[1]/XPN.2");
string hl7Value1 = "SomeValue1";
string hl7Value2 = "SomeValue2";
string hl7Value3 = "SomeValue3";
string hl7Value4 = "SomeValue4";
string hl7Value5 = "SomeValue5";
string hl7Value6 = "SomeValue6";
Is there a way to take the hl7Expr XPathExpressions and generate an XML file with the corresponding hl7Value string in it?
Or maybe just use the actual path string to do the generation (instead of using the XPathExpression object)?
Note: I saw this question: Create XML Nodes based on XPath? but the answer does not allow for [1] references like I have on hl7Expr4.
I found this answer: https://stackoverflow.com/a/3465832/16241
And I was able to modify the main method to convert the [1] to attributes (like this):
public static XmlNode CreateXPath(XmlDocument doc, string xpath)
{
XmlNode node = doc;
foreach (string part in xpath.Substring(1).Split('/'))
{
XmlNodeList nodes = node.SelectNodes(part);
if (nodes.Count > 1) throw new ApplicationException("Xpath '" + xpath + "' was not found multiple times!");
else if (nodes.Count == 1) { node = nodes[0]; continue; }
if (part.StartsWith("#"))
{
var anode = doc.CreateAttribute(part.Substring(1));
node.Attributes.Append(anode);
node = anode;
}
else
{
string elName, attrib = null;
if (part.Contains("["))
{
part.SplitOnce("[", out elName, out attrib);
if (!attrib.EndsWith("]")) throw new ApplicationException("Unsupported XPath (missing ]): " + part);
attrib = attrib.Substring(0, attrib.Length - 1);
}
else elName = part;
XmlNode next = doc.CreateElement(elName);
node.AppendChild(next);
node = next;
if (attrib != null)
{
if (!attrib.StartsWith("#"))
{
attrib = " Id='" + attrib + "'";
}
string name, value;
attrib.Substring(1).SplitOnce("='", out name, out value);
if (string.IsNullOrEmpty(value) || !value.EndsWith("'")) throw new ApplicationException("Unsupported XPath attrib: " + part);
value = value.Substring(0, value.Length - 1);
var anode = doc.CreateAttribute(name);
anode.Value = value;
node.Attributes.Append(anode);
}
}
}
return node;
}

Categories