SimpleXML Parsing Nested Namespaces - PHP - php

I am having a small problem parsing the tags in the nested namespace called "q0:", located below.
//Tag that I am trying to parse
<q0:CustomerTransactionId>
I am able to get to all of the "v12:" namespaced tags but not the "q0:" ones for some reason.
Thanks!
Here is the XML
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<soapenv:Body>
<v12:ProcessShipmentReply
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:v12="http://fedex.com/ws/ship/v12">
<v12:HighestSeverity>ERROR</v12:HighestSeverity>
<v12:Notifications>
<v12:Severity>ERROR</v12:Severity>
<v12:Source>ship</v12:Source>
<v12:Code>3058</v12:Code>
<v12:Message>Recipient Postal code or routing code is required</v12:Message>
<v12:LocalizedMessage>Recipient Postal code or routing code is required</v12:LocalizedMessage>
</v12:Notifications>
<q0:TransactionDetail xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:q0="http://fedex.com/ws/ship/v12">
<q0:CustomerTransactionId>21445634</q0:CustomerTransactionId>
</q0:TransactionDetail><q0:Version xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:q0="http://fedex.com/ws/ship/v12">
<q0:ServiceId>ship</q0:ServiceId>
<q0:Major>12</q0:Major>
<q0:Intermediate>0</q0:Intermediate>
<q0:Minor>0</q0:Minor>
</q0:Version>
</v12:ProcessShipmentReply>
</soapenv:Body>
</soapenv:Envelope>
And here is my parse
$xml = simplexml_load_string($result, NULL, NULL, 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->registerXPathNamespace('env', 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->registerXPathNamespace('v12', 'http://fedex.com/ws/ship/v12');
$xml->registerXPathNamespace('q0', 'http://fedex.com/ws/ship/v12');
$bodies = $xml->xpath('env:Body');
foreach($bodies as $body){
$reply = $body->children('v12', TRUE)->ProcessShipmentReply;
$reply2 = $body->children('q0', TRUE)->TransactionDetail;
$custInfoArr['status'] = (string) $reply->HighestSeverity;
if(strtolower($custInfoArr['status'])=="error"){
$custInfoArr['invoiceNum'] = (string)$reply2->CustomerTransactionId;
$custInfoArr['notificationSeverity']= (string) $reply->Notifications->Severity;
$custInfoArr['notificationSource']= (string) $reply->Notifications->Source;
$custInfoArr['notificationCode']= (string) $reply->Notifications->Code;
$custInfoArr['notificationMessage']= (string) $reply->Notifications->Message;
$custInfoArr['notificationLocalizedMessage']= (string) $reply->Notifications->LocalizedMessage;
}
$custInfoArr['trackingCode'] = (string) $reply->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds->TrackingNumber;
$custInfoArr['labelCode'] = (string) $reply->CompletedShipmentDetail->CompletedPackageDetails->Label->Parts->Image;
$custInfoArr['labelCode'] = base64_decode($custInfoArr['labelCode']);
}

<q0:TransactionDetail> is not a child of <env:Body>, it is a child of <v12:ProcessShipmentReply>, so you need to look for it inside $reply, not $body: $reply2 = $reply->children('q0', TRUE)->TransactionDetail;
The important thing to remember is that both the ->TagName operator and the ->children() method only look at immediate children of a particular node, they are not doing a "deep search" like XPath.
In fact, looking, both v12: and q0: are aliases for the same namespace ('http://fedex.com/ws/ship/v12'), so the next line can just be $reply2 = $reply->TransactionDetail. If you just say $reply = $body->children('http://fedex.com/ws/ship/v12')->ProcessShipmentReply rather than relying on the alias, this becomes clearer (and safer, since those aliases may change).
Incidentally, unless you're using it for something else, you can also get rid of all your XPath code (including all of the registerXPathNamespace calls) and just write $body = $xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body (I'm pretty sure SOAP only allows one Body per message).

Related

SimpleXML root node prefix php with default namespace declaration

I'm creating an xml file with PHP.
The file I need to create is this one I show you:
<p:FatturaElettronica versione="FPA12" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12" >
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
<p:FatturaElettronica>
This is my code:
$xml = new SimpleXMLElement('<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" />');
$xml->addAttribute("versione","FPA12");
$xml->addAttribute("xmlns:xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
$FatturaElettronicaHeader = $xml->addChild('FatturaElettronicaHeader');
$DatiTrasmissione=$FatturaElettronicaHeader->addChild('DatiTrasmissione');
$IdTrasmittente=$DatiTrasmissione->addChild('IdTrasmittente');
$IdTrasmittente->addChild('IdPaese', 'IT');
$IdTrasmittente->addChild('IdCodice','01234567890');
$ProgressivoInvio=$DatiTrasmissione->addChild('ProgressivoInvio', '00001');
$FormatoTrasmissione=$DatiTrasmissione->addChild('DatiTrasmissione', 'FPA12');
$CodiceDestinatario=$DatiTrasmissione->addChild('CodiceDestinatario', 'AAAAAA');
Because in my created file I initially had the prefix p: in each tag, as shown below
<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12">
<p:FatturaElettronicaHeader>
<p:DatiTrasmissione>
<p:IdTrasmittente>
<p:IdPaese>IT</p:IdPaese>
<p:IdCodice>01234567890</p:IdCodice>
</p:IdTrasmittente>
<p:ProgressivoInvio>00001</p:ProgressivoInvio>
<p:DatiTrasmissione>FPA12</p:DatiTrasmissione>
<p:CodiceDestinatario>AAAAAA</p:CodiceDestinatario>
</p:DatiTrasmissione>
while this prefix p: must be only in the root node (p:FatturaElettronica) I added xmlns="http://dummy.com"
<p:FatturazioneElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12" xmlns="http://dummy.com">
and
$fatturaelettronicaheader = $xml->addChild('FatturaElettronicaHeader', '', 'http://dummy.com');
as it was suggested in this question
Only this 'http://dummy.com' is not present in the original xml file.
How can I solve this problem or possibly eliminate it before actually generating the file?
SimpleXML abstracts nodes and has some automatic logic for namespaces. That works fine for basic/simple XML structures.
For more complex XML structures you want to be explicit - so use DOM. It has specific methods for different node types with and without namespaces.
// define a list with the used namespaces
$namespaces = [
'xmlns' => 'http://www.w3.org/2000/xmlns/',
'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'signature' => 'http://www.w3.org/2000/09/xmldsig#',
'wsdl-types' => 'http://microsoft.com/wsdl/types/'
];
$document = new DOMDocument('1.0', 'UTF-8');
// create and append an element with a namespace
// this will add the namespace definition for the prefix "p" also
$document->appendChild(
$root = $document->createElementNS($namespaces['wsdl-types'], 'p:FatturazioneElettronica')
);
// set an attribute without a namespace
$root->setAttribute('versione', 'FPA12');
// add namespace definitions using the reserved "xmlns" namespace
$root->setAttributeNS($namespaces['xmlns'], 'xmlns:xsi', $namespaces['xsi']);
$root->setAttributeNS($namespaces['xmlns'], 'xmlns:ds', $namespaces['signature']);
// create and append the an element - keep in variable for manipulation
// the element does not have a namespace
$root->appendChild(
$header = $document->createElement('FatturaElettronicaHeader')
);
$header->appendChild(
$dati = $document->createElement('DatiTrasmissione')
);
$dati->appendChild(
$id = $document->createElement('IdTrasmittente')
);
// create and append element, set text content using a chained call
$id
->appendChild($document->createElement('IdPaese'))
->textContent = 'IT';
$id
->appendChild($document->createElement('IdCodice'))
->textContent = '01234567890';
$dati
->appendChild($document->createElement('ProgressivoInvio'))
->textContent = '00001';
$dati
->appendChild($document->createElement('FormatoTrasmissione'))
->textContent = 'FPA12';
$dati
->appendChild($document->createElement('CodiceDestinatario'))
->textContent = 'AAAAAA';
$document->formatOutput = TRUE;
echo $document->saveXML();
Output:
<?xml version="1.0" encoding="UTF-8"?>
<p:FatturazioneElettronica xmlns:p="http://microsoft.com/wsdl/types/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" versione="FPA12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
</p:FatturazioneElettronica>
Be aware that in your XML p:FatturazioneElettronica has a namespace. It resolves to {http://microsoft.com/wsdl/types/}FatturazioneElettronica. However I don't think that FatturazioneElettronica is a valid element in the WSDL types namespace.
FatturaElettronicaHeader (and the descandant nodes) do not have a namespace.
First, your desired xml (as well as the one in the question you link to) is not well formed for several reasons.
Second, even after that's fixed (see below), it's not clear to me why you're going about it the way you do.
How about this way:
$string = '<?xml version="1.0" encoding="UTF-8"?>
<root>
<p:FatturaElettronica xmlns:p="http://microsoft.com/wsdl/types/" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPA12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234567890</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>00001</ProgressivoInvio>
<FormatoTrasmissione>FPA12</FormatoTrasmissione>
<CodiceDestinatario>AAAAAA</CodiceDestinatario>
</DatiTrasmissione>
</FatturaElettronicaHeader>
</p:FatturaElettronica>
</root>';
$xml = simplexml_load_string($string);
echo $xml->asXML() ."\r\n";
That should echo your well-formed xml.

simplexml_load_string always fails if there are special characters inside tags

I'm receiving from an external web service an XML like this(notice the colons inside the tags)
XML
<?xml version='1.0' encoding='UTF-8'?>
<env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns0="http://webservice/CrsWSApi.wsdl/types/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<ns0:callwsapiResponseElement>
<ns0:result>
<ns0:object>BTS</ns0:object>
<ns0:receiver>BTSV45_ANSF_PROD-AKITT</ns0:receiver>
<ns0:sender>ANSFP</ns0:sender>
<ns0:data>
{"name":"Paolo", "cognome": Rossi, "data":"18/11/1983"}
</ns0:data>
<ns0:errormess xsi:nil="true"/>
<ns0:errorcode xsi:nil="true"/>
<ns0:status>1</ns0:status>
<ns0:sessionid>akit1</ns0:sessionid>
</ns0:result>
</ns0:callwsapiResponseElement>
</env:Body>
</env:Envelope>
if I use this function to parse it
PHP
$xml = simplexml_load_string($xml) or die("Error: Cannot create object");
I'm always receiving Error. If I remove the colons it works as expected. Is there a way to make this works without manipulating the colons before the xml parsing?
I want to avoid this manipulation
PHP
$xml = preg_replace(['/env:/','/:env(?!=)/','/ns0:/','/:ns0(?!=)/'], '', $myXMLData);
UPDATE
I already accepted a good answer that is reporting usefull links on how namespases are working in an xml environment, but I want to share another way to obtain the same result without parsing an xpath expression. We can you use the children method of simple_xml_loadstring specifiying the second parameter as a namespace.
fot the above code you can obtain the ns0:data content with this code(before you have to change the if statement as the accepted answer)
PHP
$xml = simplexml_load_string($response);
echo $xml
->children("env", true)
->Body
->children("ns0", true)
->callwsapiResponseElement
->result
->data;
// This will print
{"name":"Paolo", "cognome": Rossi, "data":"18/11/1983"}
For me it is very curious the return of simplexml_load_string (https://www.php.net/manual/en/function.simplexml-load-string.php):
This function may return Boolean false, but may also return a non-Boolean value which evaluates to false. Please read the section on Booleans for more information. Use the === operator for testing the return value of this function.
And indeed if you replace the or die:
<?php
$response = <<<XML
<?xml version='1.0' encoding='UTF-8'?>
<env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns0="http://webservice/CrsWSApi.wsdl/types/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<ns0:callwsapiResponseElement>
<ns0:result>
<ns0:object>BTS</ns0:object>
<ns0:receiver>BTSV45_ANSF_PROD-AKITT</ns0:receiver>
<ns0:sender>ANSFP</ns0:sender>
<ns0:data>
{"name":"Paolo", "cognome": Rossi, "data":"18/11/1983"}
</ns0:data>
<ns0:errormess xsi:nil="true"/>
<ns0:errorcode xsi:nil="true"/>
<ns0:status>1</ns0:status>
<ns0:sessionid>akit1</ns0:sessionid>
</ns0:result>
</ns0:callwsapiResponseElement>
</env:Body>
</env:Envelope>
XML;
$xml = simplexml_load_string($response);
if($xml===false){
die();
}
echo $xml->xpath("//*[local-name() = 'receiver']")[0];
Results:
BTSV45_ANSF_PROD-AKITT
You can then use the namespaces to find your data. This is a nice post Reference - How do I handle Namespaces (Tags and Attributes with a Colon in their Name) in SimpleXML?

PHP get data from multilevel tag colon XML file DOM PHP

I have question , how to parsing data from multilevel tag colon XML file with dom PHP.
Below is my XML sample data. I want to get data inside < transfer > and return as an array data
<soapenv:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<HostCustomerResponse xmlns="http://xx.xx.xx.xx">
<ns1:output xmlns:ns1="http://xx.xx.xx" xmlns:ns2="some:url" xsi:type="ns2:Output">
<ns2:statusCode>00</ns2:statusCode>
<ns2:statusMsg/>
<ns2:txnResponseDateTime>20190625164236</ns2:txnResponseDateTime>
<ns2:txnData>
<transferRequest>
<transfer>
<transferId>123456789</transferId>
<txnDate>123456789</txnDate>
<debitAcctNo>123456789</debitAcctNo>
<benAcctNo>123456789</benAcctNo>
</transfer>
</transferRequest>
</ns2:txnData>
</ns1:output>
</HostCustomerResponse>
</soapenv:Body>
</soapenv:Envelope>
and this result i want.
array(
[transferID] => 123456789,
[txnDate] => 123456789,
.....
)
Not sure if http://xx.xx.xx.xx is the real namespace in
<HostCustomerResponse xmlns="http://xx.xx.xx.xx">
but as this defines the namespace for any default elements (i.e. the ones your after) then you need to load the source XML and then register this namespace. Then you can use XPath to find the <transfer> element. You then just iterate through the elements within that ( using children() and add them into the output array...
$xml = simplexml_load_string($source);
$xml->registerXPathNamespace("d", "http://xx.xx.xx.xx");
$transfer = $xml->xpath("//d:transfer")[0];
$output = [];
foreach ( $transfer->children() as $key=> $value ) {
$output[$key] = (string)$value;
}
Your original XML is missing the definition of the soapenv namespace, so I added that to make the XML correct...
<soapenv:Envelope xmlns:soapenv="http://soapev.com"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<HostCustomerResponse xmlns="http://xx.xx.xx.xx">
<ns1:output xmlns:ns1="http://xx.xx.xx" xmlns:ns2="some:url" xsi:type="ns2:Output">
<ns2:statusCode>00</ns2:statusCode>
<ns2:statusMsg/>
<ns2:txnResponseDateTime>20190625164236</ns2:txnResponseDateTime>
<ns2:txnData>
<transferRequest>
<transfer>
<transferId>123456789</transferId>
<txnDate>123456789</txnDate>
<debitAcctNo>123456789</debitAcctNo>
<benAcctNo>123456789</benAcctNo>
</transfer>
</transferRequest>
</ns2:txnData>
</ns1:output>
</HostCustomerResponse>
</soapenv:Body>
</soapenv:Envelope>
You can get the transfer node by using the following code snippet
$response = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $soapXMLResult);
$xml = new SimpleXMLElement($response);
$body = $xml->xpath('//soapenvBody')[0];
$array = json_decode(json_encode((array)$body), TRUE);
$transfer = $array['HostCustomerResponse']['ns1output']['ns2txnData']['transferRequest']['transfer'];
https://3v4l.org/UKnZs

PHP - parsing xml which has namespace elements

I read other posts and solutions, and they don't work for me - or perhaps I'm not understanding them well enough.
I have a hp network scanner, and have a perl script that interacts through a series of transactions such that I can initiate a scan. I'm working to port that rather directly to php; more suitable for the server I want to run it on. Some transactions work, some don't. This is about one that doesn't.
I took the XML from one of the queries and it won't successfully parse (or this is where I don't understand it well enough). I'm running php version 7.1.12, in case there is something related to that.
my test outputs this:
> php xmltest.php
SimpleXMLElement Object
(
)
object(SimpleXMLElement)#1 (0) {
}
>
And if the xml is simpler (no name-space info I think), then the print_r() is quite verbose.
And here is the full test script with some actual data to process
error_reporting( E_ALL );
ini_set('display_errors', 1);
$test_1 = <<<EOM
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wst="http://schemas.xmlsoap.org/ws/2004/09/transfer"
xmlns:mex="http://schemas.xmlsoap.org/ws/2004/09/mex"
xmlns:wsdp="http://schemas.xmlsoap.org/ws/2006/02/devprof"
xmlns:PNPX="http://schemas.microsoft.com/windows/pnpx/2005/10"
xmlns:UNS1="http://www.microsoft.com/windows/test/testdevice/11/2005"
xmlns:dd="http://www.hp.com/schemas/imaging/con/dictionaries/1.0"
xmlns:wprt="http://schemas.microsoft.com/windows/2006/08/wdp/print"
xmlns:wscn="http://schemas.microsoft.com/windows/2006/08/wdp/scan">
<SOAP-ENV:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse</wsa:Action>
<wsa:MessageID>urn:uuid:fec6e42d-5356-1f69-9c3a-001f2927cf33</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:704ccde5-6861-415d-bd65-31dd9d7a8b98</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<mex:Metadata>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisDevice">
<wsdp:ThisDevice>
<wsdp:FriendlyName xml:lang="en">Printer (HP Color LaserJet CM1312nfi MFP)</wsdp:FriendlyName>
<wsdp:FirmwareVersion>20140625</wsdp:FirmwareVersion>
<wsdp:SerialNumber>CNB885H665</wsdp:SerialNumber>
</wsdp:ThisDevice>
</mex:MetadataSection>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisModel">
<wsdp:ThisModel>
<wsdp:Manufacturer xml:lang="en">HP</wsdp:Manufacturer>
<wsdp:ManufacturerUrl>http://www.hp.com/</wsdp:ManufacturerUrl>
<wsdp:ModelName xml:lang="en">HP Color LaserJet CM1312nfi MFP</wsdp:ModelName>
<wsdp:ModelNumber>CM1312nfi MFP</wsdp:ModelNumber>
<wsdp:PresentationUrl>http://192.168.1.20:80/</wsdp:PresentationUrl>
<PNPX:DeviceCategory>Printers</PNPX:DeviceCategory>
</wsdp:ThisModel>
</mex:MetadataSection>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/Relationship">
<wsdp:Relationship Type="http://schemas.xmlsoap.org/ws/2006/02/devprof/host">
<wsdp:Hosted>
<wsa:EndpointReference>
<wsa:Address>http://192.168.1.20:3910/</wsa:Address>
<wsa:ReferenceProperties>
<UNS1:ServiceIdentifier>uri:prn</UNS1:ServiceIdentifier>
</wsa:ReferenceProperties>
</wsa:EndpointReference>
<wsdp:Types>wprt:PrinterServiceType</wsdp:Types>
<wsdp:ServiceId>uri:1cd4F16e-7c8a-a7a0-3797-00145a8827ce</wsdp:ServiceId>
<PNPX:CompatibleId>http://schemas.microsoft.com/windows/2006/08/wdp/print/PrinterServiceType</PNPX:CompatibleId>
</wsdp:Hosted>
</wsdp:Relationship>
</mex:MetadataSection>
</mex:Metadata>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
EOM;
$myxml1 = simplexml_load_string($test_1);
print_r($myxml1);
var_dump($myxml1);
exit;
?>
There are several parameters nestled in there that I want to pull out. One, for instance is:
<wsa:Address>http://192.168.1.20:3910/</wsa:Address>
Can you help me close my knowledge gap on how to access this parameter?
thanks!
First of all, soap and namespaces just make parsing XML harder than it has to be. I've never parsed XML that had namespaces that actually made the XML better to understand, or had any benefit at all. I fully get why namespaces exist, but it just means jumping through some extra hoops to get the data out. The trick with namespaces is that you have to "enter in" to the namespace branch by asking that the namespace as a child.
<?php
error_reporting( E_ALL );
ini_set('display_errors', 1);
$str = <<<EOM
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wst="http://schemas.xmlsoap.org/ws/2004/09/transfer"
xmlns:mex="http://schemas.xmlsoap.org/ws/2004/09/mex"
xmlns:wsdp="http://schemas.xmlsoap.org/ws/2006/02/devprof"
xmlns:PNPX="http://schemas.microsoft.com/windows/pnpx/2005/10"
xmlns:UNS1="http://www.microsoft.com/windows/test/testdevice/11/2005"
xmlns:dd="http://www.hp.com/schemas/imaging/con/dictionaries/1.0"
xmlns:wprt="http://schemas.microsoft.com/windows/2006/08/wdp/print"
xmlns:wscn="http://schemas.microsoft.com/windows/2006/08/wdp/scan">
<SOAP-ENV:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse</wsa:Action>
<wsa:MessageID>urn:uuid:fec6e42d-5356-1f69-9c3a-001f2927cf33</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:704ccde5-6861-415d-bd65-31dd9d7a8b98</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<mex:Metadata>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisDevice">
<wsdp:ThisDevice>
<wsdp:FriendlyName xml:lang="en">Printer (HP Color LaserJet CM1312nfi MFP)</wsdp:FriendlyName>
<wsdp:FirmwareVersion>20140625</wsdp:FirmwareVersion>
<wsdp:SerialNumber>CNB885H665</wsdp:SerialNumber>
</wsdp:ThisDevice>
</mex:MetadataSection>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisModel">
<wsdp:ThisModel>
<wsdp:Manufacturer xml:lang="en">HP</wsdp:Manufacturer>
<wsdp:ManufacturerUrl>http://www.hp.com/</wsdp:ManufacturerUrl>
<wsdp:ModelName xml:lang="en">HP Color LaserJet CM1312nfi MFP</wsdp:ModelName>
<wsdp:ModelNumber>CM1312nfi MFP</wsdp:ModelNumber>
<wsdp:PresentationUrl>http://192.168.1.20:80/</wsdp:PresentationUrl>
<PNPX:DeviceCategory>Printers</PNPX:DeviceCategory>
</wsdp:ThisModel>
</mex:MetadataSection>
<mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2006/02/devprof/Relationship">
<wsdp:Relationship Type="http://schemas.xmlsoap.org/ws/2006/02/devprof/host">
<wsdp:Hosted>
<wsa:EndpointReference>
<wsa:Address>http://192.168.1.20:3910/</wsa:Address>
<wsa:ReferenceProperties>
<UNS1:ServiceIdentifier>uri:prn</UNS1:ServiceIdentifier>
</wsa:ReferenceProperties>
</wsa:EndpointReference>
<wsdp:Types>wprt:PrinterServiceType</wsdp:Types>
<wsdp:ServiceId>uri:1cd4F16e-7c8a-a7a0-3797-00145a8827ce</wsdp:ServiceId>
<PNPX:CompatibleId>http://schemas.microsoft.com/windows/2006/08/wdp/print/PrinterServiceType</PNPX:CompatibleId>
</wsdp:Hosted>
</wsdp:Relationship>
</mex:MetadataSection>
</mex:Metadata>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
EOM;
$xml = simplexml_load_string($str);
$namespaces = $xml->getNamespaces(true);
// Here we are saying that we want the Body node in the SOAP-ENV namespace
$body = $xml->children( $namespaces['SOAP-ENV'] )->Body;
// Inside that Body node, we want to get into the mex namespace
$mex = $body->children( $namespaces['mex'] );
// We want the MetadataSections that are in each of the mex namespaces
$metadataSections = $mex->Metadata->MetadataSection;
// Loop through each of the MetadataSections
foreach( $metadataSections as $meta )
{
// Get inside the wsdp namespace
$wsdp = $meta->children( $namespaces['wsdp'] );
// Check if there is a Hosted node inside a Relationship node
if( isset( $wsdp->Relationship->Hosted ) )
{
// Get the wsa namespace inside the Hosted node
$wsa = $wsdp->Relationship->Hosted->children( $namespaces['wsa'] );
// If there is an Address inside the EndpointReference node
if( isset( $wsa->EndpointReference->Address ) )
{
// Then output it
echo $wsa->EndpointReference->Address;
}
}
}
As an extremely simple example - if you just wanted the wsa:Address element...
$myxml1 = simplexml_load_string($test_1);
$myxml1->registerXPathNamespace("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
echo "wsa:Address=".(string)$myxml1->xpath("//wsa:Address")[0];
This just makes sure that the wsa namespace is registered with the document and available to XPath expressions. Then the XPath expression just says - fetch element wsa:Address from anywhere in the document. But as xpath returns a list of all matches (even if there is only 1) so use [0] to get the first item. This outputs...
wsa:Address=http://192.168.1.20:3910/
If you needed more data around the (for example) <wsdp:Hosted> element, you could do something like...
$myxml1 = simplexml_load_string($test_1);
$myxml1->registerXPathNamespace("wsdp", "http://schemas.xmlsoap.org/ws/2006/02/devprof");
$hosted = $myxml1->xpath("//wsdp:Hosted")[0];
$hostedWSA = $hosted->children("wsa", true);
echo "wsa:Address=".(string)$hostedWSA->EndpointReference->Address.PHP_EOL;
$hostedWSPD = $hosted->children("wsdp", true);
echo "wsdp:Types=".(string)$hostedWSPD->Types.PHP_EOL;
So instead this starts of by fetching the correct element and then working with the various child nodes in the different namespaces within that node.

PHP: get values from node by name (problems with namespaces)

I received these xml from external services:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href=(...)?>
<pos:Document xmlns:pos=(...) xmlns:str=(...) xmlns:xsi=(...) xsi:schemaLocation=(...)>
<pos:DescribeDocument>
(...)
</pos:DescribeDocument>
<pos:UPP>
(...)
</pos:UPP>
<ds:Signature Id="ID-9326" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo Id="ID-9325" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pos="adress" xmlns:str="adress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
(...)
</ds:SignedInfo>
<ds:Object>
<xades:QualifyingProperties Id="ID-9337a6d1" Target="#ID-932668c0-d4f9-11e3-bb2d-001a645ad128" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
<xades:SignedProperties Id="ID-9337a6d0" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pos="adress" xmlns:str="adress" xmlns:xades="adress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xades:SignedSignatureProperties>
<xades:SigningTime>sometime</xades:SigningTime>
<xades:SigningCertificate>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="adress"/>
<ds:DigestValue>someValue</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>CNsomeValue</ds:X509IssuerName>
<ds:X509SerialNumber>SerialsomeValue</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificate>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyImplied/>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat ObjectReference="#ID-93257e60">
<xades:Description>NEEDVALUE</xades:Description>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</pos:Document>
It's have a few namespace. And I have to get value in value.
I wrote a some code but nothing works:
$xmlFileContent = file_get_contents($pathToXML);
$dom = new SimpleXMLElement($xmlFileContent, LIBXML_COMPACT);
$namespaces = $dom->getNamespaces(true);
foreach ($namespaces as $key => $namespace) {
$dom->registerXPathNamespace($key, $namespace);
}
$matches = $dom->xpath('//xades:Description'); //no success
and
$doms = new DOMDocument;
$doms->loadXML($path);
foreach($doms->getElementsByTagNameNS($namespaces['xades'],'*') as $element){
echo 'local name: ', $element->localName, ', prefix: ', $element->prefix, "\n"; //no success
}
Can you help me to get to these node (xades:Description)?
PS:
i used it too (but no success):
$result1 = $dom->xpath('/Dokument/ds:Signature/ds:Object/xades:QualifyingProperties/xades:SignedProperties/xades:SignedDataObjectProperties/xades:DataObjectFormat/xades:Description');
You removed the namespace definitions from your XML. If you see an attribute like 'xmlns:xades' the actual namespace is the value of that attribute. It defines an alias/prefix for that namespace in the current context. Most of the time URLs are used as namespace identifiers (because it avoids potential conflicts). But a value like urn:somestring is valid.
You need to register a prefix for the namespace on the DOMXpath object, too. This prefix does not need to be identical to the one in the document. Look at it this way:
DOMDocument resolves a node name from prefix:Description to {namespace-uri}:Description.
DOMXpath resolves the node names in the Xpath expressions the same way using its own definition. For example //prefix:Description to //{namespace-uri}:Description. Then it compares the resolved names.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$xpath->registerNamespace('x', 'urn:xades');
var_dump($xpath->evaluate('string(//x:Description)'));
Output: https://eval.in/181304
string(9) "NEEDVALUE"
There is possible workaround by modifying your XPath to ignore namespaces, just in case you can't find proper solution :
$result = $dom->xpath('//*[local-name() = "Description"]');

Categories