So I have this code:
List<PriceDetail> prices =
(from item in xmlDoc.Descendants(shop.DescendantXName)
select new PriceDetail
{
Price = GetPrice(item.Element(shop.PriceXPath).Value),
GameVersion = GetGameVersion(((IEnumerable)item.XPathEvaluate(shop.TitleXPath)).Cast<XAttribute>().First<XAttribute>().Value, item.Element(shop.PlatformXPath).Value),
Shop = shop,
Link = item.Element(shop.LinkXPath).Value,
InStock = InStock(item.Element(shop.InStockXPath).Value)
}).ToList<PriceDetail>();
The problem I have is this code:
((IEnumerable)item.XPathEvaluate(shop.TitleXPath)).Cast<XAttribute>().First<XAttribute>().Value
Sometimes the object from XPathEvaluate could be XElement and then the casting doesn't work. So what I need is a Cast that works with both XAttribute and XElement.
Any suggestion?
Change your XPath expression (shop.TitleXPath
) from:
someXPathExpression
to:
string(someXPathExpression)
Then you can simplify the code to just:
string result = item.XPathEvaluate(shop.TitleXPath) as string;
Complete working example:
using System;
using System.IO;
using System.Xml.Linq;
using System.Xml.XPath;
class TestXPath
{
static void Main(string[] args)
{
string xml1 =
@"<t>
<a b='attribute value'/>
<c>
<b>element value</b>
</c>
<e b='attribute value'/>
</t>";
string xml2 =
@"<t>
<c>
<b>element value</b>
</c>
<e b='attribute value'/>
</t>";
TextReader sr = new StringReader(xml1);
XDocument xdoc = XDocument.Load(sr, LoadOptions.None);
string result1 = xdoc.XPathEvaluate("string(/*/*/@b | /*/*/b)") as string;
TextReader sr2 = new StringReader(xml2);
XDocument xdoc2 = XDocument.Load(sr2, LoadOptions.None);
string result2 = xdoc2.XPathEvaluate("string(/*/*/@b | /*/*/b)") as string;
Console.WriteLine(result1);
Console.WriteLine(result2);
}
}
When this program is executed, the same XPath expression is applied on two different XML documents and, regardless of the fact that the argument to string()
is an attribute the first time and is an element on the second, we get the correct results -- written to the Console:
attribute value
element value
Dimitre's solution returns empty string if the element is not found; we can't distinguish it from actual empty value. So I had to make this extension method that handles multiple results by XPath query and returns empty enumeration if nothing is found:
public static IEnumerable<string> GetXPathValues(this XNode node, string xpath)
{
foreach (XObject xObject in (IEnumerable)node.XPathEvaluate(xpath))
{
if (xObject is XElement)
yield return ((XElement)xObject).Value;
else if (xObject is XAttribute)
yield return ((XAttribute)xObject).Value;
}
}
XElement
and XAttribute
are both forms of XObject
, so if a generic instance of type XObject
will suffice for your needs, change your Cast<XAttribute>
to Cast<XObject>
.
If that won't work for your specific situation, you make use of OfType<XAttribute>
or OfType<XElement>
to filter for one or the other, but that would require two passes over the input, one to filter for XElement
and a second pass to filter for XAttribute
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With