Home > Enterprise >  C# XPath cannot find any element
C# XPath cannot find any element

Time:08-28

I am able to read XML from this service at localhost, and it looks like this:

<?xml version="1.0" encoding="utf-8"?>
<GetEventsResponse
    xmlns="http://tempuri.org/">
    <GetEventsResult
        xmlns:d2p1="http://schemas.datacontract.org/2004/07/Zadatak3.Models"
        xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d2p1:Event>
            <d2p1:EndDate>2022-08-02T00:00:00</d2p1:EndDate>
            <d2p1:Id>1</d2p1:Id>
            <d2p1:Info></d2p1:Info>
            <d2p1:Name>Party u Močvari</d2p1:Name>
            <d2p1:Price>50</d2p1:Price>
            <d2p1:Promoter>Močvara Promotions</d2p1:Promoter>
            <d2p1:StartDate>2022-08-01T00:00:00</d2p1:StartDate>
            <d2p1:Type>Party</d2p1:Type>
            <d2p1:Url>http://mocvara.com/event/1</d2p1:Url>
            <d2p1:Venue>
                <d2p1:Address>Neka adresa</d2p1:Address>
                <d2p1:City>Zagreb</d2p1:City>
                <d2p1:ContactName>Pero</d2p1:ContactName>
                <d2p1:Country>Hrvatska</d2p1:Country>
                <d2p1:Id>1</d2p1:Id>
                <d2p1:Name>Močvara</d2p1:Name>
                <d2p1:PostalCode>10000</d2p1:PostalCode>
            </d2p1:Venue>
        </d2p1:Event>
        <d2p1:Event>
            <d2p1:EndDate>2022-08-11T00:00:00</d2p1:EndDate>
            <d2p1:Id>2</d2p1:Id>
            <d2p1:Info></d2p1:Info>
            <d2p1:Name>Koncert u Skwhatu</d2p1:Name>
            <d2p1:Price>80</d2p1:Price>
            <d2p1:Promoter>Kištra</d2p1:Promoter>
            <d2p1:StartDate>2022-08-10T00:00:00</d2p1:StartDate>
            <d2p1:Type>Koncert</d2p1:Type>
            <d2p1:Url>http://swkwhat.com/event/2</d2p1:Url>
            <d2p1:Venue>
                <d2p1:Address>Viktorovac 1</d2p1:Address>
                <d2p1:City>Sisak</d2p1:City>
                <d2p1:ContactName>Ivica</d2p1:ContactName>
                <d2p1:Country>Hrvatska</d2p1:Country>
                <d2p1:Id>2</d2p1:Id>
                <d2p1:Name>Skwhat</d2p1:Name>
                <d2p1:PostalCode>44000</d2p1:PostalCode>
            </d2p1:Venue>
        </d2p1:Event>
        <d2p1:Event>
            <d2p1:EndDate>2022-09-04T00:00:00</d2p1:EndDate>
            <d2p1:Id>3</d2p1:Id>
            <d2p1:Info></d2p1:Info>
            <d2p1:Name>Festival u Močvari</d2p1:Name>
            <d2p1:Price>220</d2p1:Price>
            <d2p1:Promoter>Čvarci</d2p1:Promoter>
            <d2p1:StartDate>2022-09-01T00:00:00</d2p1:StartDate>
            <d2p1:Type>Festival</d2p1:Type>
            <d2p1:Url>http://mocvara.com/festival/1</d2p1:Url>
            <d2p1:Venue>
                <d2p1:Address>Neka adresa</d2p1:Address>
                <d2p1:City>Zagreb</d2p1:City>
                <d2p1:ContactName>Pero</d2p1:ContactName>
                <d2p1:Country>Hrvatska</d2p1:Country>
                <d2p1:Id>1</d2p1:Id>
                <d2p1:Name>Močvara</d2p1:Name>
                <d2p1:PostalCode>10000</d2p1:PostalCode>
            </d2p1:Venue>
        </d2p1:Event>
    </GetEventsResult>
</GetEventsResponse>

This is the soap request that I'm sending and the app manages to read the query:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <s:Body>
        <SearchXmlResponse xmlns="http://tempuri.org/">
            <SearchXmlResult>/GetEventsResponse/GetEventsResult/d2p1:Event/d2p1:Name</SearchXmlResult>
        </SearchXmlResponse>
    </s:Body>
</s:Envelope>

But when I try to select single node, XPath returns nothing.

Code in the app looks like this:

public string SearchXml(string query)
{
    //

    //http://localhost:5068/EventService.asmx/

    //<?xml version="1.0" encoding="utf-8"?>
    //<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    //<soap:Body>
    //<SearchXml xmlns="http://www.w3.org/2001/XMLSchema">
    //<query>/GetEventsResponse/GetEventsResult/d2p1:Event[1]/d2p1:Name</query>
    //</SearchXml>
    //</soap:Body>
    //</soap:Envelope>

    const string serviceUrl = "http://localhost:5068/EventService.asmx/GetEvents";
    XmlDocument document = new XmlDocument();
    document.Load(serviceUrl);

    var nsmgr = new XmlNamespaceManager(document.NameTable);

    nsmgr.AddNamespace("d2p1", "http://schemas.datacontract.org/2004/07/Zadatak3.Models");
    nsmgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
    nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    nsmgr.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema");
    nsmgr.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance");

    try
    {
        XmlNode? node = document.SelectSingleNode(query, nsmgr);
        if (node != null)
        {
            return node.InnerText;
        }
        else
        {
            return $"Data does not exist! Search term: {query}";
        }
    }
    catch (Exception e)
    {
        return $"{e.Message}";
        throw;
    }
}

I tried different queries, selecting multiple nodes, but it does not fetch any results

CodePudding user response:

Your problem here is that the top two elements are in a default namespace xmlns="http://tempuri.org/":

<GetEventsResponse
    xmlns=""http://tempuri.org/"">
    <GetEventsResult>
        <!--Contents omitted -->
    </GetEventsResult>
</GetEventsResponse>

Which is semantically equivalent to e.g.:

<def:GetEventsResponse
    xmlns:def=""http://tempuri.org/"">
    <def:GetEventsResult>
        <!--Contents omitted -->
    </def:GetEventsResult>
</def:GetEventsResponse>

Thus you must add this default namespace into the XmlNamespaceManager with some assigned prefix in order to query for these nodes by qualified name. Your caller may then use that assigned prefix in their query.

E.g. if you assign the prefix tempuri to http://tempuri.org/:

nsmgr.AddNamespace("tempuri", "http://tempuri.org/");

Then the following query by the caller will now work:

/tempuri:GetEventsResponse/tempuri:GetEventsResult/d2p1:Event/d2p1:Name

Alternatively, the caller's query could specify the namespace URI directly, rather than via some namespace prefix, by using the local-name() and namespace-uri() XPath functions:

/*[local-name() = 'GetEventsResponse' and namespace-uri() = 'http://tempuri.org/']/*[local-name() = 'GetEventsResult' and namespace-uri() = 'http://tempuri.org/']/d2p1:Event/d2p1:Name

Demo fiddle here.

Notes:

  • Attempting to query a node in a default namespace without using prefix by adding the default namespace to the XmlNamespaceManager with an empty string "" prefix seems not to work. Even if I do

     nsmgr.AddNamespace("", "http://tempuri.org/");
    

    Your initial query still fails.

    Demo fiddle #2 here.

  • You might want to reconsider your API design to allow the caller to specify the namespaces they want to query, e.g. by adding a List<SerializableKeyValuePair<string, string>> namespaces argument to your method, where SerializableKeyValuePair comes from e.g. here. This makes it the responsibility of the caller to specify the namespaces correctly, rather that the responsibility of your your server-side app to hardcode all the necessary prefixes.

  • Related