How to convert XML string to PHP array with a different structure? - php

I have this method that converts an XML string into a PHP array with different keys and values to fully make sense of that XML appropriately. However, when there are multiple children of the same kind, I'm not getting the desired result from the array and I'm confused on how to alter the method to do so.
This is what the method looks like:
/**
* Converts a XML string to an array
*
* #param $xmlString
* #return array
*/
private function parseXml($xmlString)
{
$doc = new DOMDocument;
$doc->loadXML($xmlString);
$root = $doc->documentElement;
$output[$root->tagName] = $this->domnodeToArray($root, $doc);
return $output;
}
/**
* #param $node
* #param $xmlDocument
* #return array|string
*/
private function domNodeToArray($node, $xmlDocument)
{
$output = [];
switch ($node->nodeType)
{
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++)
{
$child = $node->childNodes->item($i);
$v = $this->domNodeToArray($child, $xmlDocument);
if (isset($child->tagName))
{
$t = $child->tagName;
if (!isset($output['value'][$t]))
{
$output['value'][$t] = [];
}
$output['value'][$t][] = $v;
}
else if ($v || $v === '0')
{
$output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
}
if (isset($output['value']) && $node->attributes->length && !is_array($output['value']))
{
$output = ['value' => $output['value']];
}
if (!$node->attributes->length && isset($output['value']) && !is_array($output['value']))
{
$output = ['attributes' => [], 'value' => $output['value']];
}
if ($node->attributes->length)
{
$a = [];
foreach ($node->attributes as $attrName => $attrNode)
{
$a[$attrName] = (string)$attrNode->value;
}
$output['attributes'] = $a;
}
else
{
$output['attributes'] = [];
}
if (isset($output['value']) && is_array($output['value']))
{
foreach ($output['value'] as $t => $v)
{
if (is_array($v) && count($v) == 1 && $t != 'attributes')
{
$output['value'][$t] = $v[0];
}
}
}
break;
}
return $output;
}
Here is some example XML:
<?xml version="1.0" encoding="UTF-8"?>
<characters>
<character>
<name2>Sno</name2>
<friend-of>Pep</friend-of>
<since>1950-10-04</since>
<qualification>extroverted beagle</qualification>
</character>
<character>
<name2>Pep</name2>
<friend-of>Sno</friend-of>
<since>1966-08-22</since>
<qualification>bold, brash and tomboyish</qualification>
</character>
</characters>
Running the method and passing that XML as its parameter, will result with this array:
array:1 [▼
"characters" => array:2 [▼
"value" => array:1 [▼
"character" => array:2 [▼
0 => array:2 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"attributes" => []
"value" => "Sno"
]
"friend-of" => array:2 [▼
"attributes" => []
"value" => "Pep"
]
"since" => array:2 [▼
"attributes" => []
"value" => "1950-10-04"
]
"qualification" => array:2 [▼
"attributes" => []
"value" => "extroverted beagle"
]
]
"attributes" => []
]
1 => array:2 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"attributes" => []
"value" => "Pep"
]
"friend-of" => array:2 [▼
"attributes" => []
"value" => "Sno"
]
"since" => array:2 [▼
"attributes" => []
"value" => "1966-08-22"
]
"qualification" => array:2 [▼
"attributes" => []
"value" => "bold, brash and tomboyish"
]
]
"attributes" => []
]
]
]
"attributes" => []
]
]
What I want it to result to is (indentation could be wrong):
array:1 [▼
"characters" => array:2 [▼
"value" => array:2 [▼
0 => [
"character" => array:1 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"attributes" => []
"value" => "Sno"
]
"friend-of" => array:2 [▼
"attributes" => []
"value" => "Pep"
]
"since" => array:2 [▼
"attributes" => []
"value" => "1950-10-04"
]
"qualification" => array:2 [▼
"attributes" => []
"value" => "extroverted beagle"
]
]
"attributes" => []
]
]
]
1 => array:2 [▼
"character" => array:1 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"attributes" => []
"value" => "Pep"
]
"friend-of" => array:2 [▼
"attributes" => []
"value" => "Sno"
]
"since" => array:2 [▼
"attributes" => []
"value" => "1966-08-22"
]
"qualification" => array:2 [▼
"attributes" => []
"value" => "bold, brash and tomboyish"
]
]
"attributes" => []
]
]
]
]
"attributes" => []
]
]
So basically, I want the characters key's value key to be an array of two items, which basically includes the 2 character keys. This is only to happen if there are many of the same element on the same branch. The way it currently is, where the character key is an array with 2 elements doesn't work in my situation.
Altering the method above to reflect my needs hasn't been possible for me yet and I'm not sure what kind of approach I should take. Altering an array like this from a DOMDocument instance seems quite complicated.

The problem is when to add in a new level and when to carry on with just adding the data. I've changed this logic, adding comments to the code to help understand what happens and when...
private function domNodeToArray($node, $xmlDocument)
{
$output = [];
switch ($node->nodeType)
{
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++)
{
$child = $node->childNodes->item($i);
$v = $this->domNodeToArray($child, $xmlDocument);
if (isset($child->tagName))
{
$t = $child->tagName;
// if (!isset($output['value'][$t]))
// {
// $output['value'][$t] = [];
// }
// If the element already exists
if (isset($output['value'][$t]))
{
// Copy the existing value to new level
$output['value'][] = [$t => $output['value'][$t]];
// Add in new value
$output['value'][] = [$t => $v];
// Remove old element
unset($output['value'][$t]);
}
// If this has already been added at a new level
elseif ( isset($output['value'][0][$t]))
{
// Add it to existing extra level
$output['value'][] = [$t => $v];
}
else {
$output['value'][$t] = $v;
}
}
else if ($v || $v === '0')
{
$output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
}
if (isset($output['value']) && $node->attributes->length && !is_array($output['value']))
{
$output = ['value' => $output['value']];
}
if (!$node->attributes->length && isset($output['value']) && !is_array($output['value']))
{
$output = ['attributes' => [], 'value' => $output['value']];
}
if ($node->attributes->length)
{
$a = [];
foreach ($node->attributes as $attrName => $attrNode)
{
$a[$attrName] = (string)$attrNode->value;
}
$output['attributes'] = $a;
}
else
{
$output['attributes'] = [];
}
break;
}
return $output;
}
I've tried it with...
<?xml version="1.0" encoding="UTF-8"?>
<characters>
<character>
<name2>Sno</name2>
<friend-of>Pep</friend-of>
<since>1950-10-04</since>
<qualification>extroverted beagle</qualification>
</character>
<character>
<name2>Pep</name2>
<friend-of>Sno</friend-of>
<since>1966-08-22</since>
<qualification>bold, brash and tomboyish</qualification>
</character>
<character>
<name2>Pep2</name2>
<friend-of>Sno</friend-of>
<since>1966-08-23</since>
<qualification>boldish, brashish and tomboyish</qualification>
</character>
</characters>
to check that the <character> elements are all added to the right level.

I've done some changes to your function but I'm not sure if this is what you need.
private function domNodeToArray($node, $xmlDocument)
{
$output = ['value' => [], 'attributes' => []];
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
$child = $node->childNodes->item($i);
$v = $this->domNodeToArray($child, $xmlDocument);
if (isset($child->tagName)) {
$t = $child->tagName;
if (isset($output['value'][$t])) {
$output['value'][] = [$t => $output['value'][$t]];
$output['value'][] = [$t => $v];
unset($output['value'][$t]);
} else {
$output['value'][$t] = $v;
}
} elseif (($v && is_string($v)) || $v === '0') {
$output['value'] = htmlspecialchars((string)$v, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
}
if ($node->attributes->length) {
foreach ($node->attributes as $attrName => $attrNode) {
$output['attributes'][$attrName] = (string) $attrNode->value;
}
}
break;
}
return $output;
}
Output
array:1 [▼
"characters" => array:2 [▼
"value" => array:2 [▼
0 => array:1 [▼
"character" => array:2 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"value" => "Sno"
"attributes" => []
]
"friend-of" => array:2 [▼
"value" => "Pep"
"attributes" => []
]
"since" => array:2 [▼
"value" => "1950-10-04"
"attributes" => []
]
"qualification" => array:2 [▼
"value" => "extroverted beagle"
"attributes" => []
]
]
"attributes" => []
]
]
1 => array:1 [▼
"character" => array:2 [▼
"value" => array:4 [▼
"name2" => array:2 [▼
"value" => "Pep"
"attributes" => []
]
"friend-of" => array:2 [▼
"value" => "Sno"
"attributes" => []
]
"since" => array:2 [▼
"value" => "1966-08-22"
"attributes" => []
]
"qualification" => array:2 [▼
"value" => "bold, brash and tomboyish"
"attributes" => []
]
]
"attributes" => []
]
]
]
"attributes" => []
]
]

Related

Laravel how to use $request except on nested array

Is it possible to use $request->except on a nested array? This is the request data:
[▼
"id" => 1
"products" => array:1 [▼
0 => array:4 [▼
"id" => 1
"name" => "sample product"
"date" => "07/04/2022"
"frequency" => array:1 [ …1]
]
]
]
My goal is to remove some key value pairs e.g. removing id, date and frequency. My desired result would be:
[▼
"id" => 1
"products" => array:1 [▼
0 => array:1 [▼
"name" => "sample product"
]
]
]
What I've tried so far is to use Arr:except function:
$request->products = Arr::except($request->products['0'], ['id', 'date', 'frequency']);
But this should be applied on all items. Let's say I have two item products, the desired result would be:
[▼
"id" => 1
"products" => array:2 [▼
0 => array:1 [▼
"name" => "sample product"
]
1 => array:1 [▼
"name" => "sample product 2"
]
]
]
Need your inputs on what's the best approach for this. Thank you!
Unless I am missing something obvious, could you not just iterate over the products in your $request object?
$filtered = [];
foreach ($request->products as $product) {
$filtered[] = Arr::except($product, ['id', 'date', 'frequency']);
}
dd($filtered);
Resulting in $filtered containing:
^ array:2 [▼
"id" => 1
"products" => array:2 [▼
0 => array:1 [▼
"name" => "sample product"
]
1 => array:1 [▼
"name" => "sample product 2"
]
]
]
Update 1
I can see that the $request except only works on the top level
Not sure what you mean by this as the above principle works for me.
$filtered = [];
foreach ($request->products as $product) {
$filtered[] = Arr::except($product, ['id', 'date', 'frequency']);
}
$request->merge(['products' => $filtered]);
If I supply the above with the following:
{
"id": 1,
"products": [
{
"id": 1,
"name": "sample product",
"date": "07/04/2022",
"frequency": []
},
{
"id": 2,
"name": "sample product 2",
"date": "07/04/2022",
"frequency": []
}
]
}
Then do a dd($request->products); I get:
^ array:2 [▼
0 => array:1 [▼
"name" => "sample product"
]
1 => array:1 [▼
"name" => "sample product 2"
]
]

PHP - How can I get rid of square bracket and index in array

I am trying to create string associated with an object and create an array. Instead of having 0 => array:1, 1 =>array:1, 2 =>array:1. I want to have just "orange" => Option {}, "mango" => Option{}, "banana" => Option {} in an array as Array:5
array:5 [▼
0 => array:1 [▼
"orange" => Option {#76 ▼
-name: "american orange"
-botanical: "citrus"
-year: "1"
-color: "orange"
-size: "2"
-scope: "season"
-choices: []
}
]
1 => array:1 [▼
"Mango" => Option {#3098 ▼
-name: "american orange"
-botanical: ""
-year: "2"
-color: "green"
-size: "4"
-scope: "season"
-choices: []
}
]
2 => array:1 [▼
"banana" => Option {#3099 ▼
-name: "Billy"
-botanical: ""
-year: "1"
-color: "dark"
-size: "2"
-scope: "season"
-choices: array:5 [▼
"" => ""
"status" => "hard"
"condition" => "ripe"
"thickness" => "thick"
"long" => "long"
]
}
]
]
This is my code, $options[] = [$resultKey => $option]; This is where i am getting it wrong. [$resultKey => $option] is the one creating an array. All I want is $resultKey => $option so that I can achieve "Orange" => Option{}. I tried $options[] = (object)[$resultKey => $option]; it is still displaying indices. as I only want pair values without indices. ["Orange" => Option{}, "Mango" => Option{}, "Banana" => Option{} ]
$options = [];
foreach ($result as $resultKey => $resultValue) {
$option = new Option($resultKey['scope'], $resultValue['name'], '', $resultValue['botanical'], $resultValue['year'], $resultValue['color'], $resultValue['size'] !== 'choice' ? [] :$num[0]);
$options[] = [$resultKey => $option];
// dump([$resultKey => $option]);
}
```

php retrieve multidimensional array value

This is my sample array value returned
array:3 [▼
"Status" => array:1 [▼
0 => "200"
]
"Data" => array:1 [▼
0 => array:1 [▼
0 => array:2 [▼
"sellerId" => array:1 [▼
0 => "TEST01"
]
"currency" => array:1 [▼
0 => "USD"
]
]
]
]
"Date" => array:1 [▼
0 => "Dec 31 2019"
]
]
My sample code to retrieve the value from the array above
foreach($json_array as $key => $json) {
if($key == "Status") {
$status = $json[0];
} else if ($key == "Date") {
$date = $json[0];
} else {
dd($json[0][0]['sellerId'][0]);
}
}
I am using the method above to retrieve the value from multidimensional array. Is there any better approaches that i can use to achieve my way?
Just do :
$status = $json_array['Status'];
$date = $json_array['Date'];
Maybe you should add some context to your question.

Fill array with same data in php

I have an array with some data that returns me from the database, the problem is that not all the keys are associated, I should fill the missing keys with data to 0.
My array is by default is:
array:1 [▼
9 => array:2 [▼
4 => array:3 [▼
"Orange" => array:3 [▼
"price" => "600.00"
"total" => "690.00"
]
"Apple" => array:3 [▼
"price" => "650.00"
"total" => "870.00"
]
"Banana" => array:3 [▼
"price" => "50"
"total" => "40"
]
]
21 => array:1 [▼
"Apple" => array:3 [▼
"price" => "44"
"total" => "33"
]
]
]
]
The array should have the same structure but with data at 0
Result:
array:1 [▼
9 => array:2 [▼
4 => array:3 [▼
"Orange" => array:2 [▼
"price" => "600.00"
"total" => "690.00"
]
"Apple" => array:2 [▼
"price" => "650.00"
"total" => "870.00"
]
"Banana" => array:2 [▼
"price" => "50"
"total" => "40"
]
]
21 => array:3 [▼
"Apple" => array:2 [▼
"price" => "44"
"total" => "33"
],
"Orange" => array:2 [▼
"price" => "0"
"total" => "0"
],
"Banana" => array:2 [▼
"price" => "0"
"total" => "0"
]
]
]
]
The "for" below modify each array with adding any new value and also avoid duplicate values. Basically, it finds the differences and then merge that, setting the first array to clone each array.
$fruits = array (
1 => array('Manzana', 'Naranja', 'Pera'),
2 => array('Pera', 'Sandia'),
3 => array('Manzana', 'Melocotones')
);
print_r($fruits);
for ($i = 1; $i <= count($fruits)-1; $i++) {
$result = array_diff($fruits[1], $fruits[1+$i]);
$merge = array_merge($fruits[1+$i], $result);
$fruits[1+$i] = $merge;
$fruits[1] = $merge;
}
print_r($fruits);
?>
Here to see how run it!
https://repl.it/KqUi/3
If you will always use the first element as boilerplate you can do something like this:
$boilerplate = reset($array);
array_walk_recursive($boilerplate, function (&$value) {
$value = 0;
});
$array = array_map(function ($items) use ($boilerplate) {
return array_merge($items, array_diff_key($boilerplate, $items));
}, $array);
Here is working demo.

Merge two array to create an associative array

I have a php array something like this:
array:7 [▼
"id" => 13
"agent_id" => 1
"reserved_by" => 1
"vehicle_type" => "["Bus","Car"]"
"no_of_vehicle" => "["2","1"]"
"created_at" => "2017-06-13 05:46:49"
"updated_at" => "2017-06-13 05:46:49"
]
Here, vehicle_type and no_of_vehicle are in json_encode format. In the above case,
By json_decode I can get two arrays like this
vehicle_type
dd(json_decode($data->vehicle_type));
array:2 [▼
0 => "Bus"
1 => "Car"
]
no_of_vehicle
dd(json_decode($data->no_of_vehicle));
array:2 [▼
0 => "2"
1 => "1"
]
Now, what I want is to create an associative array of vehicle type and number equals 1.
array:3 [▼
Bus => "1"
Bus => "1"
Car => "1"
]
It's impossible as all you say because of unique key. But, Is to possible to make similar array with nested like:
array:3 [▼
array:1 [▼
Bus => "1"
]
array:1 [▼
Bus => "1"
]
array:1 [▼
Bus => "1"
]
]
That will be fine for me
Any idea, I am using laravel 5.3
Thanks
array:3 [▼
Bus => "1"
Bus => "1"
Car => "1"
]
It's impossible, because the keys must unique
you can make it to
["Bus", "Bus", "Car"]
or
["Bus" => 2, "Car" => 1]
Answer to updated question
You can do like this:
$array = [
"id" => 13,
"agent_id" => 1,
"reserved_by" => 1,
"vehicle_type" => array("Bus", "Car"),
"no_of_vehicle" => array("2", "1"),
"created_at" => "2017-06-13 05:46:49",
"updated_at" => "2017-06-13 05:46:49",
];
$result = [];
foreach ($array["vehicle_type"] as $key => $vehicle) {
$num = intval($array["no_of_vehicle"][$key]);
for ($i = 1; $i <= $num; $i++) {
$result[] = array($vehicle => "1");
}
}
the $result will be:
array:3 [▼
0 => array:1 [▼
"Bus" => "1"
]
1 => array:1 [▼
"Bus" => "1"
]
2 => array:1 [▼
"Car" => "1"
]
]
I would create an array like that :
array[
{
vehicle_type => 'Bus',
no_of_vehicle => 2,
},
{
vehicle_type => 'Car',
no_of_vehicle => 1,
}
]
or
array['Bus_1', 'Bus_2', 'Car_1']

Categories