I've been trying hard on this for a while now, but can't find a solution on how to convert json to xml in php by file_get_contents. I don't want to use an online converter but want to have it in my website root directory.
Thanks in advance!
You can convert JSON to array/object by using json_decode(), once you have it inside array you can use any XML Manipulation method to build XML out of it.
For example you may build XML structure like this:
<array>
<data name="atrribute1">string 1</data>
<array name="array_atribute">
<data name="attribute2">string 2</data>
<data name="attribute2">string 2</data>
</array>
</array>
By DOMDocument, and method DOMDocument::createElement()
class XMLSerialize {
protected $dom = null;
public function serialize( $array)
{
$this->dom = new DOMDocument('1.0', 'utf-8');
$element = $this->dom->createElement('array');
$this->dom->appendChild($element);
$this->addData( $element, $array);
}
protected function addData( DOMElement &$element, $array)
{
foreach($array as $k => $v){
$e = null;
if( is_array( $v)){
// Add recursive data
$e = $this->dom->createElement( 'array', $v);
$e->setAttribute( 'name', $k);
$this->addData( $e, $v);
} else {
// Add linear data
$e = $this->dom->createElement( 'data', $v);
$e->setAttribute( 'name', $k);
}
$element->appendChild($e);
}
}
public function get_xml()
{
return $this->dom->saveXML();
}
}
Related
I want to know if passing an XML node and then calling upon a method to access it is legal syntax in PHP. I tried converting to string, but that didn't work.
What am I doing wrong?
What would be the best/simplest alternative?
XML
<user>
<widgets>
<widget>Widget 1</widget>
<stuff>
<morestuff>Things</morestuff>
</stuff>
<stuff>
<morestuff>Things</morestuff>
</stuff>
<widget>Widget 2</widget>
</widgets>
</user>
PHP
<?php
$xmlfile = 'widgets/widgets_files/widgets.xml';
$widgets = array();
$user = new SimpleXMLElement($xmlfile, NULL, true);
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom = dom_import_simplexml($user)->ownerDocument;
foreach ($user->widgets->widget as $widget) {
$new_widget = new Widget($widget); //Where the node gets passed
array_push($widgets, $new_widget);
}
//For example
$new_widget[0]->set_subnodes();
$new_widget[0]->get_subnodes();
class Widget {
private $widget;
private $stuffArray = array();
public function __construct($widget) {
$this->widget = $widget;
}
public function set_subnodes() {
foreach ($this->widget->stuff->morestuff as $morestuff => $value) {
$this->stuffArray[$morestuff] = $value;
}
}
public function get_subnodes() {
foreach ($this->stuffArray as $stuff) {
echo$stuff;
}
}
}
It is indeed possible to pass XML objects as parameters to objects and to call methods on them, but there are a number of errors in your code which are stopping it from working. In particular, the XML that you are using isn't the structure that you think it is--the stuff and morestuff nodes are not children of widget, so none of the actions that you're trying to perform with them will work. Here's a corrected version of the XML and some PHP code that does what I think you're trying to do above:
$widgets = array();
# you can load your code from a file, obviously--for the purposes of the example,
# I'm loading mine using a function.
$sxe = simplexml_load_string( get_my_xml() );
foreach ($sxe->widgets->widget as $widget) {
$new_widget = new Widget($widget); // Where the node gets passed
array_push($widgets, $new_widget);
}
// For example
foreach ($widgets as $w) {
$w->set_subnodes();
$w->get_subnodes();
}
function get_my_xml() {
return <<<XML
<user>
<widgets>
<widget>Widget 1
<stuff>
<morestuff>Things</morestuff>
</stuff>
<stuff>
<morestuff>Other Things</morestuff>
</stuff>
</widget>
<widget>Widget 2
<stuff>
<morestuff>Widget Two's Things</morestuff>
</stuff>
<stuff>
<morestuff>Widget Two's Other Things</morestuff>
</stuff>
</widget>
</widgets>
</user>
XML;
}
The Widget object:
class Widget {
private $widget;
private $stuffArray = array();
public function __construct($widget) {
$this->widget = $widget;
}
public function set_subnodes() {
# put all the "morestuff" nodes into the stuffArray
foreach ($this->widget->xpath("stuff/morestuff") as $ms) {
print "pushing $ms on to array" . PHP_EOL;
array_push($this->stuffArray, $ms);
}
}
public function get_subnodes() {
foreach ($this->stuffArray as $stuff) {
print "Running get_subnodes: got $stuff" . PHP_EOL;
}
}
}
Output:
pushing Things on to array
pushing Other Things on to array
Running get_subnodes: got Things
Running get_subnodes: got Other Things
pushing Widget Two's Things on to array
pushing Widget Two's Other Things on to array
Running get_subnodes: got Widget Two's Things
Running get_subnodes: got Widget Two's Other Things
I need to deserialize some XML I receive into an object. I use SimpleXML to do this. An example of some of the XML received is this:
<?xml version="1.0" encoding="UTF-8"?>
<coupons type="array">
<coupon>
<coupon_code>baz</coupon_code>
<name>baz</name>
<discount_type>percent</discount_type>
<discount_percent type="integer">20</discount_percent>
<plan_codes type="array">
<plan_code>plus</plan_code>
<plan_code>pro</plan_code>
</plan_codes>
</coupon>
<coupon>
<coupon_code>test</coupon_code>
<name>Test</name>
<discount_type>dollars</discount_type>
<discount_in_cents>
<USD type="integer">2000</USD>
<EUR type="integer">1500</USD>
</discount_in_cents>
<plan_codes type="array">
<plan_code>plus</plan_code>
</plan_codes>
</coupon>
</coupons>
In this piece of XML, there are 3 types of arrays: <coupons> is an array of <coupon>. <coupon> has 2 arrays as well: <plan_codes> (which is a flat array) and <discount_in_cents> which is an associative array.
How does my deserializer work: I create a SimpleXMLElement from the XML and pass it to a function visitElement which takes a SimpleXMLElement and the type, array<Model\Coupon> in this case, indicating it's an array of Model\Coupon objects.
In my visitElement I check if the type is an existing class, if so, I create a new instance of that class and get a configuration for that Model, the configuration tells me which children need to be deserialized to which object variable. visitElement looks like this:
public function visitElement($element, $type)
{
// it is possible the element is not available
if (!$element) {
return null;
} else {
// get the value based on it's mapped type
switch ($type) {
case 'string':
return $this->visitString($element);
break;
// ...
// other cases
// ...
default: // catch other cases
if (class_exists($type)) {
// We mapped to an existing class
return $this->visitModel($type, $element);
} elseif ($this->isArrayType($type, $arrayType, $keepKey)) {
// We mapped to an array<type>
return $this->visitArray($arrayType, $element, $keepKey);
} else {
// A type we can't handle
return null;
}
}
}
}
This way, I have a nice recursive way of deserializing other XML as well, that might have nested objects (For example, the Account XML has an Address object nested within).
My Model\Coupon is configured so that <plan_codes> and <discount_in_cents> are considered array<string> and array<integer> respectively.
This is my visitArray function:
protected function visitArray($type, $element, $keepKey = false)
{
$value = null;
$i = 0;
foreach ($element->children() as $key => $child) {
$k = $keepKey ? $key : $i++;
$value[$k] = $this->visitElement($child, $type);
}
return $value;
}
This all works beautifully. I get an array of 2 Model\Coupon objects, with the second object having a discount_in_cents array that looks like ['USD' => 2000, 'EUR' => 1500], but it does NOT work for the plan codes...
When I var_dump those, I get
array (size=2)
0 => null
1 => null
and
array (size=1)
0 => null
Is there any reason why looping over <plan_codes> would yield different results/children than <coupons> or <discount_in_cents>?
At the moment, I implemented a workaround in visitArray as follows:
protected function visitArray($type, $element, $keepKey = false)
{
$value = null;
$i = 0;
foreach ($element->children() as $key => $child) {
$k = $keepKey ? $key : $i++;
$value[$k] = 'string' === $type ? (string)$child : $this->visitElement($child, $type);
}
return $value;
}
is there any way to pass or bind Container object and call Service object's method in XSLTProcessor. some thing like.
XSLTProcessor::registerFunction(); //in php file.
in xsltStylesheet
<xslt:value-of select="php:function('serviceobject::serviceObjectMethod',string($xsltProcessingVariable))"/>
In "normal" php code you can do something like
<?php
class Foo {
public function __construct($prefix) {
$this->prefix = $prefix;
}
public function myMethod($id) {
return sprintf('%s#%s', $this->prefix, $id);
}
}
$fooA = new Foo('A');
$fooB = new Foo('B');
echo call_user_func_array( array($fooA, 'myMethod'), array('id1') ), "\r\n";
echo call_user_func_array( array($fooB, 'myMethod'), array('id1') ), "\r\n";
i.e. instead of giving call_user_func_array just the name of the function you pass an array($obj, 'methodName') to invoke an instance method.
Unfortunatley that doesn't seem to work with php:function(...) and I haven't found another easy/clean way to do it.
But you could register your objects in a lookup table string_id->object and then use something like
select="php:function('invoke', 'obj1', 'myMethod', string(#param1), string(#param2))"
in your stylesheet. function invoke($objectId, $methodName) now has to find the object that has been registered under $objectId and then invoke the method like in the previous example.
func_get_args() lets you retrieve all parameters passed to a function, even those that are not declared in the function signature. Cut off the first two elements (i.e. $objectId and $methodName) and pass the remaining array as arguments to call_user_func_array.
self-contained example:
<?php
class Foo {
public function __construct($prefix) {
$this->prefix = $prefix;
}
public function myMethod($id) {
return sprintf('%s#%s', $this->prefix, $id);
}
}
function invoke($objectId, $methodname)
{
static $lookup = array();
$args = func_get_args();
if ( is_null($methodname) ) {
$lookup[$objectId] = $args[2];
}
else {
$args = array_slice($args, 2);
return call_user_func_array( array($lookup[$objectId], $methodname), $args);
}
}
// second parameter null -> register object
// sorry, it's just a quick hack
// don't do this in production code, no one will remember after two weeks
invoke('obj1', null, new Foo('A'));
invoke('obj2', null, new Foo('B'));
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet(new SimpleXMLElement( style() ));
echo $proc->transformToXML(new SimpleXMLElement( document() ));
function document() {
return <<<EOB
<doc>
<element id="id1" />
<element id="id2" />
</doc>
EOB;
}
function style() {
return <<<EOB
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:output method="text"/>
<xsl:template match="element">
Obj1-<xsl:value-of select="php:function('invoke', 'obj1', 'myMethod', string(#id))"/>
|||
Obj2-<xsl:value-of select="php:function('invoke', 'obj2', 'myMethod', string(#id))"/>
</xsl:template>
</xsl:stylesheet>
EOB;
}
prints
Obj1-A#id1
|||
Obj2-B#id1
Obj1-A#id2
|||
Obj2-B#id2
btw: don't implement your invoke() function like I did in this example. I just failed to come up with a better way to implement a register()/invoke() functionality for this quick example ;-)
Simple XML templates like these ones :
structure.xml :
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<document>
<book>first book</book>
<book>second book</book>
((other_books))
</document>
book_element.xml :
<book>((name))</book>
And this test :
<?php
Header("Content-type: text/xml; charset=UTF-8");
class XMLTemplate extends DOMDocument
{
private $_content_storage;
private $_filepath;
private $_tags;
public function XMLTemplate( $sFilePath )
{
if( !file_exists( $sFilePath ) ) throw new Exception("file not found");
$this->_filepath = $sFilePath;
$this->_tags = [];
$this->_content_storage = file_get_contents( $this->_filepath );
}
public function Get()
{
$this->merge();
$this->loadXML( $this->_content_storage );
return $this->saveXML();
}
public function SetTag( $sTagName, $sReplacement )
{
$this->_tags[ $sTagName ] = $sReplacement;
}
private function merge()
{
foreach( $this->_tags as $k=>$v)
{
$this->_content_storage = preg_replace(
"/\({2}". $k ."\){2}/i",
$v,
$this->_content_storage
);
}
$this->_content_storage = preg_replace(
"/\({2}[a-z0-9_\-]+\){2}/i",
"",
$this->_content_storage
);
}
}
$aBooks = [
"troisième livre",
"quatrième livre"
];
$Books = "";
foreach( $aBooks as $bookName )
{
$XMLBook = new XMLTemplate("book_element.xml");
$XMLBook->SetTag( "name", $bookName );
$Books .= $XMLBook->Get();
}
$XMLTemplate = new XMLTemplate("test.xml");
$XMLTemplate->SetTag("other_books", $Books);
echo $XMLTemplate->Get();
?>
Give me error :
Warning: DOMDocument::loadXML(): XML declaration allowed only at the start of the document in Entity, line: 5
Because loadXML() method add automatically the declaration to the content, but i need to inject parts of xml in the final template like above. How to disable this annoying auto adding and let me use my declaration ? Or another idea to conturn the problem ?
If you dislike the error and you want to save the document you'd like to merge without the XML declaration, just save the document element instead of the whole document.
See both variants in the following example-code (online-demo):
$doc = new DOMDocument();
$doc->loadXML('<root><child/></root>');
echo "The whole doc:\n\n";
echo $doc->saveXML();
echo "\n\nThe root element only:\n\n";
echo $doc->saveXML($doc->documentElement);
The output is as followed:
The whole doc:
<?xml version="1.0"?>
<root><child/></root>
The root element only:
<root><child/></root>
This probably should be already helpful for you. Additionally there is a constant for libxml which is said can be used to control whether or not the XML declaration is output. But I never used it:
LIBXML_NOXMLDECL (integer)
Drop the XML declaration when saving a document
Note: Only available in Libxml >= 2.6.21
From: http://php.net/libxml.constants
See the link for additional options, you might want to use the one or the other in the future.
i have an xml file:
<?xml version="1.0" encoding="utf-8" ?>
<transaction dsxml_version="1.08">
<action>action1</action>
<action>action2</action>
</transaction>
If i use simplexml i can access the first "action" with die following code
$xml = simplexml_load_string($xml_content);
echo $xml->action; // Write "action1"
echo $xml->action[0]; // Write "action1"
echo $xml->action[1]; // Write "action2"
Now i create an array and a try to access it on the same way. But it doesent work.
We have a huge php skipt which use simple xml which contains a logical error. If i can emulate simple xml i can fix this error on one position
try this:
current($xml->action);
echo array_shift(array_slice($xml->action, 0, 1));
or if you're not worried about damaging the original array, $xml->action you can use the following
echo array_shift($xml->action);
Using array_shift will guarantee you get the first element if it's numbered or associative.
You can create a fake or mock object that simulates the behaviour you are looking for:
$action = new SimpleXMLArrayMock($action_array);
$xml->action = $action;
echo "\nFake:\n";
echo $xml->action, "\n"; // Write "action1"
echo $xml->action[0], "\n"; // Write "action1"
echo $xml->action[1], "\n"; // Write "action2"
/**
* Mock SimpleXML array-like behavior
*/
class SimpleXMLArrayMock extends ArrayObject
{
private $first;
public function __construct(array $array)
{
$this->first = (string) $array[0];
parent::__construct($array);
}
public function __toString()
{
return $this->first;
}
}
Demo
use xpath so you can find the element
This would work:
foreach($myarray as $key => $val)
{
print $val;
//if you don't need the rest of the elements:
break;
}
Added:
You can even make it a function:
function get_first_element($myarray)
{
foreach($myarray as $key => $val)
{
return $val;
}
}