简体   繁体   中英

How do I use XPath on an XML with imports?

I'm trying to parse a WSDL file to build PHP classes. Whilst there are plenty of tools out there for this, they all currently fail for documenting the available SOAPHeaders and SOAPFaults that exist for the request and/or response.

I've got just about enough understanding to build this: https://github.com/rquadling/wsdl2php . But then I come across a service that does not match my previous knowledge.

So. An example WSDL file (snipped to be only the login method and some of the imports).

<?xml version="1.0" encoding="UTF-8"?>
<definitions
        xmlns:tns="urn:platform_2012_2.webservices.netsuite.com"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns="http://schemas.xmlsoap.org/wsdl/"
        xmlns:platformMsgs="urn:messages_2012_2.platform.webservices.netsuite.com"
        xmlns:platformFaults="urn:faults_2012_2.platform.webservices.netsuite.com"
        targetNamespace="urn:platform_2012_2.webservices.netsuite.com">
    <types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
            <xsd:import namespace="urn:core_2012_2.platform.webservices.netsuite.com" schemaLocation="https://webservices.eu2.netsuite.com/xsd/platform/v2012_2_0/core.xsd"/>
            <xsd:import namespace="urn:faults_2012_2.platform.webservices.netsuite.com" schemaLocation="https://webservices.eu2.netsuite.com/xsd/platform/v2012_2_0/faults.xsd"/>
            <xsd:import namespace="urn:messages_2012_2.platform.webservices.netsuite.com" schemaLocation="https://webservices.eu2.netsuite.com/xsd/platform/v2012_2_0/messages.xsd"/>
            <xsd:import namespace="urn:common_2012_2.platform.webservices.netsuite.com" schemaLocation="https://webservices.eu2.netsuite.com/xsd/platform/v2012_2_0/common.xsd"/>
            <!-- snip -->
        </xsd:schema>
    </types>
    <message name="headers">
        <part name="applicationInfo" element="platformMsgs:applicationInfo"/>
        <part name="partnerInfo" element="platformMsgs:partnerInfo"/>
        <part name="documentInfo" element="platformMsgs:documentInfo"/>
        <part name="preferences" element="platformMsgs:preferences"/>
        <part name="searchPreferences" element="platformMsgs:searchPreferences"/>
        <part name="passport" element="platformMsgs:passport"/>
    </message>
    <message name="loginRequest">
        <part name="parameters" element="platformMsgs:login"/>
    </message>
    <message name="loginResponse">
        <part name="parameters" element="platformMsgs:loginResponse"/>
    </message>
    <!-- snip -->
    <portType name="NetSuitePortType">
        <operation name="login">
            <input name="loginRequest" message="tns:loginRequest"/>
            <output name="loginResponse" message="tns:loginResponse"/>
            <fault name="InsufficientPermissionFault" message="tns:InsufficientPermissionFault"/>
            <fault name="InvalidAccountFault" message="tns:InvalidAccountFault"/>
            <fault name="InvalidCredentialsFault" message="tns:InvalidCredentialsFault"/>
            <fault name="InvalidVersionFault" message="tns:InvalidVersionFault"/>
            <fault name="ExceededRequestLimitFault" message="tns:ExceededRequestLimitFault"/>
            <fault name="UnexpectedErrorFault" message="tns:UnexpectedErrorFault"/>
        </operation>
        <!-- snip -->
    </portType>
    <binding name="NetSuiteBinding" type="tns:NetSuitePortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="login">
            <soap:operation soapAction="login"/>
            <input name="loginRequest">
                <soap:header message="tns:headers" part="applicationInfo" use="literal"/>
                <soap:header message="tns:headers" part="partnerInfo" use="literal"/>
                <soap:body use="literal"/>
            </input>
            <output name="loginResponse">
                <soap:body use="literal"/>
            </output>
            <fault name="InsufficientPermissionFault">
                <soap:fault name="InsufficientPermissionFault" use="literal"/>
            </fault>
            <fault name="InvalidAccountFault">
                <soap:fault name="InvalidAccountFault" use="literal"/>
            </fault>
            <fault name="InvalidCredentialsFault">
                <soap:fault name="InvalidCredentialsFault" use="literal"/>
            </fault>
            <fault name="InvalidVersionFault">
                <soap:fault name="InvalidVersionFault" use="literal"/>
            </fault>
            <fault name="ExceededRequestLimitFault">
                <soap:fault name="ExceededRequestLimitFault" use="literal"/>
            </fault>
            <fault name="UnexpectedErrorFault">
                <soap:fault name="UnexpectedErrorFault" use="literal"/>
            </fault>
        </operation>
        <!-- snip -->
    </binding>
    <service name="NetSuiteService">
        <port name="NetSuitePort" binding="tns:NetSuiteBinding">
            <soap:address location="https://webservices.eu2.netsuite.com/services/NetSuitePort_2012_2"/>
        </port>
    </service>
</definitions>

I'm using XPath to extract headers (in and out) and faults...

    $xPathMaster['Headers'] = [
        'headers_in' => [
            'Definitions/Binding/Operation[method]/Input/Header' => '//*[local-name()="definitions"]/*[local-name()="binding"]/*[local-name()="operation"][@name="'.$call.'"]/*[local-name()="input"]/*[local-name()="header"]/@part',
        ],
        'headers_out' => [
            'Definitions/Binding/Operation[method]/Output/Header' => '//*[local-name()="definitions"]/*[local-name()="binding"]/*[local-name()="operation"][@name="'.$call.'"]/*[local-name()="output"]/*[local-name()="header"]/@part',
        ],
        'faults' => [
            'Definitions/Binding/Operation[method]/fault' => '//*[local-name()="definitions"]/*[local-name()="binding"]/*[local-name()="operation"][@name="'.$call.'"]/*[local-name()="fault"]/@name',
        ],
    ];

( $call is the name of the method being called).

So my tool knows that the header applicationInfo is associated with the loginResponse .

What I'm unsure on how to do is to programmatically find the structure for that type.

Manually, I know that it exists as a mapped type in the messages namespace import from https://webservices.eu2.netsuite.com/xsd/platform/v2012_2_0/messages.xsd ...

<!-- snip -->
<complexType name="ApplicationInfo">
  <sequence>
    <element name="applicationId" minOccurs="0" type="xsd:string"/>
  </sequence>
</complexType>
<element name="applicationInfo" type="platformMsgs:ApplicationInfo"/>

I am using PHP's SOAP technology and it is able to give me a list of types ( $client->__getTypes() , but this shows the structure as...

struct ApplicationInfo {
 string applicationId;
}

(so it must internally running the imports), but no mention of applicationInfo .

So, trying to do this via PHP's DOM, using XPath queries and I'm lost on how to "expand" the import so the actual content is present and not a reference.

I'm sort of lost.

I'm not a PHP expert at all, but I think the PHP SOAP technology is doing it right. The list of types ( XSD type definitions ) in that XSD should not include applicationInfo because applicationInfo is an XSD element declaration . ( I am assuming that $client is a reference to the imported schema, btw)

As a general comment, you may also want to bear in mind that an imported XSD might import other XSDs which might import other XSDs...etc. So if you really need to work with the entire structure of the header/fault then you will need to recurse all the way down the nested imports.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM