4

There are a ton of existing questions about PHP's simpleXML and processing XML with namespaces. All of the questions I've looked at have made a fundamental assumption: The code knows in advance what namespaces are going to be included in an incoming SOAP request. In my case, I've seen inconsistent namespaces in SOAP requests.

Specifically, I've been working on implementing a web service to talk to the Quickbooks Web Connector(pdf) and some of the example requests I've seen look like this:

<soapenv:Envelope 
 xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:dev="http://developer.intuit.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <dev:authenticate>
         <dev:strUserName>username</dev:strUserName>
         <dev:strPassword>password</dev:strPassword>
      </dev:authenticate>
   </soapenv:Body>
</soapenv:Envelope>

...and some look like this:

<s11:Envelope 
 xmlns:s11='http://schemas.xmlsoap.org/soap/envelope/' 
 xmlns:ns1='http://developer.intuit.com/'>
  <s11:Header/>
  <s11:Body>
    <ns1:authenticate>
      <ns1:strUserName>username</ns1:strUserName>
      <ns1:strPassword>password</ns1:strPassword>
    </ns1:authenticate>
  </s11:Body>
</s11:Envelope>

...or this:

<SOAP-ENV:Envelope 
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:ns1="http://developer.intuit.com/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns1:authenticate>
         <ns1:strUserName>username</ns1:strUserName>
         <ns1:strPassword>password</ns1:strPassword>
      </ns1:authenticate>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I understand using xpath() to select elements, but that assumes you know what namespace to look in. Without any consistency in the namespacing, I'm having a hard time figuring out how to correctly and programmatically select the contents of the node to process.

The namespacing is completely irrelevant in this application-- could I just run the raw XML through a regex to remove the whatever: from <whatever:mytag> first?

Community
  • 1
  • 1
beporter
  • 3,740
  • 3
  • 37
  • 45

2 Answers2

5

First off, if you plan to use SOAP a lot, you may want to take a look at PHP's SOAP extension if you haven't already. I've never used it, though.

Back to your question, you said "In my case, I've seen inconsistent namespaces in SOAP requests." Get ready because I'm about to blow your mind: no you haven't. :)

In those three examples, the two namespaces are the same: there's http://schemas.xmlsoap.org/soap/envelope/ and there's http://developer.intuit.com/ -- What's different here is their prefix. The good news is the prefix doesn't really matter. See it as an alias to the namespace. The prefixes used in the document are automatically registered for use in XPath, but you can also register your own.

Here's an example of how to use the prefixes that were defined in the document (good if you already know what they are) or register your own prefixes and use those.

$xml = '<soapenv:Envelope 
 xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:dev="http://developer.intuit.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <dev:authenticate>
         <dev:strUserName>username</dev:strUserName>
         <dev:strPassword>password</dev:strPassword>
      </dev:authenticate>
   </soapenv:Body>
</soapenv:Envelope>';

$Envelope = simplexml_load_string($xml);

// you can register and use your own prefixes
$Envelope->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
$Envelope->registerXPathNamespace('auth', 'http://developer.intuit.com/');

$nodes = $Envelope->xpath('/soap:Envelope/soap:Body/auth:authenticate/auth:strUserName');
$username = (string) $nodes[0];

// or you can use the prefixes that are already defined in the document
$nodes = $Envelope->xpath('/soapenv:Envelope/soapenv:Body/dev:authenticate/dev:strPassword');
$password = (string) $nodes[0];

var_dump($username, $password);
Josh Davis
  • 28,400
  • 5
  • 52
  • 67
  • Yes! I understood the prefix being a shortcut to the namespace. What's ironic is I actually tried your solution myself before and couldn't get xpath to recognize the alternate prefixes I had defined. I must have been doing something wrong. I obviously don't fully grok Xpath yet. Forgive the novice follow up question (which shouldn't come as a surprise at this point), but if I don't necessarily know what the next tag immediately descendant to Body is going to be, can I still select it via a wildcard or an axis using '/soap:Envelope/child::soap:Body' or something? – beporter Jan 27 '11 at 14:44
  • Re: Using the soap extension. Being new to SOAP/XML/Xpath, I haven't been able to figure out how to get the SoapServer class to generate correctly formatted responses. When my handler functions return arrays, SOAP outputs them in the XML wrapped in tags the SOAP client (QBWC) won't accept. Kieth Palmer has [a good rundown in his wiki](http://www.consolibyte.com/wiki/doku.php/quickbooks_web_connector) of the expected responses. – beporter Jan 27 '11 at 14:52
  • You can use wildcards to replace the QName or the LocalPart, e.g.: `/soapenv:Envelope/soapenv:Body/*/dev:strPassword` or `/soapenv:Envelope/soapenv:Body/dev:*/dev:strPassword` -- It will act as a wildcard for **one** node, or you can use `//` to act as a wildcard for **any number** of nodes, e.g.: `//dev:strPassword` or `/soapenv:Envelope//dev:strPassword` – Josh Davis Jan 27 '11 at 17:48
1

There are couple helpful simplexml element methods that can help you determine and make use of the proper namespaces when querying with the xpath method. The first two are getNamespaces and getDocNamespaces. getNamespaces will return all of the namespaces used in the document (specify the recursive parameter), while getDocNamespaces will return all namespaces declared by the document.

Once you have the array of namespaces available, you can use registerXPathNamespace to register each namespace to the simplexml_element that you are going to use the xpath method.

I'm a new user so I can't post the links to the other methods in php's documentation.

Sam
  • 36
  • 2
  • I *think* I get what you're saying, but it sure would help to see an example. Say I wanted to end up with a $var that contained an SimpleXMLElement for authenticate, with two children: strUserName and strPassword. My goal is to be able to hand that generalized (meaning no namespaces in element names) object off to handlers that will only know to look for $var->children()->strUserName. – beporter Jan 26 '11 at 17:33