Converting multidimensional SimpleXML to associative array - php

I have a nested multidimensional XML string that I get into SimpleXML. I want to convert it into an associative array. The examples listed on php.net do not work correctly or they do only for flat xmls.

This works better than the example on SimpleXML manual page, but in its current form it discards the attributes.
function xml2array($xmlObject, $out = array())
{
foreach ($xmlObject as $node) {
if ($node->count() > 0) {
$out[$node->getName()][] = xml2array($node);
} else {
$out[$node->getName()] = (string)$node;
}
}
return $out;
}

Related

Build an array as a tree structure from an XML file

I am trying to read this xml file, but the code I am trying to make should work for any xml-file:
<?xml version="1.0"?>
<auto>
<binnenkant>
<voorkant>
<stuur/>
<gas/>
</voorkant>
<achterkant>
<stoel/>
<bagage/>
</achterkant>
</binnenkant>
<buitenkant>
<dak>
<dakkoffer>
<sky/>
<schoen/>
</dakkoffer>
</dak>
<trekhaak/>
<wiel/>
</buitenkant>
</auto>
I am using the two functions below to turn the XML-file into an array and turn that array into a tree.
I am trying to keep the parent-child relationship of the XML file. All I am getting back from the second function is an array with all the tags in the xml-file.
Can someone please help me?
function build_xml_tree(array $vals, $parent, $level) {
$branch = array();
foreach ($vals as $item) {
if (($item['type'] == "open") || $item['type'] == "complete") {
if ($branch && level == $item['level']) {
array_push($branch, ucfirst(strtolower($item['tag'])));
} else if ($parent == "" || $level < $item['level']) {
$branch = array(ucfirst(strtolower($item['tag'])) => build_xml_tree($vals, strtolower($item['tag']), $level));
}
}
}
return $branch;
}
function build_tree ($begin_tree, $content_naam) {
$xml = file_get_contents('xml_files/' . $content_naam . '.xml');
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
?>
<pre>
<?php
print_r($vals);
?>
</pre>
<?php
$eindarray = array_merge($begin_tree, build_xml_tree($vals, "", 1));
return $eindarray;
}
There are many classes which can load an XML file. Many of them already represent the file in a tree structure: DOMDocument is one of them.
It seems a bit strange that you want to make a tree as an array when you already have a tree in a DOMDocument object: since you'll have to traverse the array-tree in some way... why don't you traverse directly the tree structure of the object-tree for printing for example?
Anyway the following code should do what you're asking for: I've used a recursive function in which the array-tree is passed by reference.
It should be trivial at this point to arrange my code to better suit your needs - i.e. complete the switch statement with more case blocks.
the $tree array has a numeric key for each node level
the tag node names are string values
if an array follows a string value, it contains the node's children
any potential text node is threated as a child node
function buildTree(DOMNode $node = NULL, &$tree) {
foreach ($node->childNodes as $cnode) {
switch($cnode->nodeType) {
case XML_ELEMENT_NODE:
$tree[] = $cnode->nodeName;
break;
case XML_TEXT_NODE:
$tree[] = $cnode->nodeValue;
break;
}
if ($cnode->hasChildNodes())
buildTree($cnode, $tree[count($tree)]);
}
}
$source ='the string which contains the XML';
$doc = new DOMDocument();
$doc->preserveWhiteSpace = FALSE;
$doc->loadXML($source, LIBXML_NOWARNING);
$tree = array();
buildTree($doc, $tree);
var_dump($tree);

Customize json_decode behaviour

I have a JSON string returned by a REST API which follows:
'{"success":true,"product":{"id":"2","category_id":"2","type":"9","name":".ch","description":"","visible":"1","domain_options":"0","stock":"0","qty":"0","autosetup":"2","subdomain":"","owndomain":"0","tax":"0","upgrades":"","sort_order":"0","client_limit":"0","rel":"Product","paytype":"DomainRegular","m_setup":"0.00","q_setup":"0.00","s_setup":"0.00","a_setup":"0.00","b_setup":"0.00","t_setup":"0.00","p4_setup":"0.00","p5_setup":"0.00","d_setup":"0.00","w_setup":"0.00","h_setup":"0.00","m":"0.00","q":"0.00","s":"0.00","a":"0.00","b":"0.00","t":"0.00","p4":"0.00","p5":"0.00","d":"0.00","w":"0.00","h":"0.00","ptype":"DomainsType","options":"3","module":"13","server":"","tlds":null,"periods":{"1":{"product_id":"2","period":"1","register":"17.00","transfer":"17.00","renew":"17.00"}},"tag_name":".ch","tag_description":"","free_domain":"0","product_id":"2","not_renew":"0","epp":true,"ns":["ns3.dfinet.ch","ns4.dfinet.ch","",""],"nsips":"|||","tld":".ch","nsip":["","","",""],"asciimode":true,"app_id":"1","app_ns1":"","app_ns2":"","app_ns3":"","app_ns4":"","app_ip1":"","app_ip2":"","app_ip3":"","app_ip4":"","emails":{"AfterRegistrarRegistration":"28","AfterRegistrarRenewal":"29","AfterRegistrarTransfer":"30","expiringDomain":"54"}},"config":false,"call":"getProductDetails","server_time":1412061849}'
I am trying to convert this to an object and then serve an XML for a soap webservice, what I was doing up to now was
retrieving the result from the rest API -> convert it to object with json_decode($obj)
serve the soap handle() with the converted object
The problem is that, with the following JSON, there are some properties that are "numeric" but not sequential, so the JSON convert the string to an object as follows:
$o = new stdClass();
$o->1 = 'a string';
The problem is that when soap converts object to XML, the node named <1> is an invalid XML markup.
What can I do to "pre-parse" the JSON and convert all of those fake objects to sequentials arrays?
EDIT: Solution based on dmikam answer
I did something cleaner based on the proposed solution:
function fixVariables($variables)
{
if (!is_array($variables) && !is_object($variables)) {
return $variables;
}
foreach ($variables as $k => &$variable) {
if (is_object($variable)) {
if (is_numeric(key($variable))) {
$values = array();
foreach ($variable as $value) {
$values[] = $value;
}
$variable = $values;
unset($values);
}
$this->fixVariables($variable);
} elseif (is_array($variable)) {
if (is_numeric(key($variable))) {
$variable = array_values($variable);
}
$this->fixVariables($variable);
}
}
return $variables;
}
Well, I think you could walk through the resulting parsed object and convert all items that contains numeric indexes into arrays. The function would be something like this:
function fixJsonObject($obj){
if (is_object($obj)){
foreach(get_object_vars($obj) as $key=>$value){
$obj->$key = fixJsonObject($obj->$key);
if (is_numeric($key)){
return (array)$obj;
}
}
}elseif (is_array($obj)){
foreach($obj as $key=>$value){
$obj[$key] = fixJsonObject($obj[$key]);
}
}
return $obj;
}
$json = json_decode('{your: "json"}');
$json = fixJsonObject($json);
I have tested it a bitand looks like it works.

php remove duplicates from multidimensional associative array using recursion

I'm trying to remove duplicated entries where value and type are both equal on a multidimensional associative array, but only using recursion and without array_unique. All keys are associative.
I tried this, and I'm getting the same result as the main array. My logic seems to fail me at this late hour.
function rmDuplicates(&$array) {
$uniqueArray = array();
foreach($array as $k=>$v) {
if (is_array($v)) {
$uniqueArray[$k] = rmDuplicates($v);
} else {
if (!in_array($v, $uniqueArray)) {
$uniqueArray[] = $v;
}
}
}
return $uniqueArray;
}

Get values from Multidimensional nested array in PHP

Hi I am trying to get fulltext values from this json file on this Link
I am getting only top of the array how can I get all values from all arrays even the nested ones
$json_output = json_decode($json, true);
var_dump($json_output);
foreach($json_output['results'] as $item) {
echo '<br/>'. $item['fulltext'];
}
Id try looking at spl in PHP, it gives you useful iterator classes including ones specially for complex arrays - http://php.net/manual/en/class.recursivearrayiterator.php
another approach would be to
1) convert your json to xml (json -> php array -> xml) - you can use this recursive function:
function to_xml(SimpleXMLElement $object, array $data)
{
foreach ($data as $key => $value)
{
if (is_array($value))
{
$new_object = $object->addChild($key);
to_xml($new_object, $value);
}
else
{
$object->addChild($key, $value);
}
}
}
$xml = new SimpleXMLElement('<rootTag/>');
to_xml($xml, $my_array);
2) query the xml with XPath to fetch the collection of data you need, for example
$titles = $xml->xpath('//#fulltext');
Utilising XPath you can easily fetch data also by complex constraints, check out the docs
Found a solution
foreach($json_output['results'] AS $result) {
foreach($result['printouts']['Covers topic'] AS $topic) {
echo "<ul><li>".$topic['fulltext']."</li></ul>";
}
echo "<br/>".$result['fulltext'];
}
Thanks for your help guys

how to cast an xml object to a associative array?

I am doing some geocoding with the google api and was wondering how do i cast the returned simplexml object? I tried the following but it does no cast the child objects.. ie.. i would like a multi dimensional array.
$url = "http://maps.googleapis.com/maps/api/geocode/xml?address=".$adr."
&sensor=false";
$result = simplexml_load_file($url);
$result = (array) $result;
You could make a JSON request rather than XML; it's recommended; unless your application requires it. Then use:
json_decode( $result, true );
http://us2.php.net/manual/en/function.json-decode.php
I found very useful this function for converting Object to Array recursively:
http://forrst.com/posts/PHP_Recursive_Object_to_Array_good_for_handling-0ka
Adapted from the site above, to use it outside classes:
function object_to_array($obj) {
$arrObj = is_object($obj) ? get_object_vars($obj) : $obj;
foreach ($arrObj as $key => $val) {
$val = (is_array($val) || is_object($val)) ? object_to_array($val) : $val;
$arr[$key] = $val;
}
return $arr;
}
Turn the SimpleXMLElement object into json and decode the json string again into an associative array:
$array = json_decode(json_encode($result), 1);
The simple cast to array does not go more deep in there, that's why the trick via json_encode and json_decode is used.

Categories