XML Attributes to arrays (PHP) - php

I have an XML which includes many thousands of nodes with information about them in attributes.
Each node is like :
<Class Option1="fiahfs;if" Option2="fiowfr0r0" ClassID="1">
<Class Option1="ro;ewaj;frwajro" Option2="afj;wh;fha" Option3="34014upkla" ClassID="2">
....
I need to parse that info into PHP arrays with array names being attribute names and the number of element in array equal to ClassID Attribute.
The problem is that some nodes have attributes that other nodes I dont have. I previously used ->attributes() for one selected element as
$a => $b,
$$a=array();
then
${$a}[(integer)$products['ClassID']]=$b
Where $products is simplexml element got from parsing XML with xpath. That basically gave me what i needed - i had a few arrays and i could address my requests like $Option1[1] , $Option2[1], Option1[2]...etc.
But the problem is that if I create that structure using only attribute list of one selected element -there`ll be elements that that one element do not have,but other have, and after a create arrays and then parse XML -there'll not be some arrays. Like if i create arrays from [0] of that example, that will give me $Option1, $Option2, but no $Option3.
When I rebuilt my code to :
foreach ($XML->xpath('/idspace/Class') as $products){
foreach($products->attributes() as $a => $b){
if(!isset($$a) $$a=array();
${$a}[(integer)$products['ClassID']]=$b;
}}
And after that I tried to foreach($ClassID) - I`ve got only one element in that array.
How can I parse every XML attribute to array while using attribute "ClassID" as element number in array?

I'm quite confused with your arrays. For me it sounds strange to have an extra array for each attribute. Why not make one array that contains each element and have the attributes as children. So that in the end you get an array like this:
$products = array(
[1] => array(
[Option1] => "fiahfs;if",
[Option2] => "fiowfr0r0",
),
[2] => array(
[Option1] => "ro;ewaj;frwajro",
[Option2] => "afj;wh;fha",
[Option3] => "34014upkla",
),
In this way you can easily access all the attributes for a given ChildId:
$products[$childId]['Option1'] - or better check if the Attribute-Name exists.
You can use simplexml->attributes()
$products = array(); // products will have the result!
foreach ($xml->products as $productXml) { // don't know your xml structure here,
// but you need to iterate over every product in the xml
$singleProduct = array();
$classId = 0;
foreach($productXml->attributes() as $a => $b) {
if ($a == 'ClassID') {
$classId = $b;
} else {
$singleProduct[$a] = $b;
}
}
$products[$classId] = $singleProduct;
}

Related

Create hierarchical / parent-child array using loops and references instead of recursion

I have a list of objects, each object could belong as a child of another object and each one has an element ('parent_id') that specifies which object it's a child of.
An example of what the expected tree should look like after children are properly imbedded:
Current Array
element 1
element 2
element 3
element 4
element 5
Organized into a hierarchy
-----------------------
element 2
-children:
element 1
-children
element 5
element 3
-children:
element 4
There should be two top elements, the first element would contain the element1 as a child, which would itself contain element 5 as a child. The third element would contain element 4 as a child.
The final result should produce a proper tree structure from it, however I'm running into an issue that when I generate the tree some of the elements are null.
From what I understand it is because of the weird scoping rules PHP has and that the last reference in the loop remains bound even after the end of the loop. So I included the unset($var) statements after each loop, however I'm still getting null values pushed into the tree.
Here is the fully contained example of the code:
$response = array(
array( "kind"=> "t1", "data" => array( "id" => 25, "parent_id" => 30 )),
array("kind"=> "t1", "data" => array( "id" => 30,"parent_id" => 0)),
array("kind"=> "t1", "data" => array("id" => 32, "parent_id" => 0 )),
array("kind"=> "t1", "data" => array("id" => 33,"parent_id" => 32)),
array("kind"=> "t1", "data" => array("id" => 35,"parent_id" => 25))
);
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
unset($secondObject);
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = $firstObj;
}
}
unset($firstObj);
print_r($tree);
The expected tree should contain only the topmost elements that are not children of other elements, the children should be embedded through references into the appropriate spaces of the top tree elements.
While I would probably opt for a recursive approach on a professional project, I've managed to produce a non-recursive approach using references like your posted code but with a single loop.
Code: (Demo)
$result = [];
foreach ($array as $obj) {
$id = $obj['data']['id'];
$parentId = $obj['data']['parent_id'];
if (isset($ref[$id])) { // child array populated before parent encountered
$obj['children'] = $ref[$id]['children']; // don't lose previously stored data
}
$ref[$id] = $obj;
if (!$parentId) {
$result[] = &$ref[$id]; // push top-level reference into tree
} else {
$ref[$parentId]['children'][] = &$ref[$id]; // push into parent-specific collection of references
}
}
var_export($result);
For a stacking process (not a recursive one) to build a hierarchical structure, your sample data is a little unexpected in that a child id integer is less than a parent id. I mean, in nature, parents are born first and then children are born. With your sample data, the input could not be pre-sorted by id value before looping -- this would have ensured that all parents where declared before children were encountered.
As a consequence, my snippet needs to push previously encountered children data into a newly encountered parent so that the declaration of the parent as a reference does not erase the cached children data.
I extended your sample data by one extra row while testing. If you find any breakages with my script, please supply new test data that exposes the issue.
I found the solution with the help of GPT/Bing:
So while I was unsetting the other variables, I wasn't unsetting the $parentFound, which has to be done as well.
Another thing was that I was not saving the item by reference when saving the item to the tree, which also has to be done in order for the whole reference tree to work.
The final code is:
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = &$firstObj; //has to be passed by reference
}
unset($secondObject);
unset($parentFound); //have to also include the $parentFound in the unset
}
unset($firstObj);
print_r($tree);

Restructure 2d array so that column values become row values (transpose but preserve first level keys)

The situation is as follows. I have a parent array which looks like the following:
$parent = [
1 => ['test1', 'test2'],
2 => ['test1_1', 'test2_2'],
];
I would like to group the data by column.
Desired result:
[
1 => ['test1', 'test1_1'],
2 => ['test2', 'test2_2'],
]
1 parent array called parent contains 2 arrays inside. I want to combine these two so that they have the same values as stated above. So this would mean that the arrays should be combined based on index number.
Since I do not make use of string keys, how would I accomplish this? I believe that there is no build in function available for this situation.
I would imagine that I could start beginning to create a new array and use a for loop through the parent array.
I tried the array-combine function however, this is NOT displaying the results I want.
[
1 => ['test1' => 'test1_1', 'test2' => 'test2_2'
]
If you need to preserve those first level keys, you can re-apply them after tranposing.
Code: (Demo)
var_export(
array_combine(array_keys($parent), array_map(null, ...$parent))
);
Otherwise, you can just transpose and accept the re-indexed first level keys. Honestly, I can't see any good reason to preserve the first level keys because by transposing, you remove the initial association between first level keys and the row values.
Code: (Demo)
var_export(
array_map(null, ...$parent)
);
If these techniques do not suit your actual project data, then we will need a more realistic sample array to be provided in your question body.
Loop over the keys of the top-level array. Then use the current index of the iteration to get the corresponding columns of the nested arrays.
$result = [];
foreach (array_keys($parent) as $i => $k) {
$result[$k] = array_column($parent, $i);
}
DEMO
This assumes the number of rows is the same as the number of columns. It's not clear what you expect the result to be if that's not true.

Programatically extending an array with sub arrays

I have a working array as follows
"content" => array
(
array //sub array for image 1
(
'url' => $uploadspath.$media_url0,//main image
"type" =>$type0,//media type
"caption"=>$caption0
),
array //sub array for image 2
(
'url' => $uploadspath.$media_url1,//main image
"type" =>$type1,//media type
"caption"=>$caption1
),
array //sub array for image 3
(
'url' => $uploadspath.$media_url2,//main image
"type" =>$type2,//media type
"caption"=>$caption2
),
....Programatically add new sub arrays here subject to the existence of $media_url(4,5,6 etc)..
);
I am getting the $media_url and other data from database. I would like too programatically extend the array by adding additional sub arrays and associated URL/type/caption elements IF there is a value for $media_url4; $media_url5; $media_url6; $media_url7; etc. etc. (Max 10 images)
My problem is how to code the extension of my array with additional sub-arrays based purely on the existence of additional media_urls. Simplistically I would like to be able to do something along the following lines but I don't know how to implement it within a nested array structure...
if ($media_url4) {code to add sub array/element for image 4}
if ($media_url5) {code to add sub array/elementsfor image 5}
etc...
Thank you for any assistance.
//Below we have a function or pdo function that return the new values from database that you want to put in the CONTENT MASTER ARRAY, NOW we put the values in a variable $rows
$rows = functionThatReturnNewValuesToPutInContentArray();
//The foreach looping the variable rows, getting each row in database and putting this in a variable $row
foreach($rows as $row){
//If the $row object data from database contains media_url value add new subarray to masterContentArray
if($row->media_url != ''){
$newSubArray = [
//$row->media_url, is considering that you have a column in your db named by media_url, and that contains url string
'url'=>$row->uploadsPath . $row->media_url,
'type'=>$row->type,
'caption'=>$row->caption,
];
array_push($masterContentArray['content'], $newSubArray);
}
}
return json_encode($content);

Sort an associative array by using another array's ordered key-value association

Finding the right title for this was next to impossible.
Imagine this scenario:
We have an array that contains certain product tags. The key is each tag's unique id and the value is its label:
Available Tags
Array (
[3] => Sweet
[4] => Sour
[5] => Bitter
[6] => Winter
[7] => Organic
)
We have another array which contains the tags that have been selected. The selection has a specific order which is defined by the key, while the value represents the id (of the actual tag we see in array #1).
Selected Tags in Specific Order
Array (
[10] => 4
[20] => 3
[30] => 7
)
My theoretical Approach
Certainly i could go about foreach-ing through the second array, collecting the appropriate values (that correspond to the first array's entries) in a new array. Then i could iterate over the first array and add all the values (to the new array) which are not yet present in the new array.
Quite honestly - that doesn't feel very professional. Unfortunately, i have no idea how to do this better.
Question
How can i neatly sort the first array (Available Tags) by using the chronology defined by the second array (Selected Tags)?
Note
I want to end up with all items from the first array. Not just the ones that are listed in the second one.
In case someone's curious: this is for multiple-selects which are sortable. Items which have been selected are sortable and must therefore appear in the right order. The other items order doesn't matter. My server-side data handler class gives me these two arrays as described, so that's what i got to work with.
Here's a solution that uses uksort(). Elements of the $tags array that are not present in the $order array are sorted to the end, and the relative order between them is undefined.
function my_sort($a, $b) {
global $order;
if(in_array($a, $order)) {
if(in_array($b, $order)) {
// Both $a and $b have an order
return array_search($a, $order) - array_search($b, $order);
}
else {
// Only $a has an order, so it goes before $b
return -1;
}
}
else if(in_array($b, $order)) {
// Only $b has an order, so it goes before $a
return 1;
}
else {
// Neither $a or $b has an order, so we don't care how they're sorted
return 0;
}
}
uksort($tags, 'my_sort');
I think you can just loop in your second array and build a new one using keys
$new = array();
foreach($array2 as $key => $val)
{
$new_array[] = $array1[$val];
}
Now the selected items are ordered in your $new_array
Sample

Given an array of values corresponding to the location of a leaf node, how would I access said node?

Note: I am unfamiliar with terminology regarding tree structures. Please forgive any oversights that may be a result of my ignorance!
Practical Example
Given an array as such:
Array
(
[0] => 0
[1] => 2
[2] => 8
[3] => 9
)
The tree node with the key "9" would be found at $tree[2][8][9] (with 0 being the root). Given the above array, how would I construct a statement in PHP that would access the leaf node?
Target Code
/*
Let's say I am given a $leafNodeID of 9, and I'd like to save some
data ($dataToSave) into said leaf node
*/
$leafNodeID = 9;
$dataToSave = array("name" => "foobar");
$tree_path = $this->findPathToRootNode($tree, $leafNodeID); // This returns the array found above.
${?????} = $dataToSave; // <-- Here be dragons
Thanks in advance!
Edit: For those wondering, my findPathToRootNode function just recursively finds the parent node, and saves it in the array format found above. If there's a better way to represent said data (especially if it solves my problem), that would be even better.
Edit: On a read through, it seems this question is less about trees, but rather how to access an array given its structure in a separate array. Tagging as such.
Make a self-targeting function. This should do the trick (untested)
function getLeaf($tree, $targetleaf, $depth = 0){
if (isset($targetleaf[$depth+1])
return getLeaf($tree[$targetleaf[$depth]], $targetleaf, $depth + 1)
else
return $tree[$depth];
}
With $tree being the data, $tree the path to the array, and $depth speaks for itself.
Call the function with
$leaf = getLeaf($tree,$targetleaf);

Categories