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!
Related
I am using an API however the way that they setup their returned XML is incorrect so I am needing to come up with a solution for parsing it. i am unable to convert to JSON (my preferred return method) because they don't support it. Below I have listed my XML and PHP.
XML Returned by API
<?xml version="1.0" encoding="utf-8"?>
<interface-response>
<Domain>example.com</Domain>
<Code>211</Code>
<Domain>example.net</Domain>
<Code>210</Code>
<Domain>example.org</Domain>
<Code>211</Code>
</interface-response>
Each Code is for the previous domain. I have no idea how to tie these two together and still be able to loop through all of the results returned. There will essentially be one Domain and one Code returned for each Top Level Domain, so a lot of results.
PHP code so far:
<?php
$xml = new SimpleXMLElement($data);
$html .= '<table>';
foreach($xml->children() as $children){
$html .= '<tr>';
$html .= '<td>'.$xml->Domain.'</td>';
if($xml->Code == 211){
$html .= '<td>This domain is not avaliable.</td>';
}elseif($xml->Code == 210){
$html .= '<td>This domain is avaliable.</td>';
}else{
$html .= '<td>I have no idea.</td>';
}
$html .= '<tr>';
}
$html .= '</table>';
echo $html;
?>
If you don't want to deal with crappy XML (I'm not saying XML is crappy in general, but this one is) you could consider something like this:
<?php
$responses = [];
$responses['210'] = 'This domain is avaliable.';
$responses['211'] = 'This domain is not avaliable.';
$xml = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<interface-response>
<Domain>example.com</Domain>
<Code>211</Code>
<Domain>example.net</Domain>
<Code>210</Code>
<Domain>example.org</Domain>
<Code>211</Code>
</interface-response>
XML;
$data = (array) simplexml_load_string($xml);
$c = count($data['Domain']);
for($i = 0; $i < $c; $i++)
{
echo $data['Domain'][$i], PHP_EOL;
echo array_key_exists($data['Code'][$i], $responses) ? $responses[$data['Code'][$i]] : 'I have no idea', PHP_EOL;
}
Output
example.com
This domain is not avaliable.
example.net
This domain is avaliable.
example.org
This domain is not avaliable.
I'm now using the function fwrite(); in PHP. But i want to locate my new things after a specific rule.
This wil be the output.
<?xml version="1.0" encoding="utf-8" ?>
<logs>
<log type="text">the new log</log>
<log type="text>the old log</log>
<log type="login">some other log.</log>
</logs>
How can i get the new log in the new log and not on the end. I only can find something like file_get_contents and then str_replace. But that seems really not efficient.
My php Code:
$file = $this->path.'logs.xml';
// Open our file. And Create file if it doesn't exsist
$fopen = fopen($file, "w+");
// Looks if file is empty.
if(filesize($file) == 0) {
/*
* Put your data in XML data.
*/
$xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \r\n";
$xmlData .= "<logs> \r\n";
$xmlData .= "\t<log type=\"".$data[0]."\">\r\n";
$xmlData .= "\t\t<author>".$data[1]."</author>\r\n";
$xmlData .= "\t\t<action>".$data[2]."</action>\r\n";
$xmlData .= "\t\t<result>".$data[3]."</result>\r\n";
$xmlData .= "\t\t<note>".$data[4]."</note>\r\n";
$xmlData .- "\t</log>\r\n";
$xmlData .= "</logs>";
} else {
}
if(is_writeable($file)) {
fwrite($fopen, $xmlData);
return true;
}
return false;
fclose($fopen);
Sincerely thank you.
Well, you're lucky your data is in XML. PHP has got a bunch of easy to use libraries (extensions) that deal with XML data. For example SimpleXML or the more capable DOM (both extension are enabled by default).
<?php
$filename = $this->path.'logs.xml';
if (!file_exists($filename)) {
// Here's your code from above, although it would be easier to use
// the libraries here, as well
} else {
$logs = simplexml_load_file($filename);
// See if there's a "text" log element
$txtlog = $logs->xpath('./log[#type = "text"]');
...
}
You could use the array_splice method. This way you can insert a new element in an array at any position.
$file = $this->path.'logs.xml';
$content = file($file); //is array with all lines as elements.
/*
0: <?xml version="1.0" encoding="utf-8" ?>
1: <logs>
2: <log type="text>the old log</log>
3: <log type="login">some other log.</log>
4: </logs>
*/
//insert the new line at position 2
array_splice( $content, 2, 0, ' <log type="text">the new log</log>' );
/*
0: <?xml version="1.0" encoding="utf-8" ?>
1: <logs>
2: <log type="text">the new log</log>
3: <log type="text>the old log</log>
4: <log type="login">some other log.</log>
5: </logs>
*/
$fopen = fopen($file, "w+");
fwrite($fopen, implode("\n", $content);
fclose($fopen);
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();
}
This snippet of code use to work perfectly, nothing has changed except now it's giving off errors.
function wrapOutput($str){
header('content-type: text/xml; charset: utf-8');
$o = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
$o .= ' <rss version="2.0"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:amp="http://www.adobe.com/amp/1.0">' . PHP_EOL;
$o .= ' <channel>' . PHP_EOL;
$o .= $str;
$o .= ' </channel>' . PHP_EOL;
$o .= ' </rss>' . PHP_EOL;
return $o;
}
Something is going wrong and turning the xml tags into <'xml version="1.0" encoding="UTF-8"'>
Which results into the following error
XML Parsing Error: not well-formed
Location: http://localhost/mrss.php?feed=test
Line Number 1, Column 2:<'xml version="1.0" encoding="UTF-8"'>
-^
I think the problem is de quotes, beacause XML read like eval(), sou u can try escape \"\" quotes
I'm using the W3 validator API, and I get this kind of response:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<m:markupvalidationresponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding" xmlns:m="http://www.w3.org/2005/10/markup-validator">
<m:uri>http://myurl.com/</m:uri>
<m:checkedby>http://validator.w3.org/</m:checkedby>
<m:doctype>-//W3C//DTD XHTML 1.1//EN</m:doctype>
<m:charset>utf-8</m:charset>
<m:validity>false</m:validity>
<m:errors>
<m:errorcount>1</m:errorcount>
<m:errorlist>
<m:error>
<m:line>7</m:line>
<m:col>80</m:col>
<m:message>character data is not allowed here</m:message>
<m:messageid>63</m:messageid>
<m:explanation> <![CDATA[
PAGE HTML IS HERE
]]>
</m:explanation>
<m:source><![CDATA[ HTML AGAIN ]]></m:source>
</m:error>
...
</m:errorlist>
</m:errors>
<m:warnings>
<m:warningcount>0</m:warningcount>
<m:warninglist>
</m:warninglist>
</m:warnings>
</m:markupvalidationresponse>
</env:Body>
</env:Envelope>
How can I extract some variables from there?
I need validity, errorcount and if possible from the list of errors: line, col, and message :)
Is there a easy way to do this?
You can load the XML string into a SimpleXMLElement with simplexml_load_string and then find the attributes using XPath. It's important to register the namespaces involved with registerXPathNamespace before using XPath.
$xml = file_get_contents('example.xml'); // $xml should be the XML source string
$doc = simplexml_load_string($xml);
$doc->registerXPathNamespace('m', 'http://www.w3.org/2005/10/markup-validator');
$nodes = $doc->xpath('//m:markupvalidationresponse/m:validity');
$validity = strval($nodes[0]);
echo 'is valid: ', $validity, "\n";
$nodes = $doc->xpath('//m:markupvalidationresponse/m:errors/m:errorcount');
$errorcount = strval($nodes[0]);
echo 'total errors: ', $errorcount, "\n";
$nodes = $doc->xpath('//m:markupvalidationresponse/m:errors/m:errorlist/m:error');
foreach ($nodes as $node) {
$nodes = $node->xpath('m:line');
$line = strval($nodes[0]);
$nodes = $node->xpath('m:col');
$col = strval($nodes[0]);
$nodes = $node->xpath('m:message');
$message = strval($nodes[0]);
echo 'line: ', $line, ', column: ', $col, ' message: ', $message, "\n";
}
You should be using a SOAP library to get this in the first place. There are various options you can try for this; nusoap, http://php.net/manual/en/book.soap.php, the zend framework also has SOAP client and server which you can use. Whatever implementation you use will allow you to get the data in some way. Doing a var_dump() on whatever holds the initial response should aid you in navigating through it.
If you rather use the DOMDocument class from php. You don't have to know Xpath to get this working. An example:
$url = "http://www.google.com";
$xml = new DOMDocument();
$xml->load("http://validator.w3.org/check?uri=".urlencode($url)."&output=soap12");
$doctype = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'doctype')->item(0)->nodeValue;
$valid = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'validity')->item(0)->nodeValue;
$errorcount = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'errorcount')->item(0)->nodeValue;
$warningcount = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'warningcount')->item(0)->nodeValue;
$errors = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'error');
foreach ($errors as $error) {
echo "<br>line: ".$error->childNodes->item(1)->nodeValue;
echo "<br>col: ".$error->childNodes->item(3)->nodeValue;
echo "<br>message: ".$error->childNodes->item(5)->nodeValue;
}
// item() arguments are uneven because the empty text between tags is counted as an item.