XML into Associative Array using PHP - php

Can anyone help with converting data from an XML document into an associative array? I'm running into issues given that the XML structure is sort of 3D and the array is more of a 2D structure (please forgive my lack of correct terminology throughout).
The XML elements have attributes, children and grand-children (but I never know their names), so I figured I'd try to make the key in the array a concatenation of each child/attribute name and the value equal to, well, the value. Trouble is I need the attribute name and value as part of the concatenated array key to make it unique...
For example:
<Computer id="1">
<OS>
<Name>Linux</Name>
<Age>Older than me</Age>
</OS>
</Computer>
<Computer id="2">
<OS>
<Name>Windows</Name>
<Age>Not so much</Age>
</OS>
</Computer>
Should ideally give:
[Computer-id-1-OS-Name] = 'Linux'
[Computer-id-1-OS-Age] = 'Older than me'
[Computer-id-2-OS-Name] = 'Windows'
[Computer-id-2-OS-Age] = 'Not so much'
But I'm getting this result:
[Computer-id] = '1'
[Computer-OS-Name] = 'Linux'
[Computer-OS-Age] = 'Older than me'
[Computer-id] = '2'
[Computer-OS-Name] = 'Windows'
[Computer-OS-Age] = 'Not so much'
So that the [Computer-id] key is not unique. I'm using a recursive function to read in the values, but I can't figure how to get the attribute name and attribute value into the name of the subordinate keys...(By the way there is a good reason for doing this seemingly illogical task!)
Any help would be greatly appreciated...
Here is the function which 'flattens' the XML data after it has been read into a multi-dimensional array. I'm not sure I'm going about this the right way!
function flattenArray ($array, $baseName = NULL)
{
reset($array);
while (list ($key, $value) = each($array)) {
$outKey = $key . "-";
if (is_array($value)) {
flattenArray($value, $baseName . $outKey);
} else {
$finalKey = $baseName . rtrim($outKey, '-');
$finalValue = $value;
echo "$finalKey = $finalValue\n";
}
}
}

This worked great for me, and it was simple.
$ob = simplexml_load_file('test.xml');
$json = json_encode($ob);
$array = json_decode($json, true);

One example could be:
$dom = new DOMDocument;
$dom->loadXML(
'<root>
<Computer id="1">
<OS>
<Name>Linux</Name>
<Age>Older than me</Age>
</OS>
</Computer>
<Computer id="2">
<OS>
<Name>Windows</Name>
<Age>Not so much</Age>
</OS>
</Computer>
</root>'
);
$xpath = new DOMXPath($dom);
$result = array();
foreach ($xpath->query('//*[count(*) = 0]') as $node) {
$path = array();
$val = $node->nodeValue;
do {
if ($node->hasAttributes()) {
foreach ($node->attributes as $attribute) {
$path[] = sprintf('%s[%s]', $attribute->nodeName, $attribute->nodeValue);
}
}
$path[] = $node->nodeName;
}
while ($node = $node->parentNode);
$result[implode('/', array_reverse($path))] = $val;
}
print_r($result);
Output:
Array
(
[#document/root/Computer/id[1]/OS/Name] => Linux
[#document/root/Computer/id[1]/OS/Age] => Older than me
[#document/root/Computer/id[2]/OS/Name] => Windows
[#document/root/Computer/id[2]/OS/Age] => Not so much
)
Thats not exactly what you're looking for, but it's a start and can easily be tweaked to give different results.

here's my function to generate associated array, derived from
Recursive cast from SimpleXMLObject to Array
function xml2assoc($obj, &$arr) {
$children = $obj->children();
foreach ( $children as $elementName => $node ) {
if (!isset($arr[$elementName])) {
$arr[$elementName] = array();
}
$temp = array();
$attributes = $node->attributes();
foreach ( $attributes as $attributeName => $attributeValue ) {
$attribName = strtolower(trim((string) $attributeName));
$attribVal = trim((string) $attributeValue);
$temp[$attribName] = $attribVal;
}
$text = (string) $node;
$text = trim($text);
if (strlen($text) > 0) {
$temp ['text='] = $text;
}
$arr[$elementName][] = $temp;
$nextIdx = count($arr[$elementName]);
xml2assoc($node, $arr[$elementName][$nextIdx - 1]);
}
return;
}
$xml = '<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>2</ArticleCount>
<Articles>
<item>
<Title><![CDATA[title1]]></Title>
<Description><![CDATA[description1]]></Description>
<PicUrl><![CDATA[picurl]]></PicUrl>
<Url><![CDATA[url]]></Url>
</item>
<item>
<Title><![CDATA[title]]></Title>
<Description><![CDATA[description]]></Description>
<PicUrl><![CDATA[picurl]]></PicUrl>
<Url><![CDATA[url]]></Url>
</item>
</Articles>
</xml> ';
$dom = new SimpleXMLElement($xml);
$arr = array();
xml2assoc($dom, $arr);
print_r($arr);
generated array:
Array
(
[ToUserName] => Array
(
[0] => Array
(
[text=] => toUser
)
)
[FromUserName] => Array
(
[0] => Array
(
[text=] => fromUser
)
)
[CreateTime] => Array
(
[0] => Array
(
[text=] => 12345678
)
)
[MsgType] => Array
(
[0] => Array
(
[text=] => news
)
)
[ArticleCount] => Array
(
[0] => Array
(
[text=] => 2
)
)
[Articles] => Array
(
[0] => Array
(
[item] => Array
(
[0] => Array
(
[Title] => Array
(
[0] => Array
(
[text=] => title1
)
)
[Description] => Array
(
[0] => Array
(
[text=] => description1
)
)
[PicUrl] => Array
(
[0] => Array
(
[text=] => picurl
)
)
[Url] => Array
(
[0] => Array
(
[text=] => url
)
)
)
[1] => Array
(
[Title] => Array
(
[0] => Array
(
[text=] => title
)
)
[Description] => Array
(
[0] => Array
(
[text=] => description
)
)
[PicUrl] => Array
(
[0] => Array
(
[text=] => picurl
)
)
[Url] => Array
(
[0] => Array
(
[text=] => url
)
)
)
)
)
)
)

Read the xml into a DOM object, loop through it, save results into an array. Is that simple.

Simple arrays may be 2d but multi-dimensional arrays can replicate a hierarchical structure like xml very easily.
Google 'associative multi-dimensional array php' for more info.
However, as has already been stated, PHP has a built-in xml parser so there shouldn't be any need to recreate the xml within an array anyhow, let alone flatten it to a simple array.
Within PHP your array structure should resemble this:
$computers["computers"]["computer-1"]["OS"]["Name"] = "Linux";
$computers["computers"]["computer-1"]["OS"]["Age"] = "Older Than Me";
$computers["computers"]["computer-2"]["OS"]["Name"] = "Windows";
$computers["computers"]["computer-2"]["OS"]["Age"] = "Not so much";
etc...

I modified user655000's answer to be closer to how json_decode(json_encode($dom)) would format/return the data. I also made the initial array parameter optional, since it's just going to be empty anyway.
I couldn't use the decode(encode) method as there appears to be bugs in PHP's encode function, which resulted in the decode() returning null on some sample data. I tried a safer version of the encode function, but it ran out of memory.
There is a minor behavior difference. the decode(encode) method will discard any attributes (possibly children too) if there is nodeText. My method does not.
function readxml($xmlfile, $recursive = false){
$ob = simplexml_load_file($xmlfile);
//primary method
$json = json_encode($ob);
$array = json_decode($json, true);
if(is_null($array)){//backup method
$array = xml2assoc($ob);
}
return $array;
}
function xml2assoc($obj, &$arr = null) {
$children = $obj->children();//->count();
$nodes = [];
foreach ( $children as $elementName => $node ) {
if(!isset($nodes[$elementName])){
$nodes[$elementName] = 0;
}
$nodes[$elementName]++;
}
$indexes = [];
if($arr === null){
$arr = [];
}
foreach ( $children as $elementName => $node ) {
$temp = array();
$grandchildren = $node->children()->count();
//attributes
$attributes = $node->attributes();
foreach ( $attributes as $attributeName => $attributeValue ) {
$attribName = trim((string) $attributeName);
$attribVal = trim((string) $attributeValue);
$temp["#attributes"][$attribName] = $attribVal;
}
//text
$text = (string) $node;
$text = trim($text);
if (strlen($text) > 0) {
if(count($temp) == 0 && $grandchildren == 0){
$temp = $text;//discard the children/attribute data since there aren't any
} else {
$temp["NodeText"] = $text;//retain the children/attributes
}
}
//grandchildren
if($temp || is_string($temp) || $grandchildren > 0 ){
if( $nodes[$elementName] == 1 ){//only one of it's kind
$arr[$elementName] = $temp;
xml2assoc($node, $arr[$elementName]);
} else {//has multiple nodes of the same kind
if(isset($indexes[$elementName])){
$indexes[$elementName]++;
} else {
$indexes[$elementName] = 0;
}
$index = $indexes[$elementName];
$arr[$elementName][$index] = $temp;
xml2assoc($node, $arr[$elementName][$index]);
}
}
}
return $arr;
}

Related

Php parse multilevel - xml into array

I am trying to convert xml into php array but somehow i am doing a mistake, can anyone please help me ?
Here the xml formate :
<Department Id="3">
<Week Date="23/03/2020">
<Class DateTime="23/03/2020 18:00"/>
<Class DateTime="23/03/2020 18:45"/>
</Week>
<Week Date="30/03/2020">
<Class DateTime="30/03/2020 18:00"/>
<Class DateTime="30/03/2020 18:45"/>
</Week>
</Department>
Output Need like this :
Array
(
[0] => Array
(
[0] => Array
(
[DateTime] => 23/03/2020 18:00
)
[1] => Array
(
[DateTime] => 23/03/2020 18:45
)
)
[1] => Array
(
[0] => Array
(
[DateTime] => 30/03/2020 18:00
)
[1] => Array
(
[DateTime] => 30/03/2020 18:45
)
)
)
This is what i have tried
foreach ($xml->children() as $week) {
foreach ($week->children() as $class) {
$j = 0;
foreach ($class->attributes() as $a => $b){
$results[$i][$j][$a] = (string) $b;
}
$j++;
}
$i++;
}
I dont know whats wrong in my code :(
It's just a case of getting the right levels in the XML to match the loops, this builds a weeks data at a time and adds it into the overall results...
$results = [];
foreach ( $xml->Week as $week ) {
$weekData = [];
foreach ( $week->Class as $class ) {
$weekData[]['DateTime'] = (string)$class['DateTime'];
}
$results[] = $weekData;
}
To make this load all attributes...
$results = [];
foreach ( $xml->Week as $week ) {
$weekData = [];
foreach ( $week->Class as $class ) {
$classData = [];
foreach ( $class->attributes() as $name => $value ) {
$classData[$name] = (string)$value;
}
$weekData[] = $classData;
}
$results[] = $weekData;
}

How to parse arrays with different levels PHP

In a foreach loop i would like to compare [name] value beetween different arrays but they have not the same levels.
Array(
[array1] => Array
(
[0] => WP_Term Object
(
[name] => Plafond
)
)
[array2] => WP_Term Object
(
[name] => Chaudière
)
[array3] => Array
(
[0] => WP_Term Object
(
[name] => Pla
)
[1] => WP_Term Object
(
[name] => Toc
)
)
)
I don't know how could i get the [name] in the same loop whereas levels are different.
I have tried to make :
foreach( $fields as $name => $value )
{
echo $value->name; }
Should i add another loop in the first loop ?
thanks
So your data looks like this:
$json = '{"array1":[{"name":"Plafond"}],"array2":{"name":"Chaudière"},"array3":[{"name":"Pla"},{"name":"Toc"}]}';
$array = json_decode($json);
If you don't know how deep it will go, a simple recursive function should work. Perhaps something like this:
function get_name($o, &$output) {
if (is_array($o)) {
foreach($o as $v) {
get_name($v, $output);
}
} elseif (property_exists($o, "name")) {
$output[] = $o->name;
}
}
$output = [];
foreach ($array as $v) {
get_name($v, $output);
}
If you data is going to look like the sample you provided (i.e. it will always be first or second level) then you don't need to worry about recursion.
$output = [];
foreach ($array as $k=>$v) {
if (is_array($v)) {
foreach ($v as $k2=>$v2) {
$output[] = $v2->name;
}
} else {
$output[] = $v->name;
}
}
Either way, your output values are all in the $output array:
print_r($output);
Output:
Array
(
[0] => Plafond
[1] => Chaudière
[2] => Pla
[3] => Toc
)
You can use array_map, array_key_exists to retrive the name index from the array
$jsonFormat = '{"array1":[{"name":"Plafond"}],"array2":{"name":"Chaudière"},"array3":[{"name":"Pla"},{"name":"Toc"}]}';
$jsonArray = json_decode($jsonFormat,true);
$res = [];
array_map(function($v) use (&$res){
if(array_key_exists('name', $v)){
$res[] = $v['name'];
}else{
foreach($v as $_key => $_value){
$res[] = $_value['name'];
}
}
}, $jsonArray);
echo '<pre>';
print_r($res);
Result:-
Array
(
[0] => Plafond
[1] => Chaudière
[2] => Pla
[3] => Toc
)
You can use $res to compare the names.

Get to leaf in multidimension array

I have array structure where i must get leaf.
Example
First type of array
[name] => long_desc
[values] => Array
(
[0] => Array
(
[values] => xxx
)
)
)
or
(
[name] => long_desc
[values] => Array
(
[0] => Array
(
[name] => span
[values] => Array
(
[0] => Array
(
[values] => xxx
)
)
)
How to get value what name xxx? My array have longer depth and using foreach many times not work fine. I was try recursivearrayiterator but not help.
Try array_walk_recursive() function:
function testArrayItem($item, $key)
{
if ( $item == "xxx" ) {
echo "Found xxx on key {$key}";
}
}
array_walk_recursive($array, 'testArrayItem');
EDIT:
If you want to get entire branch, which leads to the leaf you can recursively iterate through it:
function getPathToLeafRecursive(array $input, array &$branch)
{
foreach ( $input as $key => $item ) {
if ( 'xxx' == $item ) {
$branch[] = $key;
return true;
}
if ( is_array($item) ) {
$res = getPathToLeafRecursive($item, $branch);
if ( $res ) {
$branch[] = $key;
return true;
}
}
}
return false;
}
$found_branch = array();
getPathToLeafRecursive($array, $found_branch);
$found_branch = array_reverse($found_branch);
var_export($found_branch);
Here's how to find the leaf node, without depending on the name of the key. It's rather primitive and could use some OOP, but it demonstrates the basic algo:
$array = array(
array('name' => 'span',
'values' => array('values' => 'xxx', array('values' => 'yyy')),
'stuff' => '123'
)
);
$deepest_depth = 0;
$deepest_node = null;
find_leaf($array, 0);
function find_leaf($array, $current_depth) {
global $deepest_depth, $deepest_node;
do {
$current_node = current($array);
if (is_array($current_node)) {
find_leaf($current_node, $current_depth+1);
} else {
if ($deepest_node === null || $current_depth > $deepest_depth) {
$deepest_depth = $current_depth;
$deepest_node = $current_node;
}
}
next($array);
} while ($current_node !== FALSE);
}
echo $deepest_node;
What is this xxx value? Do you know the content and you just want to know that it is in the Array?
In that case you can use the RecursiveArrayIterator with the RecursiveFilterIterator.
If you want to get all "values" keys that are leafs, then you can use the RecursiveFilterIterator too, but checking for "values" that are scalar for example.

Replace key of array with the value of another array while looping through

I have two multidimensional arrays. First one $properties contains english names and their values. My second array contains the translations. An example
$properties[] = array(array("Floor"=>"5qm"));
$properties[] = array(array("Height"=>"10m"));
$translations[] = array(array("Floor"=>"Boden"));
$translations[] = array(array("Height"=>"Höhe"));
(They are multidimensional because the contains more elements, but they shouldn't matter now)
Now I want to translate this Array, so that I its at the end like this:
$properties[] = array(array("Boden"=>"5qm"));
$properties[] = array(array("Höhe"=>"10m"));
I have managed to build the foreach construct to loop through these arrays, but at the end it is not translated, the problem is, how I tell the array to replace the key with the value.
What I have done is this:
//Translate Array
foreach ($properties as $PropertyArray) {
//need second foreach because multidimensional array
foreach ($PropertyArray as $P_KiviPropertyNameKey => $P_PropertyValue) {
foreach ($translations as $TranslationArray) {
//same as above
foreach ($TranslationArray as $T_KiviTranslationPropertyKey => $T_KiviTranslationValue) {
if ($P_KiviPropertyNameKey == $T_KiviTranslationPropertyKey) {
//Name found, save new array key
$P_KiviPropertyNameKey = $T_KiviTranslationValue;
}
}
}
}
}
The problem is with the line where to save the new key:
$P_KiviPropertyNameKey = $T_KiviTranslationValue;
I know this part is executed correctly and contains the correct variables, but I believe this is the false way to assing the new key.
This is the way it should be done:
$properties[$oldkey] = $translations[$newkey];
So I tried this one:
$PropertyArray[$P_KiviPropertyNameKey] = $TranslationArray[$T_KiviTranslationPropertyKey];
As far as I understood, the above line should change the P_KiviPropertyNameKey of the PropertyArray into the value of Translation Array but I do not receive any error nor is the name translated. How should this be done correctly?
Thank you for any help!
Additional info
This is a live example of the properties array
Array
(
[0] => Array
(
[country_id] => 4402
)
[1] => Array
(
[iv_person_phone] => 03-11
)
[2] => Array
(
[companyperson_lastname] => Kallio
)
[3] => Array
(
[rc_lot_area_m2] => 2412.7
)
[56] => Array
(
[floors] => 3
)
[57] => Array
(
[total_area_m2] => 97.0
)
[58] => Array
(
[igglo_silentsale_realty_flag] => false
)
[59] => Array
(
[possession_partition_flag] => false
)
[60] => Array
(
[charges_parkingspace] => 10
)
[61] => Array
(
[0] => Array
(
[image_realtyimagetype_id] => yleiskuva
)
[1] => Array
(
[image_itemimagetype_name] => kivirealty-original
)
[2] => Array
(
[image_desc] => makuuhuone
)
)
)
And this is a live example of the translations array
Array
(
[0] => Array
(
[addr_region_area_id] => Maakunta
[group] => Kohde
)
[1] => Array
(
[addr_town_area] => Kunta
[group] => Kohde
)
[2] => Array
(
[arable_no_flag] => Ei peltoa
[group] => Kohde
)
[3] => Array
(
[arableland] => Pellon kuvaus
[group] => Kohde
)
)
I can build the translations array in another way. I did this like this, because in the second step I have to check, which group the keys belong to...
Try this :
$properties = array();
$translations = array();
$properties[] = array("Floor"=>"5qm");
$properties[] = array("Height"=>"10m");
$translations[] = array("Floor"=>"Boden");
$translations[] = array("Height"=>"Höhe");
$temp = call_user_func_array('array_merge_recursive', $translations);
$result = array();
foreach($properties as $key=>$val){
foreach($val as $k=>$v){
$result[$key][$temp[$k]] = $v;
}
}
echo "<pre>";
print_r($result);
output:
Array
(
[0] => Array
(
[Boden] => 5qm
)
[1] => Array
(
[Höhe] => 10m
)
)
Please note : I changed the array to $properties[] = array("Floor"=>"5qm");, Removed a level of array, I guess this is how you need to structure your array.
According to the structure of $properties and $translations, you somehow know how these are connected. It's a bit vague how the indices of the array match eachother, meaning the values in $properties at index 0 is the equivalent for the translation in $translations at index 0.
I'm just wondering why the $translations array need to have the same structure (in nesting) as the $properties array. To my opinion the word Height can only mean Höhe in German. Representing it as an array would suggest there are multiple translations possible.
So if you could narrow down the $translations array to an one dimensional array as in:
$translation = array(
"Height"=>"Höhe",
"Floor"=>"Boden"
);
A possible loop would be
$result = array();
foreach($properties as $i => $array2) {
foreach($array2 as $i2 => $array3) {
foreach($array3 as $key => $value) {
$translatedKey = array_key_exists($key, $translations) ?
$translations[$key]:
$key;
$result[$i][$i2][$translatedKey] = $value;
}
}
}
(I see every body posting 2 loops, it's an array,array,array structure, not array,array ..)
If you cannot narrow down the translation array to a one dimensional array, then I'm just wondering if each index in the $properties array matches the same index in the $translations array, if so it's the same trick by adding the indices (location):
$translatedKey = $translations[$i][$i2][$key];
I've used array_key_exists because I'm not sure a translation key is always present. You have to create the logic for each case scenario yourself on what to check or not.
This is a fully recursive way to do it.
/* input */
$properties[] = array(array("Floor"=>"5qm", array("Test"=>"123")));
$properties[] = array(array("Height"=>"10m"));
$translations[] = array(array("Floor"=>"Boden", array("Test"=>"Foo")));
$translations[] = array(array("Height"=>"Höhe"));
function array_flip_recursive($arr) {
foreach ($arr as $key => $val) {
if (is_array($val)) {
$arr[$key] = array_flip_recursive($val);
}
else {
$arr = #array_flip($arr);
}
}
return $arr;
}
function array_merge_it($arr) {
foreach ($arr as $key => $val) {
if (is_array($val)) {
$arr[$key] = array_merge_it($val);
} else {
if(isset($arr[$key]) && !empty($arr[$key])) {
#$arr[$key] = $arr[$val];
}
}
}
return $arr;
}
function array_delete_empty($arr) {
foreach ($arr as $key => $val) {
if (is_array($val)) {
$arr[$key] = array_delete_empty($val);
}
else {
if(empty($arr[$key])) {
unset($arr[$key]);
}
}
}
return $arr;
}
$arr = array_replace_recursive($properties, $translations);
$arr = array_flip_recursive($arr);
$arr = array_replace_recursive($arr, $properties);
$arr = array_merge_it($arr);
$arr = array_delete_empty($arr);
print_r($arr);
http://sandbox.onlinephpfunctions.com/code/d2f92605b609b9739964ece9a4d8f389be4a7b81
You have to do the for loop in this way. If i understood you right (i.e) in associative array first key is same (some index).
foreach($properties as $key => $values) {
foreach($values as $key1 => $value1) {
$propertyResult[] = array($translations[$key][$key1][$value1] => $properties[$key][$key1][$value1]);
}
}
print_r($propertyResult);

Create multidimensional array from rows

The result of a query in my database returns something like this (a record for each row):
1.
1.1.
1.1.01.
1.1.01.001
1.2.
1.2.01.
1.2.02.
I'm trying to create something that returns me a multidimensional array in a tree format, like this:
array(
'1.' => array(
'1.1' => array(
'1.1.01.' => array(
(int) 0 => '1.1.01.001'
)
),
'1.2' => array(
(int) 0 => '1.2.01.',
(int) 1 => '1.2.02.'
)
)
)
All I could think to do was reverse the order of elements using explode().
I appreciate any suggestions.
Your format is very tricky because of :
1.2.
1.2.01. |
1.2.02. V Making this array instead of value
You can try
$string = "1.
1.1.
1.1.01.
1.1.01.001
1.2.
1.2.01.
1.2.02.";
$array = explode("\n", $string);
$data = array();
$temp = &$data;
$it = new CachingIterator(new ArrayIterator($array), CachingIterator::FULL_CACHE);
$continue = false;
foreach ( $it as $v ) {
$v = trim($v);
if ($it->hasNext()) {
$next = trim($it->getInnerIterator()->current());
if (stripos($next, $v) === 0) {
$temp = &$temp[$v];
} else {
$temp[] = $v;
if (strlen($next) != strlen($v)) {
$temp = &$data;
}
}
} else {
$temp[] = $v;
}
}
print_r($data);
Output
Array
(
[1.] => Array
(
[1.1.] => Array
(
[1.1.01.] => Array
(
[0] => 1.1.01.001
)
)
)
[1.2.] => Array
(
[0] => 1.2.01.
[1] => 1.2.02.
)
)
Here is a move COMPLEX demo

Categories