5

I am using Xpath to attempt to parse and get data back from a SOAP Salesforce query response, I found the sample API request and response WSDL from this Salesforce Documentation.

My SOAP Salesforce API request looks like the following

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:urn="urn:partner.soap.sforce.com" xmlns:urn1="urn:sobject.partner.soap.sforce.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:wd="http://www.workday.com">
    <soapenv:Header>
        <urn:SessionHeader>
            <urn:sessionId>
                <!-- Insert Some Session ID Here -->
            </urn:sessionId>
        </urn:SessionHeader>
    </soapenv:Header>
    <soapenv:Body>
        <urn:query>
            <urn:queryString>
                SELECT Id FROM Contact WHERE Name ='Booker Dewitt'
            </urn:queryString>
        </urn:query>
    </soapenv:Body>
</soapenv:Envelope>

The SOAP Salesforce API Response looks like the following

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:sf="urn:sobject.partner.soap.sforce.com">
    <soapenv:Header>
        <LimitInfoHeader>
            <limitInfo>
                <current>1523</current>
                <limit>6235000</limit>
                <type>API REQUESTS</type>
            </limitInfo>
        </LimitInfoHeader>
    </soapenv:Header>
    <soapenv:Body>
        <queryResponse>
            <result xsi:type="QueryResult">
                <done>true</done>
                <queryLocator xsi:nil="true" />
                <records xsi:type="sf:sObject">
                    <sf:type>Contact</sf:type>
                    <sf:Id>000000000001122334</sf:Id>
                    <sf:Id>000000000001122334</sf:Id>
                </records>
                <size>1</size>
            </result>
        </queryResponse>
    </soapenv:Body>
</soapenv:Envelope>

The Data node that I am trying to access the first <sf:Id>000000000001122334</sf:Id> node.

The XPath for that node is /soapenv:Envelope/soapenv:Body/queryResponse/result/records/sf:Id. However this value is pulling null. If I back up to the soapenv:Body node I am able to pull all of the child nodes as follows.

Xpath: /soapenv:Envelope/soapenv:Body/

Response:

<soapenv:Body>
        <queryResponse>
            <result xsi:type="QueryResult">
                <done>true</done>
                <queryLocator xsi:nil="true" />
                <records xsi:type="sf:sObject">
                    <sf:type>Contact</sf:type>
                    <sf:Id>000000000001122334</sf:Id>
                    <sf:Id>000000000001122334</sf:Id>
                </records>
                <size>1</size>
            </result>
        </queryResponse>
    </soapenv:Body>

But any node past soapenv:Body returns null. Why is this happening and is there any way to get to the sf:Id Node using Xpath?

  • Fair enough question on the XPath usage. It looks like you are running a SOQL query against the partner API. Is there a reason you don't use the SOAP support that's built into Apex? – Daniel Ballinger Mar 29 '17 at 22:27
2

It appears to be a quirk of the XPath specification. Once there is a namespace specified, it becomes harder to access nodes in the default namespace. (As a side note, this is firmly outside the limits of my knowledge, and I'm not really inclined to dig through the W3C documents to verify this)

Based on the answer on this Stackoverflow question...

If you have a namespace prefix set, you could use it, like:

//soap:Body

But since the nodes you are trying to get use a default namespace, without a prefix, using plain XPath, you can only acesss them by the local-name() and namespace-uri() attributes. Examples:

//*[local-name()="HelloWorldResult"]/text()

...

Using that information, and an XPath tester that I found, the following query works

/soapenv:Envelope/soapenv:Body/*[local-name()="queryResponse"]/*[local-name()="result"]/*[local-name()="records"]/sf:Id

The * before using [local-name()="something"] appears to be required, and will select all nodes matching that name. To get a specific node, you'd just add [<some integer>] afterwords like so:

.../*[local-name()="something][2]/...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.