How do you access SimpleXML nodes in a namespace environment? - php

How do you access PHP SimpleXML nodes in a namespace environment?
I want to pragmatically add <param>value</param> under <request>. Not just edit it in a string.
$xml = \SimpleXMLElement(
'<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL
. '<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/">' . PHP_EOL
. ' <soap:Body>' . PHP_EOL
. ' <Function xmlns="https://webservices.sveaekonomi.se/webpay">' . PHP_EOL
. ' <request />' . PHP_EOL
. ' </Function>' . PHP_EOL
. ' </soap:Body>' . PHP_EOL
. '</soap:Envelope>'
);
I have attempted the following:
#1
$xml->Envelope->Body->Function->request->addChild('param', 'value');
#2
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->addChild('param', 'value');
#3
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->addChild('param', 'value');

You've made a few common mistakes here, so I'll go through each in turn.
Let's start with the beginning of your first attempt:
$xml->Envelope->Body->...
SimpleXML doesn't have a separate object for the document, only the root element - in this case, Envelope. So you don't need to say ->Envelope, you're already there. Without namespaces being involved, you would write:
$xml->Body->...
However, the object doesn't automatically "select" the namespace of that element, so you have to either immediately call ->children(), or pass the namespace you want to pre-select into the constructor:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->...
// Or
$xml = new SimpleXMLElement($xmlString, 0, false, 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->Body->...
With that in mind, we get to:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->Function->...
This fails because Function isn't in the same namespace as Body. The way I like to think of it is that the ->children() method is a "switch namespace" method, so we switch to the right namespace and carry on:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body
->children('https://webservices.sveaekonomi.se/webpay')->Function->request
->addChild('param', 'value');
This will work!
Your second attempt makes a different mistake:
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->...
The "children" method doesn't let you jump deep into the document - as its name suggests, it gives you the direct children of an element, not the grand-children, great grand-children, and so on. The $xml variable points to the "Envelope" node, and that doesn't have a child called "request".
There isn't a built-in method for "any descendant of", except by using XPath...
Although seemingly completely different, your third attempt actually fails for the same reason:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->...
The / operator in XPath similarly means "children of"; there is an operator for "any descendant of", which is //, so you would have got nearly the right result with this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')->...
That will fail for a slightly more subtle reason: the xpath() method of SimpleXML always returns an array, not an object, so you have to ask for the first element ([0]) of that array.
So the working XPath code is this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')[0]->addChild('param', 'value');

Since you are dealing with xml one way to approach it is to use xpath and local-name():
$xml = '<?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>
<Function xmlns="https://webservices.sveaekonomi.se/webpay">
<request/>
</Function>
</soap:Body>
</soap:Envelope>
';
$doc = new SimpleXMLElement($xml);
$destination = ($doc->xpath('//*[local-name()="request"]'));
$destination[0]->addChild('param', 'value');
echo $doc->asXML();
Output:
<?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>
<Function xmlns="https://webservices.sveaekonomi.se/webpay">
<request><param>value</param></request>
</Function>
</soap:Body>
</soap:Envelope>

Related

PHP SimpleXMLElement addAttribute namespaces syntax

I'm working with SimpleXMLElement for the first time and need to generate a line in my XML as follows:
<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
I haven't used addAttribute with namespaces before and can't get the correct syntax working here - I've started with this:
$node = new SimpleXMLElement('< Product ></Product >');
$node->addAttribute("xmlns:", "xsd:", 'http://www.w3.org/2001/XMLSchema-instance');
but can't work out how to correct this for the appropriate syntax to generate the desired output?
solution 1: add a prefix to the prefix
<?php
$node = new SimpleXMLElement('<Product/>');
$node->addAttribute("xmlns:xmlns:xsi", 'http://www.w3.org/2001/XMLSchema-instance');
$node->addAttribute("xmlns:xmlns:xsd", 'http://www.w3.org/2001/XMLSchema');
echo $node->asXML();
output:
<?xml version="1.0"?>
<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
note: this is a workaround and actually doesn't set the namespace for the attribute, but just quite enough if you are going to echo / save to file the result
solution 2: put namespace directly in the SimpleXMLElement constructor
<?php
$node = new SimpleXMLElement('<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>');
echo $node->asXML();
output is the same as in solution 1
solution 3 (adds additional attribute)
<?php
$node = new SimpleXMLElement('<Product/>');
$node->addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns");
$node->addAttribute("xmlns:xsd", 'http://www.w3.org/2001/XMLSchema', "xmlns");
echo $node->asXML();
output adds additional xmlns:xmlns="xmlns"
<?xml version="1.0"?>
<Product xmlns:xmlns="xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>

simplexml_load_string return empty result

It may be simple, but I am kind of stuck. I want to convert an XML string into PHP object. My XML string is:
$a = '<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<VerifyTxnResponse xmlns="http://www.exmlplekhe.com/">
<VerifyTxnResult>BID <11467></VerifyTxnResult>
</VerifyTxnResponse>
</soap:Body>
</soap:Envelope>';
I have tried var_dump(simplexml_load_string($a));, but it returns empty SimpleXMLElement object. I want to get VerifyTxnResult node. I think, <11467> is causing the problem. What may be the possible solution?
Thanks.
I want to get VerifyTxnResult node
The simplexml_load_string function returns an instance of SimpleXMLElement which is actually not empty for the XML you posted.
Register the namespace and fetch the node with xpath method:
$se = simplexml_load_string($a);
$se->registerXPathNamespace('r', 'http://www.nibl.com.np/');
foreach ($se->xpath('//r:VerifyTxnResult') as $result) {
var_dump((string)$result);
}
Sample Output
string(11) "BID <11467>"

PHP - SOAP xml returning empty when SimpleXmlElement object created

I've been trying to figure this out for over an hour now, and I give up. I'm getting the following response from a webservice.
I want to be able to get access to the children of Fault (I have this working for another webservice call, but for some reason, which I believe MIGHT be namespace related, this one doesn't work).
<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Invalid Scope</faultstring>
<detail>
<faultresponse xmlns="xsd url" xmlns:ns2="xsd url">
<transactionid>TEST</transactionid>
<errorcode>ERR-4223</errorcode>
</faultresponse>
</detail>
</soap:Fault>
</soap:Body>
</soap:envelope>
My PHP code is pretty simple. Firstly, I create the SimpleXmlElement object from the above xml.
I get a list of namespaces in the xml (which it picks up two: "soap", and ""). I then get the children of the XML in the soap: namespace.
$xml = simplexml_load_string($response_xml);
$namespace = $xml->getNamespaces(true);
$soap = $xml->children($namespace['soap']);
Given the above code. I would expect to be able to do something like this:
$fault_fields = $soap->Body->children()->Fault->children();
foreach ($fault_fields as $field):
echo (string) $field->getName() . ' - ' . $field[0] . '<br />';
endforeach;
However, if I run $soap->asXML(); I can see the following:
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Invalid Scope</faultstring>
<detail>
<faultresponse xmlns="xsd url" xmlns:ns2="xsd url">
<transactionid>TEST</transactionid>
<errorcode>ERR-4223</errorcode>
</faultresponse>
</detail>
</soap:Fault>
</soap:Body>
But if I try to go to access Body or Fault:
$soap = &$this->parse_soap_body($response_xml);
$body = $soap->Body->children()->Fault->children();
echo $body->asXML();
I get a Node no longer exists error with the stack returning this XML.
<soap:Envelope xmlns:soap="`http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Invalid Scope</faultstring>
.... more xml
This is seriously doing my head in.
Any help would be greatly appreciated.

Can't read XML with SimpleXML

I'm receiving through cUrl an XML generated with PHP with this code:
$c = curl_init("http://www.domain.com/script.php");
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
$xmlstr = curl_exec($c);
If I echo the $xmlstr variable it shows the following:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<XGetPasoParadaREGResponse xmlns="http://tempuri.org/">
<XGetPasoParadaREGResult>
<PasoParada><cabecera>false</cabecera>
<e1>
<minutos>1</minutos>
<metros>272</metros>
<tipo>NORMAL</tipo>
</e1>
<e2>
<minutos>7</minutos>
<metros>1504</metros>
<tipo>NORMAL</tipo>
</e2><linea>28
</linea>
<parada>376</parada>
<ruta>PARQUE ALCOSA</ruta>
</PasoParada>
</XGetPasoParadaREGResult><status>1</status></XGetPasoParadaREGResponse>
</soap:Body>
</soap:Envelope>
Which is correct and the same generated at the script. However, if I try to do the following
$xml = simplexml_load_string($xmlstr);
if (!is_object($xml))
throw new Exception('Reading XML error',1001);
echo $xml->e1->minutos;
Doesn't show anything and print_r the object prints an empty object. What could be wrong?
You have two main problems, firstly you aren't taking into account any namespaces (e.g. soap) and secondly you aren't traversing the XML hierarchy to get at the desired element.
The "simple" way would be:
$minutos = (int) $xml
->children('soap', TRUE) // <soap:*>
->Body // <soap:Body>
->children('') // <*> (default/no namespace prefix)
->XGetPasoParadaREGResponse // <XGetPasoParadaREGResponse>
->XGetPasoParadaREGResult // <XGetPasoParadaREGResult>
->PasoParada // <PasoParada>
->e1 // <e1>
->minutos; // <minutos> yay!
You could also make an XPath query for the value:
// Our target node belongs in this namespace
$xml->registerXPathNamespace('n', 'http://tempuri.org/');
// Fetch <minutos> elements children to <e1>
$nodes = $xml->xpath('//n:e1/n:minutos');
// $nodes will always be an array, get the first item
$minutos = (int) $nodes[0];
For what it is worth, the PHP Manual contains basic usage examples covering how to traverse the XML structure and the specific pages for children() and registerXPathNamespace() show you how to work with namespaced elements with examples.
Finally, as you have seen, the output from print_r()/var_dump() with SimpleXML is not always very helpful at all! The best way to see what you've got is to echo saveXML() which will display the XML for a given element.
Try the children method:
foreach ($xml->children("soap", true) as $k => $v) {
echo $k . ":\n";
var_dump($v);
}

How to use nuSOAP for messages with multiple namespaces

I'm trying to access a WebService using nuSOAP (because I'm bound to PHP4 here) that uses more than 1 namespace in a message. Is that possible?
An example request message would look like this:
<soapenv:Envelope ...
xmlns:ns1="http://domain.tld/namespace1"
xmlns:ns2="http://domain.tld/namespace2">
<soapenv:Header/>
<soapenv:Body>
<ns1:myOperation>
<ns2:Person>
<ns2:Firstname>..</ns2:Firstname>
..
</ns2:Person>
<ns1:Attribute>..</ns1:Attribute>
</ns1:myOperation>
</soapenv:Body>
</soapenv:Envelope>
I tried to following:
$client = new nusoap_client("my.wsdl", true);
$params = array(
'Person' => array(
'FirstName' => 'Thomas',
..
),
'Attribute' => 'foo'
);
$result = $client->call('myOperation', $params, '', 'soapAction');
in the hope that nuSOAP would try to match these names to the correct namespaces and nodes. Then I tried to use soapval() to generate the elements and their namespace - but if I call an operation, nuSOAP creates the following request:
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Body>
<queryCCApplicationDataRequest xmlns="http://domain.tld/namespace1"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So something goes wrong during the "matching" phase.
After trying around with the matching, I found two possible solutions:
1) Don't use the WSDL to create the nusoap_client and soapval() to create the message
This has the disadvantage that the message contains a lot of overhead (the namespace is defined in each element). Not so fine.
2) Instead of relying on the matching of parameters, construct your reply in xml and put all the definition for prefixes in the first element - e.g.
$params = "<ns1:myOperation xmlns:ns1="..." xmlns:ns2="...">
<ns2:Person>
<ns2:Firstname>..</ns2:Firstname>
..
</ns2:Person>
<ns1:Attribute>..</ns1:Attribute>
</ns1:myOperation>";
Still not a very nice solution, but it works :-)
Building on Irwin's post, I created the xml manually and had nusoap do the rest. My webhost does not have the php soap extension, so I had to go with nusoap, and the web service I'm trying to consume required the namespaces on each tag (e.g. on username and password in my example here).
require_once('lib/nusoap.php');
$client = new nusoap_client('https://service.somesite.com/ClientService.asmx');
$client->soap_defencoding = 'utf-8';
$client->useHTTPPersistentConnection(); // Uses http 1.1 instead of 1.0
$soapaction = "https://service.somesite.com/GetFoods";
$request_xml = '<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:GetFoods xmlns:n1="https://service.somesite.com">
<n1:username>banjer</n1:username>
<n1:password>theleftorium</n1:password>
</n1:GetFoods>
</env:Body>
</env:Envelope>
';
$response = $client->send($request_xml, $soapaction, '');
echo '<h2>Request</h2><pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
echo '<h2>Response</h2><pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>';
echo '<h2>Debug</h2><pre>' . htmlspecialchars($client->getDebug(), ENT_QUOTES) . '</pre>';
Then I had an error that said:
Notice: Undefined property: nusoap_client::$operation in ./lib/nusoap.php on line 7674
So I went the lazy route and went into nusoap.php and added this code before line 7674 to make it happy:
if(empty($this->operation)) {
$this->operation = "";
}
Another bypass this issue would be a modification to nusoap_client::call() function. Next to the this line (7359 in version 1.123) in nusoap.php:
$nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
I added this one:
$nsPrefix = $this->wsdl->getPrefixFromNamespace("other_ns_name");
And it worked! Since I only needed this library for one project, it was OK for me to hardcode this hack. Otherwise, I would dig more and modify the function to accept array instead of string for a namespace parameter.
Yeah, i've been having this same problem (found your q via google!) and i've come across this:
http://www.heidisoft.com/blog/using-nusoap-consume-net-web-service-10-min
Here, the dev creates the xml body of the message in coe and then uses nusoap to submit.

Categories