I need to access the value of an attribute located within a nested namespace in a XML document. Here is a sample of the XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp:epp xmlns:epp="urn:ietf:params:xml:ns:epp-1.0">
<epp:response>
<epp:result code="1000">
<epp:msg>Domain Check Command completed successfully</epp:msg>
</epp:result>
<epp:msgQ count="4" id="OTE-EPP-155DF5E824E-1BC86"/>
<epp:resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="1">example.com</domain:name>
</domain:cd>
</domain:chkData>
</epp:resData>
<epp:trID>
<epp:clTRID>123</epp:clTRID>
<epp:svTRID>456</epp:svTRID>
</epp:trID>
</epp:response>
</epp:epp>
In the above, I need to access the "avail" attribute which from what I can gather is located in a nested namespace domain within the epp namespace.
I am using simplexml_load_string($xml) to access the properties. I am able to access the epp namespace but my lack of XML knowledge is failing me on this one :)
Thank you in advance!
EDIT
As the question is marked as a duplicate I want to clear up that I am looking for a value of a property located within a nested namespace.
I have tried to access the property via xpath but have only received undefined namespace prefix errors:
echo $data->xpath('/domain:name')->attributes()->avail;
echo $data->xpath('/epp:response/epp:resData/domain:chkData/domain:cd/domain:name')->attributes()->avail;
I have also tried the above without the epp namespace but the same result. I know how to get the value from a single namespace but not from a nested namespace.
Another Edit:
I am still struggling. I really cannot get xpath to find the associated namespace element within the xml document. The below code is what I'm trying.
I have tried to also just do a vardump on $sxe->xpath('//d:name')) but get an empty array, indicating no values?
Please can someone help a brother out :)
$sxe = simplexml_load_string($result, "SimpleXMLElement", 0, "epp", true);
$code = $sxe->response->result->attributes()->code;
if ($code == 1000) {
$sxe->registerXPathNamespace('d','ietf:params:xml:ns:domain-1.0');
echo $sxe->xpath('//d:name')->attributes()->avail;
}
exit;
YET ANOTHER EDIT
Following another answer I changed my simplexml_load_string() function declaration and attempted to declare 2 xpath namespaces (feels like the closest I have gotten so far) - I am able to get the value in the epp namespace but still no luck with the domain namespace.
$sxe = simplexml_load_string($result,NULL,NULL,'urn:ietf:params:xml:ns:epp-1.0');
$sxe->registerXPathNamespace('epp', 'ietf:params:xml:ns:epp-1.0');
$sxe->registerXPathNamespace('domain', 'ietf:params:xml:ns:domain-1.0');
$code = $sxe->xpath('//epp:result')[0]->attributes()->code;
if ($code == 1000) {
print_r($sxe->xpath('domain:name'));
print_r($sxe->xpath('//domain:name'));
}
exit;
Related
I'm having troubles parsing an XML object in PHP. I'm using Laravel 5.8
Here is one of my tries:
$xml = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
var_dump($xml->children('soapenv', true)->Envalop->Body->children('ns3', true)->getAddressBookResponse->addressBook[0]->businessUnit);
And I'm getting the following error:
Call to a member function children() on null
I have tried different variations of creating and accessing the SimpleXMLElement but always with the same result. I'm getting back an empty object of typeSimpleXMLElement
Here is a sample XML that I'm using as input (coming from SOAP API call):
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns3:getAddressBookResponse xmlns:ns2="http://oracle.e1.bssv.JPRCUST0/"
xmlns:ns3="http://oracle.e1.bssv.JPR01000/">
<e1MessageList>
<e1Messages>
<message>Description: All records for the query have not been returned.
</message>
</e1Messages>
</e1MessageList>
<addressBook>
<businessUnit>123456</businessUnit>
<categoryCodesAddressBook>
<categoryCode001>XXX</categoryCode001>
<categoryCode002>XXX</categoryCode002>
</categoryCodesAddressBook>
<description1>MOHAMEDHASSANALI</description1>
</addressBook>
<addressBook>
<businessUnit>789789</businessUnit>
<categoryCodesAddressBook>
<categoryCode001>YYY</categoryCode001>
<categoryCode002>YYY</categoryCode002>
</categoryCodesAddressBook>
<description1>ALIHASSANAHMED</description1>
</addressBook>
</ns3:getAddressBookResponse>
</soapenv:Body>
</soapenv:Envelope>
That is not just XML, but SOAP. I suggest using a SOAP library (like ext/soap).
It's Envelope not Envalop. And this is the element in you $xml variable.
If you want to treat it as just XML, do not rely on the namespaces prefixes but the actual namespace URIs (the values of the xmlns attributes). Prefixes can change. A prefix like ns3 is auto generated by a SOAP library, just adding/removing an element with another namespace could change it. So define constants or variables for the XMLNS values and use them.
const XMLNS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
const XMLNS_ADDRESSES = 'http://oracle.e1.bssv.JPR01000/';
$envelope = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
var_dump(
$envelope->children(XMLNS_SOAP)->Body->children(XMLNS_ADDRESSES)->getAddressBookResponse->children('')->addressBook[0]->businessUnit
);
Xpath expressions (SimpleXMLElement::xpath()) allow for conditional fetching. This avoids problems if an element is missing, but you will have to register your own prefixes for the namespaces.
const XMLNS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
const XMLNS_ADDRESSES = 'http://oracle.e1.bssv.JPR01000/';
$envelope = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
$envelope->registerXpathNamespace('s', XMLNS_SOAP);
$envelope->registerXpathNamespace('a', XMLNS_ADDRESSES);
var_dump(
$envelope->xpath('(s:Body/a:getAddressBookResponse/addressBook)[1]/businessUnit')
);
I am new to simplexml parser in PHP. I have run the examples that I have found and they work as advertised. I can't get it to work with my program though. I have searched in here for hours.
I have an XML file called core.xml (note that the node tags have colons in):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>Lecture 3</dc:title>
<dc:creator>John Brown & Greg Smith</dc:creator>
<cp:lastModifiedBy>Greg Smith</cp:lastModifiedBy>
<cp:revision>165</cp:revision>
<dcterms:created xsi:type="dcterms:W3CDTF">2010-02-19T04:37:55Z</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2014-01-30T02:41:30Z</dcterms:modified>
</cp:coreProperties>
I use following code to load it into the parser:
if (file_exists('core.xml')){
echo 'file exists <br>';
$xml_core = simplexml_load_file('core.xml');
print_r($xml_core);
}
else
{
exit('Failed to open core.xml.');
}
The file exists but all I get at the print_r is:
file exists
SimpleXMLElement Object ( )
How do I access the nodes? Other XML files that I have to use are many layers in depth.
Thanks
What you describe as an error pattern in your question is actually correct behaviour and not an error.
If you use print_r (or var_dump for that matter) on a SimpleXMLElement it will only show you some rudimentary information about that object and not the entire content of the XML document.
To see the whole XML, use the asXML() method instead. Alternatively to obtain debug information you can use a library that is specifically aware on how to debug SimpleXMLElement content's like simplexml_debug written by IMSop.
In your specific case, the object shows empty (no object members) because there aren't any nodes in the XML document within the default namespace (see: Namespaces in XML 1.0) that - after the more or less complex rules of SimpleXMLElement to array transitions were applied - would have been passed from the objects internal debug handler to the print_r or var_dump function to be displayed then. So only the class-name is given so that you can see it's an object of a certain class - and has no members:
SimpleXMLElement Object ( )
^ ^ ^
object type | `------ object fields (empty)
|
variable type
How to deal with that? It's very simple: Know the XML (you know it aleady) and obtain the data from it as it's documented:
PHP: Dealing with XML errors - Manual - Shows how you create (load) a SimpleXMLElement in a controlled manner and controlling error conditions.
PHP: Basic SimpleXML usage - Manual - Shows how to access data inside an XML document via the API of SimpleXMLElement.
As your XML element names contain colons, you should be made aware that these are so called XML namespaces. As you've seen differences in the print_r output, you will also see differences in accessing the child elements within that namespace. You have to use the children() method and specify the namespace of which you would like to obtain the child elements. If you use the xpath() method to access elements, you need to register an namespace prefix before you use it on the SimpleXMLElement you call that method on.
Some assorted selection of SimpleXML and XML Namespace related Q&A material here on site:
Parse XML with Namespace using SimpleXML (Feb 2009)
PHP namespace simplexml problems (Jan 2010)
PHP SimpleXML Namespace Problem (May 2011)
Parse XML namespaces with php SimpleXML (May 2013)
You can also obtain this information from the PHP manual, just take a look for namespace related information within the SimpleXMLElement API documentation.
You can use file_get_contents to load XML file.
Try
if(file_exists('core.xml'))
{
echo 'file exists <br>';
$xml_core = file_get_contents('core.xml');
$xml = new SimpleXMLElement($xml_core);
print_r($xml);
}
else
{
exit('Failed to open core.xml.');
}
I am stuck with DomDocument
This thing is working fine, no doubt -
$resource1->appendChild($dom->createAttribute('type'))
->appendChild($dom->createTextnode("webcontent"));
It is adding type="webcontent" to resource node
However when I am using this code its not adding it to it -
$resource1->appendChild($dom->createAttribute('adlcp:scormType'))
->appendChild($dom->createTextnode("sco"));
Expected to generate - adlcp:scormType="sco" <-- Not Working
However If I am creating
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <-- Working
Code -
$manifestNode->appendChild($dom->createAttribute('xmlns:xsi'))
->appendChild($dom->createTextNode("http://www.w3.org/2001/XMLSchema-instance"));
Let me know what I am doing wrong and how do I make it working
EDIT
Error -
XML Parsing Error: prefix not bound to a namespace
Googled same with the keyword - xml parsing error prefix not bound to a namespace php but not much help.
To set an attribute on a node:
$resource1->setAttribute('type', 'webcontent');
To set a namespaced attribute on a node (assuming this is the namespace represented by the "adlcp" prefix):
$resource1->setAttributeNS('http://www.adlnet.org/xsd/adlcp_rootv1p2', 'adlcp:scormType', 'sco');
I'm fairly new to the SOAP and WSDL world. What do I have to do, to make sure that the namespace will always be in the return-element?
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://someurl.com">
<SOAP-ENV:Body>
<ns1:sayHelloResponse>
<return>Say Hello Kelvin</return>
</ns1:sayHelloResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
What I want is:
<ns1:sayHelloResponse>
<ns1:return>Say Hello Kelvin</ns1:return>
</ns1:sayHelloResponse>
I'm using PHP and the Zend Framework. The WSDL gets generated by Zend_Soap_AutoDiscovery. This shouldn't be a barrier though, because I will modify the output of it anyways.
Thanks for your help.
After taking a break for a while, I approached the problem once again.
This time though, I stumbled upon this nice article (credits to my former co-worker Mike). If the attribute elementFormDefault isn't declared in the schema tag, unqualified is assumed as its value. By giving it the value "qualified" the following will happen:
But if you add elementFormDefault="qualified" to all of the schemas in the document/literal wrapped WSDL, then all elements in the messages would be qualified with their parent's namespace.
In general, you do not want to use elementFormDefault="qualified"
because it bloats the messages, but a year or more ago there were
interoperability issues between various vendors, and setting this
attribute sometimes fixed the problems.
Even though I wasn't using document/literal, I wanted to try it out. I added the attribute to my schema tag and made a request to a different call. Here's the response I got:
<ns1:getUserResponse>
<return>
<ns1:firstname>First</ns1:firstname>
<ns1:lastname>Last</ns1:lastname>
</return>
</ns1:getUserResponse>
As you can see, the child elements of the "return"-element got the namespace prefixed.
At this point I got really excited, because I finally got closer to where I wanted to be.
Unfortunately, the return element didn't have the namespace prefixed. I tried the earlier call (see question post) again, but the response was the same as before.
I couldn't spend more time on this issue. It was just a prototype after all.
That's why I decided to hook into Zend_Soap_Server's handle function, to modify the response before outputting it.
class Custom_Soap_Server extends Zend_Soap_Server
{
public function __construct($wsdl = null, array $options = null)
{
parent::__construct($wsdl, $options);
// Response of handle will always be returned
$this->setReturnResponse(true);
}
public function handle($request = null)
{
$response = parent::handle($request);
echo str_replace(array('<return>', '</return>'), array('<ns1:return>', '</ns1:return>'), $response);
return;
}
}
To be honest, it's a nasty hack. I'm always assuming that there's just one namespace. The replace function could be written much better. But it was for a prototype after all and this was my first thought to make it work.
After using the new custom class instead of Zend_Soap_Server, all of the return elements had ns1 prefixed to them.
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?
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);
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.