Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast XPathEvalute when it can be XElement or XAttribute?

Tags:

c#

.net

xml

xpath

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?

like image 711
Spindel Avatar asked Sep 30 '12 08:09

Spindel


3 Answers

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
like image 107
Dimitre Novatchev Avatar answered Nov 14 '22 08:11

Dimitre Novatchev


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;
    }
}
like image 26
Dmitry Fedorkov Avatar answered Nov 14 '22 07:11

Dmitry Fedorkov


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.

like image 7
NotSoFastMyFriend Avatar answered Nov 14 '22 09:11

NotSoFastMyFriend