validate a xml file against a xsd using php - php

how to validate a xml file against a xsd? there is domdocument::schemaValidate() but It does not tell where are the errors. is there any class for that? does it have any worth making that parser from scratch? or is it just reinventing he wheel,

This code does the business:
$xml= new DOMDocument();
$xml->loadXML(<A string goes here containing the XML data>, LIBXML_NOBLANKS); // Or load if filename required
if (!$xml->schemaValidate(<file name for the XSD file>)) // Or schemaValidateSource if string used.
{
// You have an error in the XML file
}
See the code in http://php.net/manual/en/domdocument.schemavalidate.php To retrieve the errors.
I.e.
justin at redwiredesign dot com 08-Nov-2006 03:32 post.

User contrib from http://php.net/manual/en/domdocument.schemavalidate.php
It works like a charm!
For more detailed feedback from DOMDocument::schemaValidate, disable
libxml errors and fetch error information yourself. See
http://php.net/manual/en/ref.libxml.php for more info.
example.xml
<?xml version="1.0"?>
<example>
<child_string>This is an example.</child_string>
<child_integer>Error condition.</child_integer>
</example>
example.xsd
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element name="example">
<xs:complexType>
<xs:sequence>
<xs:element name="child_string" type="xs:string"/>
<xs:element name="child_integer" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
PHP
<?php
function libxml_display_error($error)
{
$return = "<br/>\n";
switch ($error->level) {
case LIBXML_ERR_WARNING:
$return .= "<b>Warning $error->code</b>: ";
break;
case LIBXML_ERR_ERROR:
$return .= "<b>Error $error->code</b>: ";
break;
case LIBXML_ERR_FATAL:
$return .= "<b>Fatal Error $error->code</b>: ";
break;
}
$return .= trim($error->message);
if ($error->file) {
$return .= " in <b>$error->file</b>";
}
$return .= " on line <b>$error->line</b>\n";
return $return;
}
function libxml_display_errors() {
$errors = libxml_get_errors();
foreach ($errors as $error) {
print libxml_display_error($error);
}
libxml_clear_errors();
}
// Enable user error handling
libxml_use_internal_errors(true);
$xml = new DOMDocument();
$xml->load('example.xml');
if (!$xml->schemaValidate('example.xsd')) {
print '<b>DOMDocument::schemaValidate() Generated Errors!</b>';
libxml_display_errors();
}
?>

This is a complete code snippet for displaying xsd validation errors:
$xml = '<test/>';
$xsd = '/path/to/xsd';
// needed for getting errors
libxml_use_internal_errors(true);
$domDocument= new DOMDocument();
$domDocument->loadXML($xml);
if (!$domDocument->schemaValidate($xsd)) {
$errors = libxml_get_errors();
foreach ($errors as $error) {
print_r($error);
}
libxml_clear_errors();
}

Related

PHP updating xml CDATA fields with DOMdocument

I have the following XML data:
<?xml version="1.0" encoding="utf-8"?>
<source>
<publisher>some-data</publisher>
<publisherurl>some-data</publisherurl>
<lastBuildDate>a-date</lastBuildDate>
<element>
<sub-element><![CDATA[some-data]]></sub-element>
</element>
</source>
I'm trying to use PHP's built in DOMdocument parser to update the text inside sub-element.
I've tried:
$dom=new DOMDocument();
$dom->load("document.xml");
$ele=$root->getElementsByTagName('element');
foreach ($ele as $e) {
$e->getElementsByTagName('sub-element')->item(0)->nodeValue = "new val";
}
this kind of works but it removes the CDATA and just replaces it with new-val. I want to preserve the CDATA field so I tried the following:
$dom=new DOMDocument();
$dom->load("document.xml");
$ele=$root->getElementsByTagName('element');
foreach ($ele as $e) {
$sub=$e->getElementsByTagName('sub-element');
foreach($sub->childNodes as $child) {
if ($child->nodeType == XML_CDATA_SECTION_NODE) {
$child->nodeValue = 'new-val';
}
}
}
This seems like it should work but PHP returns the following Notice
Undefined property: DOMNodeList::$childNodes
Feel like I'm on the right path but I just can't figure out what I'm doing wrong here. Does anyone know how to fix?
My end goal output is:
<?xml version="1.0" encoding="utf-8"?>
<source>
<publisher>some-data</publisher>
<publisherurl>some-data</publisherurl>
<lastBuildDate>a-date</lastBuildDate>
<element>
<sub-element><![CDATA[new-val]]></sub-element>
</element>
</source>
You need to compare against firstChild
$xml = '<?xml version="1.0" encoding="utf-8"?>
<source>
<publisher>some-data</publisher>
<publisherurl>some-data</publisherurl>
<lastBuildDate>a-date</lastBuildDate>
<element>
<sub-element><![CDATA[some-data]]></sub-element>
</element>
<element>
<sub-element>some-other-data</sub-element>
</element>
</source>';
$dom = new DOMDocument();
$dom->loadXML($xml);
$ele=$dom->getElementsByTagName('element');
foreach ($ele as $e) {
$item = $e->getElementsByTagName('sub-element')->item(0);
if($item->firstChild->nodeType == XML_CDATA_SECTION_NODE) { //<------
//it's CDATA do whatever
$item->firstChild->nodeValue = "new val";
} else {
//it's not , do something else
$item->nodeValue = "new val";
}
}
echo "<pre>";
print_r(htmlentities($dom->saveXML()));
echo "</pre>";
Output:
<?xml version="1.0" encoding="utf-8"?>
<source>
<publisher>some-data</publisher>
<publisherurl>some-data</publisherurl>
<lastBuildDate>a-date</lastBuildDate>
<element>
<sub-element><![CDATA[new val]]></sub-element>
</element>
<element>
<sub-element>new val</sub-element>
</element>
</source>
PS: if you don't have to make a distinction between CDATA or not, just use firstChild
foreach ($ele as $e) {
$item = $e->getElementsByTagName('sub-element')->item(0);
$item->firstChild->nodeValue = "new val";
}

Nusoap error "no operations defined in the WSDL document!"

Hi I'm getting an error:
no operations defined in the WSDL document!
I'm searching all possible ways that might answer my problem for almost a week but no luck I can't find it. I use nusoap library and here's my code:
<?php
include_once('lib/nusoap.php');
$username = 'xxxxx#username.com';
$password = 'xxxxxxx';
$endpoint = 'https://mail.xxxxxxxxx.com/ews/services.wsdl';
$wsdl = true;
$soapclient = new nusoap_client($endpoint, $wsdl);
$xml = '<wsdl:definitions targetNamespace="http://schemas.microsoft.com/exchange/services/2006/messages">';
$xml .= '<wsdl:message name="UploadItemsSoapIn">';
$xml .= '<wsdl:part name="request" element="tns:UploadItems"/>';
$xml .= '<wsdl:part name="Impersonation" element="t:ExchangeImpersonation"/>';
$xml .= '<wsdl:part name="MailboxCulture" element="t:MailboxCulture"/>';
$xml .= '<wsdl:part name="RequestVersion" element="t:RequestServerVersion"/>';
$xml .= '</wsdl:message>';
$xml .= '</wsdl:definitions>';
$operation = 'UploadItemsSoapIn';
$result = $soapclient->call($operation,$xml);
$soapclient->setCredentials($username, $password, 'ntlm');
if ($soapclient->fault) {
echo '<h2>Fault (Expect - The request contains an invalid SOAP body)</h2><pre>'; print_r($result); echo '</pre>';
} else {
$err = $soapclient->getError();
if ($err) {
echo '<h2>Error</h2><pre>' . $err . '</pre>';
} else {
echo '<h2>Result</h2><pre>'; print_r($result); echo '</pre>';
}
}
and this is the view in services.wsdl link:
<wsdl:definitions targetNamespace="http://schemas.microsoft.com/exchange/services/2006/messages">
<wsdl:types>
<xs:schema>
<xs:import namespace="http://schemas.microsoft.com/exchange/services/2006/messages" schemaLocation="messages.xsd"/>
</xs:schema>
</wsdl:types>
<wsdl:message name="UploadItemsSoapIn">
<wsdl:part name="request" element="tns:UploadItems"/>
<wsdl:part name="Impersonation" element="t:ExchangeImpersonation"/>
<wsdl:part name="MailboxCulture" element="t:MailboxCulture"/>
<wsdl:part name="RequestVersion" element="t:RequestServerVersion"/>
</wsdl:message>
<wsdl:message name="UploadItemsSoapOut">
<wsdl:part name="UploadItemsResult" element="tns:UploadItemsResponse"/>
<wsdl:part name="ServerVersion" element="t:ServerVersionInfo"/>
</wsdl:message>
Sorry I'm just a newbie in soap. All I want is just to run the basic function and just display email or names of the login user. Thanks in advance!

Undefined error while loading xml from string to array

I have this really simple xml that I receive via POST form a desktop app:
<?xml version="1.0" encoding="utf-8"?>
<root>
<receipt status="1" id="PAR/2" idreceipt="1" date="YYMMDD" errorstr="" />
<receipt status="1" id="PAR/2/2" idreceipt="2" date="YYYY-MM-DD HH:II:SS" errorstr="" />
<receipt status="0" id="PAR/2/3" idreceipt="3" date="YYYY-MM-DD HH:II:SS" errorstr="ERROR" />
</root>
I save it to a variable and then try to load it to array using simplexml_load_string($string). var_dump($xml) returns false. I know there are no contents, but when I try to print_r the attributes using foreach on every receipt it's empty too. Is the xml not well-formed or am I missing something in the PHP?
Whole PHP:
$string = $_POST['results'];
$xml = simplexml_load_string($string);
foreach ($xml->receipt as $value) {
print_r($value);
}
There is a problem in parsing the XML.
I tried the following code (using some code from http://php.net/manual/en/function.libxml-get-errors.php):
<?php
libxml_use_internal_errors(true);
$string = $_POST['results'];
$loaded_xml = simplexml_load_string($string);
$xml = explode("\n", $string);
$errors = libxml_get_errors();
foreach ($errors as $error) {
echo display_xml_error($error, $xml);
}
foreach ($loaded_xml->receipt as $value) {
print_r($value);
}
function display_xml_error($error, $xml)
{
$return = $xml[$error->line - 1] . "\n";
$return .= str_repeat('-', $error->column) . "^\n";
switch ($error->level) {
case LIBXML_ERR_WARNING:
$return .= "Warning $error->code: ";
break;
case LIBXML_ERR_ERROR:
$return .= "Error $error->code: ";
break;
case LIBXML_ERR_FATAL:
$return .= "Fatal Error $error->code: ";
break;
}
$return .= trim($error->message) .
"\n Line: $error->line" .
"\n Column: $error->column";
if ($error->file) {
$return .= "\n File: $error->file";
}
return "$return\n\n--------------------------------------------\n\n";
}
And I got many errors. Here are the first two:
<?xml version=\"1.0\" encoding=\"utf-8\"?> <root> <receipt status=\"1\" id=\"PAR/2\" idreceipt=\"1\" date=\"YYMMDD\" errorstr=\"\" /> <receipt status=\"1\" id=\"PAR/2/2\" idreceipt=\"2\" date=\"YYYY-MM-DD HH:II:SS\" errorstr=\"\" /> <receipt status=\"0\" id=\"PAR/2/3\" idreceipt=\"3\" date=\"YYYY-MM-DD HH:II:SS\" errorstr=\"ERROR\" /> </root>
--------------^
Fatal Error 33: String not started expecting ' or "
Line: 1
Column: 14
--------------------------------------------
<?xml version=\"1.0\" encoding=\"utf-8\"?> <root> <receipt status=\"1\" id=\"PAR/2\" idreceipt=\"1\" date=\"YYMMDD\" errorstr=\"\" /> <receipt status=\"1\" id=\"PAR/2/2\" idreceipt=\"2\" date=\"YYYY-MM-DD HH:II:SS\" errorstr=\"\" /> <receipt status=\"0\" id=\"PAR/2/3\" idreceipt=\"3\" date=\"YYYY-MM-DD HH:II:SS\" errorstr=\"ERROR\" /> </root>
--------------^
Fatal Error 96: Malformed declaration expecting version
Line: 1
Column: 14
--------------------------------------------
Now, according to this post, This is probably caused by magic_quotes_runtime adding backslashes when you ... .
So, I think this will solve your problem:
<?php
libxml_use_internal_errors(true);
$string = $_POST['results'];
$string = stripslashes($string);
$loaded_xml = simplexml_load_string($string);
// rest of the code is the same as above
So I've tried pretty much everything - reinstalling the module, using the solution given by Ako, but it didn't work. It turned out my server while processing POST turned every special character into character entity and the parser didn't recognize string as a valid XML with < and > instead of actual < > so I added:
$string = html_entity_decode($string);
and it worked perfectly. Hope this helps somebody else!

How can I add custom elements to the detail section of a SoapFault using PHP's SOAP library

I am building a service against Sonos' Music API (SMAPI). Sometimes I have to send back a response in the following format:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>Client.NOT_LINKED_RETRY</faultcode>
<faultstring>Link Code not found retry...</faultstring>
<detail>
<ExceptionInfo>NOT_LINKED_RETRY</ExceptionInfo>
<SonosError>5</SonosError>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
I am building my service using the PHP SOAP library and for the above response I tried throwing a SoapFault like this:
throw new SoapFault('Client.NOT_LINKED_RETRY', 'Link Code not found retry...');
But when I try this the response that is sent back looks like this:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>Client.NOT_LINKED_RETRY</faultcode>
<faultstring>Link Code not found retry...</faultstring>
<detail>
<SonosError/>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Notice that there is no ExceptionInfo and that SonosError is empty. Is it possible to set ExceptionInfo and SonosError using SoapFault? I tried all kinds of things, but couldn't get it working, so as a work around I am doing this now:
http_response_code(500);
header("Content-type: text/xml");
$ret = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
$ret .= '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">';
$ret .= '<SOAP-ENV:Body>';
$ret .= '<SOAP-ENV:Fault>';
$ret .= '<faultcode>Client.NOT_LINKED_RETRY</faultcode>';
$ret .= '<faultstring>Link Code not found retry...</faultstring>';
$ret .= '<detail>';
$ret .= '<ExceptionInfo>NOT_LINKED_RETRY</ExceptionInfo>';
$ret .= '<SonosError>5</SonosError>';
$ret .= '</detail>';
$ret .= '</SOAP-ENV:Fault>';
$ret .= '</SOAP-ENV:Body>';
$ret .= '</SOAP-ENV:Envelope>'."\n";
echo $ret; exit;
Not sure if it's relevant but the WSDL can be found here.
Update: when I try the suggestion below like this:
$detail = new StdClass();
$detail->SonosError = 5;
$detail->ExceptionInfo = 'NOT_LINKED_RETRY';
throw new SoapFault(
'Client.NOT_LINKED_RETRY',
'Link Code not found retry...',
NULL,
$detail
);
I get:
<detail>
<customFault>
<SonosError>5</SonosError>
<ExceptionInfo>NOT_LINKED_RETRY</ExceptionInfo>
</customFault>
</detail>
This is almost what I need, except for <customFault> tag. Is there a way to get rid of it and have SonosError and ExceptionInfo in <detail> directly?
The fact that you don't see the ExceptionInfo tag is because it is not defined in the wsdl. On the other hand SonosError is defined.
First thing first, in order to fill the SonosError you have to pass the arguments.
From here you can see that the constructor has more parameters
SoapFault('code', 'string', 'actor', 'detail', 'name', 'header');
In order to pass the SonosError call it like this
$detail = new StdClass();
$detail->SonosError = 5;
throw new SoapFault('Client.NOT_LINKED_RETRY', 'Link Code not found retry...', null, $details);
As for the ExceptionInfo, the wsdl must be changed. As it is now, the details tag is represented by this sections
<wsdl:message name="customFault">
<wsdl:part name="customFault" element="tns:SonosError"/>
</wsdl:message>
<xs:element name="SonosError" type="xs:int"/>
If you change the above sections with these, you will have what you need.
<wsdl:message name="customFault">
<wsdl:part name="customFault" type="tns:customFaultType" />
</wsdl:message>
<xs:complexType name="customFaultType">
<xs:sequence>
<xs:element name="SonosError" type="xs:int"/>
<xs:element name="ExceptionInfo" type="xs:string"/>
</xs:sequence>
</xs:complexType>
And of course you add the parameter and the array becomes like this
$detail = new StdClass();
$detail->SonosError = 5;
$detail->ExceptionInfo = 'NOT_LINKED_RETRY';

read XML tag id from php

i am have the following XML file
<?xml version="1.0" encoding="iso-8859-1"?>
<Message Id="Language">German</Message>
<Message Id="LangEnglish">German</Message>
<Message Id="TopMakeHomepage">
Mache 4W Consulting Webseite zu deiner Starseite!
</Message>
<Message Id="TopLinkEmpSec">
4W Mitarbeiter
</Message>
<Message Id="TopLinkFeedback">
Feedback
</Message>
<Message Id="TopLinkSiteMap">
Site Map
</Message>
<Message Id="TopLinkContactUs">
Kontakt
</Message>
<Message Id="TopSetLangEn">
ins Englische
</Message>
<Message Id="TopSetLangDe">
ins Deutsche
</Message>
<Message Id="TopSetLangEs">
ins Spanische
</Message>
<Message Id="MenuLinks">
!~|4W Starseite|Company|Über uns|Kontakt|4W anschließen|Services|Kunden Software Entwicklung|Altsystem Neugestalltung & Umwandlung|Altsystem Dokumentation|Daten Umwandlung & Migration|Erstellen von Datenbeschreibungsverzeichnis|System- & Anwendungs Support|Projekt Management & Planunng|Personal Erweiterung|Projekt Ausgliederung|Mitarbeiter Ausbildung|Technologie|Intersystems Caché|M / MUMPS|Zusätzliche Technologien|Methodologie|Feedback|~!
</Message>
</MsgFile>
in this XML file i need to fetch the contents using the tagid . what exactly i need is when i input the 'TopMakeHomepage' i need output as 'Mache 4W Consulting Webseite zu deiner Starseite!' ...
Please help me to find out this . Thanks in advance
Use SimpleXML:
$xml = simplexml_load_file($grabUrl);
foreach ($xml->Message as $message) {
echo $message->attributes()->Id.'<br />';
}
Or use XMLReader, with which you can miss memory leaks when processing large XMLs.
$xml = new XMLReader;
$xml->open($grabUrl);
while ($xml->read()) {
if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'Message')
echo $xml->getAttribute('Id');
}
With the DOM extension it should be something like this:
$dom = new DOMDocument;
$dom->validateOnParse = TRUE;
$dom->loadXML($xmlString); // or use ->load('file.xml')
$node = $dom->getElementById('foo');
echo $node->nodeValue;
See the manual on
DOMDocument::getElementById — Searches for an element with a certain id
If it doesn't work with getElementById (which usually only happens if the DTD doesn't know the id attribute), you can still use XPath to do the query:
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//Message[#id = "foo"]');
foreach($nodes as $node) {
echo $node->nodeValue;
}
Unlike getElementById, an XPath query always returns a DOMNodeList. It will be empty if the query didn't find any nodes.
If the ID is a real XML ID, you can also use the id() function in XPath
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('id("foo")');
foreach($nodes as $node) {
echo $node->nodeValue;
}
See Simplify PHP DOM XML parsing - how? for more details on XML IDs.
For SimpleXML this should do the trick:
$xml = simplexml_load_file($xmlFileLoc);
foreach ($xml->Message as $msg)
{
echo $msg['Id'];
}

Categories