So, after doing some studying I have successfully managed to parse some XML that I'm getting via Guzzle via simplexml_load_string. The issue is then when I then subsequently try to dispatch a job for each of the children using the following code I get a "Serialization of 'SimpleXMLElement' is not allowed" error.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
So to try and fix this I can use the following trick to convert the XML into an array;
json_decode(json_encode($child))
however, while this does mean I can send the data to the new job, it does mean that, as far as I can work out, I have no way to access the #attributes. An alternative would be something along the lines of the following;
// ParentJob
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML, true), $this->otherVar);
}
// ChildJob
public function __construct($xml, $otherVar)
{
$this->xml = simplexml_load_string($xml);
$this->otherVar = $otherVar;
}
however it still throws a serialization error on the dispatch for some reason that I cannot work out, since it sould only be sending raw XML and not an object.
So my main question is what would be the correct way to pass and child SimpleXMLObject to a job in Laravel 5.3 ?
(short of something like looping through all the nodes/attributes and building my own collection from them)
Converting the XML into JSON that way means loosing data. I suggest keeping the XML if possible.
SimpleXMLElement::asXML() is a method. Do not forget the brackets.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML(), true), $this->otherVar);
}
Calling it as a property means that SimpleXML tries to interpret it as a child element node. This means it will be an (empty) SimpleXMLElement.
Here is a small example showing the behavior:
$node = new SimpleXMLElement('<foo/>');
var_dump($node->asXml);
var_dump($node->asXml->getName());
var_dump($node->asXml());
Output:
object(SimpleXMLElement)#2 (0) {
}
string(0) ""
string(29) "<?xml version="1.0"?>
<foo/>
"
The SimpleXmlElement can be converted to array as follows:
$xml = <<<'XML'
<root>
<x a="a1">1</x>
<y b="b2">2</y>
<z>3</z>
</root>
XML;
$xe = simplexml_load_string($xml);
$a = $xe->xpath('*');
$a = array_map(function ($e) {
$item = (array) $e;
$item['nodeName'] = $e->getName();
return $item;
}, $a);
// Now `$a` is an array (serializable object)
echo json_encode($a, JSON_PRETTY_PRINT);
Output
[
{
"#attributes": {
"a": "a1"
},
"0": "1",
"nodeName": "x"
},
{
"#attributes": {
"b": "b2"
},
"0": "2",
"nodeName": "y"
},
{
"0": "3",
"nodeName": "z"
}
]
Note, you can get the string value of a SimpleXmlElement by casting it to string:
$item['value'] = (string) $e;
Since xpath method supports relative XPath expressions, the asterisk should work even with namespaced XMLs. Consider using the DOM extension, as it is much more flexible than SimpleXML. In particular, its DOMXPath class allows to register namespaces and use the registered identifiers in the XPath expressions:
$xpath->registerNamespace('myprefix', 'http://example.com/ns');
$xpath->query('/root/myprefix:*');
As it turns out the entire reason it was not working was due to me using simplexml_load_string() on the child jobs constructor, which was turning it into a simpleXMLElement before the job was actually serialized and pushed onto the queue. the correct way to do it was to parse the XML string on the handle method, which is done after the job has been pulled from the queue for actual processing.
Now this works I can simply dispatch the child job with $child->asXML, and parse it when the job is actually being processed, meaning I can still use all the nifty simpleXML features such as attributes().
Example ParentJob:
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
Example ChildJob:
protected $xml;
protected $otherVar;
public function __construct($xml, $otherVar)
{
$this->xml = $xml;
$this->otherVar = $otherVar;
}
public function handle()
{
$child = simplexml_load_string($this->xml);
$attributes = $child->attributes();
}
Related
im trying to filter out some informations from an XML file using PHP xpath, somehow the response seems always to be empty. Does anyone have an idea what could cause this?
Xml example:
<account>
<a>Information1</a>
<b>Information2</b>
<c>Informatio3</c>
</account>
My actual code:
public static function user_cache($username) {
$xml = #file_get_contents('xmlfile');
$xml = #simplexml_load_string($xml);
if ( $xml ) {
$results = array(
"userid" => $xml->xpath('a')
);
}
else {
$results['message'] = 'Unable to get data.';
}
return $results;
}
{
"userid": []
}
This is more of a XPATH question. The correct syntax would be
$xml->xpath('//a')
You can find more information about XPATH's syntax here: http://www.w3schools.com/xsl/xpath_syntax.asp
On the other hand, xpath method returns an array of SimpleXMLElement, so take that into account.
http://php.net/manual/en/simplexmlelement.xpath.php
Is there any way to convert json to xml in PHP? I know that xml to json is very much possible.
If you're willing to use the XML Serializer from PEAR, you can convert the JSON to a PHP object and then the PHP object to XML in two easy steps:
include("XML/Serializer.php");
function json_to_xml($json) {
$serializer = new XML_Serializer();
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
}
else {
return null;
}
}
It depends on how exactly you want you XML to look like. I would try a combination of json_decode() and the PEAR::XML_Serializer (more info and examples on sitepoint.com).
require_once 'XML/Serializer.php';
$data = json_decode($json, true)
// An array of serializer options
$serializer_options = array (
'addDecl' => TRUE,
'encoding' => 'ISO-8859-1',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$Serializer = &new XML_Serializer($serializer_options);
$status = $Serializer->serialize($data);
if (PEAR::isError($status)) die($status->getMessage());
echo '<pre>';
echo htmlspecialchars($Serializer->getSerializedData());
echo '</pre>';
(Untested code - but you get the idea)
Crack open the JSON with json_decode, and traverse it to generate whatever XML you want.
In case you're wondering, there is no canonical mapping between JSON and XML, so you have to write the XML-generation code yourself, based on the needs of your application.
I combined the two earlier suggestions into:
/**
* Convert JSON to XML
* #param string - json
* #return string - XML
*/
function json_to_xml($json)
{
include_once("XML/Serializer.php");
$options = array (
'addDecl' => TRUE,
'encoding' => 'UTF-8',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$serializer = new XML_Serializer($options);
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
} else {
return null;
}
}
A native approch might be
function json_to_xml($obj){
$str = "";
if(is_null($obj))
return "<null/>";
elseif(is_array($obj)) {
//a list is a hash with 'simple' incremental keys
$is_list = array_keys($obj) == array_keys(array_values($obj));
if(!$is_list) {
$str.= "<hash>";
foreach($obj as $k=>$v)
$str.="<item key=\"$k\">".json_to_xml($v)."</item>".CRLF;
$str .= "</hash>";
} else {
$str.= "<list>";
foreach($obj as $v)
$str.="<item>".json_to_xml($v)."</item>".CRLF;
$str .= "</list>";
}
return $str;
} elseif(is_string($obj)) {
return htmlspecialchars($obj) != $obj ? "<![CDATA[$obj]]>" : $obj;
} elseif(is_scalar($obj))
return $obj;
else
throw new Exception("Unsupported type $obj");
}
Another option would be to use a JSON streaming parser.
Using a streamer parser would come in handy if you want to bypass the intermediate object graph created by PHP when using json_decode. For instance, when you got a large JSON document and memory is an issue, you could output the XML with XMLWriter directly while reading the document with the streaming parser.
One example would be https://github.com/salsify/jsonstreamingparser
$writer = new XMLWriter;
$xml->openURI('file.xml');
$listener = new JSON2XML($writer); // you need to write the JSON2XML listener
$stream = fopen('doc.json', 'r');
try {
$parser = new JsonStreamingParser_Parser($stream, $listener);
$parser->parse();
} catch (Exception $e) {
fclose($stream);
throw $e;
}
The JSON2XML Listener would need to implement the Listener interface:
interface JsonStreamingParser_Listener
{
public function start_document();
public function end_document();
public function start_object();
public function end_object();
public function start_array();
public function end_array();
public function key($key);
public function value($value);
}
At runtime, the listener will receive the various events from the parser, e.g. when the parser finds an object, it will send the data to the start_object() method. When it finds an array, it will trigger start_array() and so on. In those methods you'd then delegate the values to the appropriate methods in the XMLWriter, e.g. start_element() and so on.
Disclaimer: I am not affiliated with the author, nor have I used the tool before. I picked this library because the API looked sufficiently simple to illustrate how to use an event based JSON parser.
Is there any way to convert json to xml in PHP? I know that xml to json is very much possible.
If you're willing to use the XML Serializer from PEAR, you can convert the JSON to a PHP object and then the PHP object to XML in two easy steps:
include("XML/Serializer.php");
function json_to_xml($json) {
$serializer = new XML_Serializer();
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
}
else {
return null;
}
}
It depends on how exactly you want you XML to look like. I would try a combination of json_decode() and the PEAR::XML_Serializer (more info and examples on sitepoint.com).
require_once 'XML/Serializer.php';
$data = json_decode($json, true)
// An array of serializer options
$serializer_options = array (
'addDecl' => TRUE,
'encoding' => 'ISO-8859-1',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$Serializer = &new XML_Serializer($serializer_options);
$status = $Serializer->serialize($data);
if (PEAR::isError($status)) die($status->getMessage());
echo '<pre>';
echo htmlspecialchars($Serializer->getSerializedData());
echo '</pre>';
(Untested code - but you get the idea)
Crack open the JSON with json_decode, and traverse it to generate whatever XML you want.
In case you're wondering, there is no canonical mapping between JSON and XML, so you have to write the XML-generation code yourself, based on the needs of your application.
I combined the two earlier suggestions into:
/**
* Convert JSON to XML
* #param string - json
* #return string - XML
*/
function json_to_xml($json)
{
include_once("XML/Serializer.php");
$options = array (
'addDecl' => TRUE,
'encoding' => 'UTF-8',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$serializer = new XML_Serializer($options);
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
} else {
return null;
}
}
A native approch might be
function json_to_xml($obj){
$str = "";
if(is_null($obj))
return "<null/>";
elseif(is_array($obj)) {
//a list is a hash with 'simple' incremental keys
$is_list = array_keys($obj) == array_keys(array_values($obj));
if(!$is_list) {
$str.= "<hash>";
foreach($obj as $k=>$v)
$str.="<item key=\"$k\">".json_to_xml($v)."</item>".CRLF;
$str .= "</hash>";
} else {
$str.= "<list>";
foreach($obj as $v)
$str.="<item>".json_to_xml($v)."</item>".CRLF;
$str .= "</list>";
}
return $str;
} elseif(is_string($obj)) {
return htmlspecialchars($obj) != $obj ? "<![CDATA[$obj]]>" : $obj;
} elseif(is_scalar($obj))
return $obj;
else
throw new Exception("Unsupported type $obj");
}
Another option would be to use a JSON streaming parser.
Using a streamer parser would come in handy if you want to bypass the intermediate object graph created by PHP when using json_decode. For instance, when you got a large JSON document and memory is an issue, you could output the XML with XMLWriter directly while reading the document with the streaming parser.
One example would be https://github.com/salsify/jsonstreamingparser
$writer = new XMLWriter;
$xml->openURI('file.xml');
$listener = new JSON2XML($writer); // you need to write the JSON2XML listener
$stream = fopen('doc.json', 'r');
try {
$parser = new JsonStreamingParser_Parser($stream, $listener);
$parser->parse();
} catch (Exception $e) {
fclose($stream);
throw $e;
}
The JSON2XML Listener would need to implement the Listener interface:
interface JsonStreamingParser_Listener
{
public function start_document();
public function end_document();
public function start_object();
public function end_object();
public function start_array();
public function end_array();
public function key($key);
public function value($value);
}
At runtime, the listener will receive the various events from the parser, e.g. when the parser finds an object, it will send the data to the start_object() method. When it finds an array, it will trigger start_array() and so on. In those methods you'd then delegate the values to the appropriate methods in the XMLWriter, e.g. start_element() and so on.
Disclaimer: I am not affiliated with the author, nor have I used the tool before. I picked this library because the API looked sufficiently simple to illustrate how to use an event based JSON parser.
I'm working on a new class to wrap XML handling. I want my class to use simplexml if it's installed, and the built in XML functions if it's not. Can anyone give me some suggestions on a skeleton class to do this? It seems "wrong" to litter each method with a bunch of if statements, and that also seems like it would make it nearly impossible to correctly test.
Any upfront suggestions would be great!
EDIT: I'm talking about these built-in xml functions.
Which built-in xml functions are you referring to? SimpleXml is a standard extension, which uses libxml underneath - just as the dom extension does. So if the dom extension is installed, chances are that so is SimpleXml.
I've made a class which wraps SimpleXml functionality... take what you may from it...
bXml.class.inc
There is one weird thing... it's that SimpleXml doesn't allow its constructor to be overloaded, so you can't do things at initiation ... like override the input value (i.e. so you can accept XML as in input). I got around that limitation by using an ArrayObject class to wrap the new SimpleXml class.
I use something like this for doing xml translations and content:
Assuming xml structure something like this (important to use a regular structure, means you can pull off some nice agile tricks!):
<word name="nameofitem">
<en>value</en>
<pt>valor</pt>
<de>value_de</de>
</word>
and then a class to handle the xml:
class translations
{
public $xml = null;
private $file = null;
private $dom = null;
function __construct($file="translations") {
// get xml
$this->file = $file;
$this->haschanges = false;
$this->xml = file_get_contents($_SERVER['DOCUMENT_ROOT']."/xml/".$file.".xml");
$this->dom = new DOMdocument();
$this->dom->loadXML($this->xml);
}
function updateNode($toupdate, $newvalue, $lang="pt",$rootnode="word"){
$this->haschanges = true;
$nodes = $this->dom->getElementsByTagName($rootnode);
foreach ($nodes as $key => $value) {
if ($value->getAttribute("name")==$toupdate) {
$nodes->item($key)->getElementsByTagName($lang)->item(0)->nodeValue = htmlspecialchars($newvalue,ENT_QUOTES,'UTF-8');
}
}
}
function saveUpdated(){
$toSave = $this->dom->saveXML();
if ($this->haschanges === true) {
file_put_contents($_SERVER['DOCUMENT_ROOT']."/xml/".$this->file.".xml", $toSave);
return true;
}
else {
return false;
}
}
}
I took out a few of the methods I have, for brevity, but I extend this with things to handle file and image uploads etc too.
Once you have all this you can do:
$xml = new translations();
// loop through all the language posts
foreach ($_POST["xml"]["en"] as $key => $value) {
$xml->updateNode($key, stripslashes($value), "en");
}
Or something ;) hope this gives you some ideas!
Is there any way to convert json to xml in PHP? I know that xml to json is very much possible.
If you're willing to use the XML Serializer from PEAR, you can convert the JSON to a PHP object and then the PHP object to XML in two easy steps:
include("XML/Serializer.php");
function json_to_xml($json) {
$serializer = new XML_Serializer();
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
}
else {
return null;
}
}
It depends on how exactly you want you XML to look like. I would try a combination of json_decode() and the PEAR::XML_Serializer (more info and examples on sitepoint.com).
require_once 'XML/Serializer.php';
$data = json_decode($json, true)
// An array of serializer options
$serializer_options = array (
'addDecl' => TRUE,
'encoding' => 'ISO-8859-1',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$Serializer = &new XML_Serializer($serializer_options);
$status = $Serializer->serialize($data);
if (PEAR::isError($status)) die($status->getMessage());
echo '<pre>';
echo htmlspecialchars($Serializer->getSerializedData());
echo '</pre>';
(Untested code - but you get the idea)
Crack open the JSON with json_decode, and traverse it to generate whatever XML you want.
In case you're wondering, there is no canonical mapping between JSON and XML, so you have to write the XML-generation code yourself, based on the needs of your application.
I combined the two earlier suggestions into:
/**
* Convert JSON to XML
* #param string - json
* #return string - XML
*/
function json_to_xml($json)
{
include_once("XML/Serializer.php");
$options = array (
'addDecl' => TRUE,
'encoding' => 'UTF-8',
'indent' => ' ',
'rootName' => 'json',
'mode' => 'simplexml'
);
$serializer = new XML_Serializer($options);
$obj = json_decode($json);
if ($serializer->serialize($obj)) {
return $serializer->getSerializedData();
} else {
return null;
}
}
A native approch might be
function json_to_xml($obj){
$str = "";
if(is_null($obj))
return "<null/>";
elseif(is_array($obj)) {
//a list is a hash with 'simple' incremental keys
$is_list = array_keys($obj) == array_keys(array_values($obj));
if(!$is_list) {
$str.= "<hash>";
foreach($obj as $k=>$v)
$str.="<item key=\"$k\">".json_to_xml($v)."</item>".CRLF;
$str .= "</hash>";
} else {
$str.= "<list>";
foreach($obj as $v)
$str.="<item>".json_to_xml($v)."</item>".CRLF;
$str .= "</list>";
}
return $str;
} elseif(is_string($obj)) {
return htmlspecialchars($obj) != $obj ? "<![CDATA[$obj]]>" : $obj;
} elseif(is_scalar($obj))
return $obj;
else
throw new Exception("Unsupported type $obj");
}
Another option would be to use a JSON streaming parser.
Using a streamer parser would come in handy if you want to bypass the intermediate object graph created by PHP when using json_decode. For instance, when you got a large JSON document and memory is an issue, you could output the XML with XMLWriter directly while reading the document with the streaming parser.
One example would be https://github.com/salsify/jsonstreamingparser
$writer = new XMLWriter;
$xml->openURI('file.xml');
$listener = new JSON2XML($writer); // you need to write the JSON2XML listener
$stream = fopen('doc.json', 'r');
try {
$parser = new JsonStreamingParser_Parser($stream, $listener);
$parser->parse();
} catch (Exception $e) {
fclose($stream);
throw $e;
}
The JSON2XML Listener would need to implement the Listener interface:
interface JsonStreamingParser_Listener
{
public function start_document();
public function end_document();
public function start_object();
public function end_object();
public function start_array();
public function end_array();
public function key($key);
public function value($value);
}
At runtime, the listener will receive the various events from the parser, e.g. when the parser finds an object, it will send the data to the start_object() method. When it finds an array, it will trigger start_array() and so on. In those methods you'd then delegate the values to the appropriate methods in the XMLWriter, e.g. start_element() and so on.
Disclaimer: I am not affiliated with the author, nor have I used the tool before. I picked this library because the API looked sufficiently simple to illustrate how to use an event based JSON parser.