I need to recursively cast a PHP SimpleXMLObject to an array. The problem is that each sub element is also a PHP SimpleXMLElement.
Is this possible?
json_decode(json_encode((array) simplexml_load_string($obj)), 1);
Didn't test this one, but this seems to get it done:
function convertXmlObjToArr($obj, &$arr)
{
$children = $obj->children();
foreach ($children as $elementName => $node)
{
$nextIdx = count($arr);
$arr[$nextIdx] = array();
$arr[$nextIdx]['#name'] = strtolower((string)$elementName);
$arr[$nextIdx]['#attributes'] = array();
$attributes = $node->attributes();
foreach ($attributes as $attributeName => $attributeValue)
{
$attribName = strtolower(trim((string)$attributeName));
$attribVal = trim((string)$attributeValue);
$arr[$nextIdx]['#attributes'][$attribName] = $attribVal;
}
$text = (string)$node;
$text = trim($text);
if (strlen($text) > 0)
{
$arr[$nextIdx]['#text'] = $text;
}
$arr[$nextIdx]['#children'] = array();
convertXmlObjToArr($node, $arr[$nextIdx]['#children']);
}
return;
}
Taken from http://www.codingforums.com/showthread.php?t=87283
It is possible. This is a recursive function which prints out the tags of parent elements and the tags + contents of elements that have no more children. You can alter it to build an array:
foreach( $simpleXmlObject as $element )
{
recurse( $element );
}
function recurse( $parent )
{
echo '<' . $parent->getName() . '>' . "\n";
foreach( $parent->children() as $child )
{
if( count( $child->children() ) > 0 )
{
recurse( $child );
}
else
{
echo'<' . $child->getName() . '>';
echo iconv( 'UTF-8', 'ISO-8859-1', $child );
echo '</' . $child->getName() . '>' . "\n";
}
}
echo'</' . $parent->getName() . '>' . "\n";
}
I don't see the point since SimpleXMLObject can be threated just like arrays anyway...
But if you really need that, just check chassagnette's answer of in this thread or this post in a forum.
Depending on some troubles with CDATA, arrays etc.
(see: SimpleXMLElement to PHP Array)
I think, this would be the best solution:
public function simpleXml2ArrayWithCDATASupport($xml)
{
$array = (array)$xml;
if (count($array) === 0) {
return (string)$xml;
}
foreach ($array as $key => $value) {
if (is_object($value) && strpos(get_class($value), 'SimpleXML') > -1) {
$array[$key] = $this->simpleXml2ArrayWithCDATASupport($value);
} else if (is_array($value)) {
$array[$key] = $this->simpleXml2ArrayWithCDATASupport($value);
} else {
continue;
}
}
return $array;
}
Here my iterative (even if I don't think you will get a stack explosion by parsing data with a recursive one) implementation of a recursive cast to array. This is a more direct manner of doing it than passing through json_**decode functions:
function xml2Array(SimpleXMLElement $el): stdClass {
$ret = $el;
$stack = [&$ret];
while (count($stack) > 0) {
$cur = &$stack[count($stack) - 1];
array_splice($stack, -1);
$cur = (object) (array) $cur;
foreach ($cur as $key => $child) {
$childRef = &$cur->{$key};
if ($child instanceof SimpleXMLElement)
$stack[count($stack) - 1] = &$childRef;
elseif(is_array($child))
foreach ($childRef as $ckey => $cell) {
if ($cell instanceof SimpleXMLElement)
$stack[count($stack) - 1] = &$childRef[$ckey];
}
}
}
return $ret;
}
For those of you who have concerns about the CDATA case,
combining #ajayi-oluwaseun-emmanuel's answer with this answer worked for me:
$xml = simplexml_load_string($xml_str, 'SimpleXMLElement', LIBXML_NOCDATA);
$json = json_encode($xml);
$arr = json_decode($json,TRUE);
Related
After parsing some html, we have an array similar to ["\u00a0","\u00a0", "foo", "\u00a0", "bar"]. I want to filter out the "\u00a0" leaving us with ["foo", "bar"]. However, when we run an equality check, we are not getting a match.
We have tried
$item != "\u00a0"
$item != "\\u00a0"
$item != ""
$item != " "
$item != chr(160)
We get the same array returned without the "\u00a0" filtered out.
function getContent($xPath) {
$query = "//div[#class='WordSection1']";
$elements = $xPath->query($query);
if (!is_null($elements)) {
$content = array();
foreach ($elements as $element){
$nodes = $element->childNodes;
foreach ($nodes as $node) {
if ($node->nodeValue != "\u00a0") {
$content[] = $node->nodeValue;
}
}
}
return $content;
}
}
Try doing this condition:
if ($node->nodeValue != "\u{00a0}") {
$content[] = $node->nodeValue;
}
Here's the sample script I used:
<?php
$words = [chr(0xC2).chr(0xA0), 'foo', chr(0xC2).chr(0xA0), 'bar'];
$output = [];
foreach ($words as $word) {
if ($word != "\u{00a0}")
$output[] = $word;
}
var_dump($output);
I was trying to make a function con convert an array in a string value, the code is:
function makestring($array)
{
$outval = '';
foreach($array as $key=>$value)
{
if(is_array($value))
{
$outval .= makestring($value);
}
else
{
$outval .= $value;
}
}
return $outval;
}
But I get this error: Warning: Invalid argument supplied for foreach(). Can anybody please help me?
function makestring($array)
{
if(is_array( $array )) {
$outval = '';
foreach($array as $key=>$value)
{
if(is_array($value))
{
$outval .= makestring($value);
}
else
{
$outval .= $value;
}
}
return $outval;
} else {
die("Supplied argument is not an array");
}
}
OR
function makestring( array $array)
{
// you code goes here
}
Try this. You need to check passing argument is array or not before you use foreach
To prevent an error in foreach, it's better to set type to variable:
foreach((array)$someArray as $data) {
Even if $someArray is not an array, you'll not get any error.
→ Try this:
function makestring($array)
{
$outval = '';
$keys = array_keys( $array );
for( $x = 0; $x < count( $array ); $x++ )
{
if( is_array( $array[ $keys[ $x ] ] ) )
{
$outval .= makestring( $array[ $keys[ $x ] ] );
}
else
{
$outval .= $array[ $keys[ $x ] ];
}
}
return $outval;
}
Maybe the variables you use $key and $value has old data in them , normally a unset($key,$value) in the end of a foreach will cure that
$array = array(
'1',
'2',
'3',
array(
'4',
'5',
array(
'6',
'7'
)
)
);
$output = null;
if(!is_array($array)){
$array = array($array);
}
// use a reference to the $output in the callback
array_walk_recursive($array, function($array_element) use (&$output){
if(is_object($array_element) and method_exists($array_element, '__toString')){
$array_element = strval($array_element);
}
if(is_scalar($array_element)){
$output .= $array_element;
}else{
// found a non scalar... handle it! :)
}
});
echo $output;
Check this out.
array_walk_recursive((array) $array, function($val, $key) {
global $result;
$result .= $val;
});
You check via
if(is_array($value))
{
$outval .= makestring($value);
}
whether to call makestring() recursively. However, in the code that enters makestring() in the first place, you do no such check. That's ok, but then you have to check in makestring(), whether you actually got an array from the caller. That's called defensive programming.
Another option is to do the check on the calling side. That's part of Design By Contractâ„¢:
$var = i_usually_give_you_an_array_but_i_might_also_fail();
if (is_array($var)) {
echo makestring($value);
}
recursive implode() this convert multidimensional array to string
function mYimplode($array, $delimeter) {
$result='';
foreach ($array as $key) {
if (is_array($key)) {
$result .= mYimplode($key, $delimeter) . $delimeter;
} else {
$result .= $key . $delimeter;
}
}
$result = substr($result, 0, 0-strlen($delimeter));
return $result;
}
I currently have coded a way to turn a multidimensional array to comma separated values (I'm using pipes instead of commas for ease of debugging). The problem is, I know that the code I use to do this is bloody awful. It works how I want it to, but it's not nice at all.
What I need
Currently the arr_to_csv() function works for five levels of nested data within the multidimensional array. I need a recursive function to perform the same for one or an unlimited number of nested arrays, or a good nudge in the right direction. Recursion is not my strong point at all, but I know it's the way forward.
Data input
A multi-dimensional array is passed to the function.
array
'name' =>
array
'singular' => null
'plural' => null
'fields' =>
array
'price' =>
array
'label' => string 'Preis' (length=5)
'company_id' =>
array
'label' => null
'placeholder' => null
//...the array could go on...
The function returns the following...
This is exactly what I want...
0 => string 'name||singular||null' (length=20)
1 => string 'name||plural||null' (length=18)
2 => string 'fields||price||label||Preis' (length=27)
3 => string 'fields||company_id||label||null' (length=31)
4 => string 'fields||company_id||placeholder||null' (length=37)
5 => string 'fields||name||label||null' (length=25)
6 => string 'fields||name||placeholder||null' (length=31)
My horrible constructed function
I'm no good with recursion, so here's my awful list of foreachs. As you can see from the below code, this is terrible (no need to read the whole thing, it just copies itself). Please help me sort out my horrible code!
function arr_to_csv($data,$csv = '||') {
$array = array();
/* Epic amount of for each's. This could be done with recursion */
foreach($data as $key => &$value) {
if (!is_array($value)) {
$array[] = $key . $csv .(is_null($value)?'null':$value);
} else {
foreach ($value as $k => &$v) {
if (!is_array($v)) {
$array[] = $key . $csv . $k . $csv . (is_null($v) ? 'null' : $v);
} else {
foreach ($v as $kk => &$vv) {
if (!is_array($vv)) {
$array[] = $key . $csv . $k . $csv . $kk . $csv . (is_null($vv) ? 'null' : $vv);
} else {
foreach ($vv as $x => &$y) {
if (!is_array($y)) {
$array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . (is_null($y) ? 'null' : $y);
} else {
foreach ($y as $too => $long) {
if(!is_array($long)) {
$array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . $too . $csv. (is_null($long)?'null':$long);
} else {
foreach ($long as $omg => $why) {
if(!is_array($why)) {
$array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . $too . $csv . $omg . $csv . (is_null($why) ? 'null' : $why);
}
}
}
}
}
}
}
}
}
}
}
}
return $array;
}
This is some pseudocode, but it is a start:
$strings = [];
$flattenArray = function($arr, $level) use (&$strings, &$flattenArray) {
foreach($arr as $key=>$value){
$s = &$strings[$level];
if(!isset($s)) { $s = array(); }
$s[] = $key;
if(is_array($value)) {
$flattenArray($value, $level);
}
else {
$s[] = $value;
}
$level ++;
}
};
$flattenArray($myArray, 0);
foreach($strings as &$arr) {
$arr = implode("||", $arr);
}
Small demo with your array: http://codepad.viper-7.com/CR2SPY <-- It does not work fully, but it is a start
Update:
Here is a demo that I think works the way you want: http://codepad.viper-7.com/shN4pH
Code:
$strings = [];
$flattenArray = function($arr, $level, $k = null) use (&$strings, &$flattenArray) {
foreach($arr as $key=>$value){
if($k === null) {
$s = &$strings[$key];
}
else {
$s = &$strings[$k];
}
if(!isset($s)) {
$s = array();
}
$str = &$s[$level];
if(!isset($str)) {
$str = array();
if($k !== null) { $str[] = $k; }
}
$str[] = $key;
if(is_array($value)) {
$flattenArray($value, $level, ($k === null) ? $key : $k);
}
else {
$str[] = is_null($value) ? "null" : $value;
}
$level ++;
}
};
$flattenArray($myArray, 0);
$all = [];
foreach($strings as $k => $arr){
$new = array();
foreach($arr as $ky => $ar) {
$all[] = implode("||", $ar);
}
}
print_r($all);
I didn't check it, so in case it doesn't work it should be corrected.
function readarray($from_array, $addr = array()) {
global $output;
foreach ($from_array as $key => $value) {
if (is_Array($value) && count($value) > 0) {
$addr[] = $key;
readarray($value, $addr);
} else {
$output[] = implode('||', $addr) . $value;
}
}
}
$output = array();
foreach ($my_array as $key=>$value){
readarray($value);
}
// improved to get separate arrays of the root of initial array
Not sure if this will help you, but would it not be easier to flatten the array first and then format in in the way you want? To flatten the array try this:
$array = "YOUR ARRAY";
$FlatArray = array();
foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $k=>$v)
{
$FlatArray[$k] = $v;
}
Been trying all morning to come up with a recursive function for this. This is as close as I got, Maybe you can improve upon this.
$data = array('name' =>array('singular' => NULL,'plural' => NULL,'fields' =>array('price' =>array('label' =>'Preis','company_id' =>array('label' => NULL,'placeholder' => NULL)))));
function arr_to_csv($data,$csv = '||')
{
$list = "";
foreach($data as $key => &$value)
{
$list .= $key . $csv .((!is_array($value))?(is_null($value)?'null':$value): arr_to_csv($value))."<br>";
}
return $list;
}
print_r(arr_to_csv($data));
Returns This:
name||singular||null
plural||null
fields||price||label||Preis
company_id||label||null
placeholder||null
I want to combine strings in PHP. My script creates every possible combination like below.
$part1 = array('','d','n','s','g');
$part2 = array('a','e','o','oo');
$part3 = array('m','n','s','d','l','t','g','j','p');
$part4 = array('g','p','l','');
$part5 = array('g','p','l');
$part6 = array('a','e','o');
$part7 = array('d','l','r','');
$names = array();
foreach ($part1 as $letter1) {
foreach ($part2 as $letter2) {
foreach ($part3 as $letter3) {
foreach ($part4 as $letter4) {
foreach ($part5 as $letter5) {
foreach ($part6 as $letter6) {
foreach ($part7 as $letter7) {
$names[] = $letter1 . $letter2 . $letter3 . $letter4 . $letter5 . $letter6 . $letter7;
}
}
}
}
}
}
}
But I am not happy with my solution. I is quick and dirty code. Is there a solution wich works with a flexible number of part arrays, so I can extend the script by e.g. $part8 easiely? (without changing the loop construction)
Recursive one:
function buildNames( $parts, $chars = ''){
// Nothing to do, shouldn't happen
if( !count( $parts)){
return array();
}
$names = array();
$part = array_shift( $parts);
// Max level, we can build final names from characters
if( !count( $parts)){
foreach( $part as $char){
$names[] = $chars . $char;
}
return $names;
}
// "We need to go deeper" and build one more level with remembered chars so far
foreach( $part as $char){
$names = array_merge( $names, buildNames( $parts, $chars . $char));
}
return $names;
}
$parts = array( $part1, $part2, $part3, $part4, $part5, $part6, $part7);
$names = buildNames( $parts);
From head, from scratch, comment if something, but idea should be good
You could reduce this problem to six cartesian products:
cartesianProduct($part1,
cartesianProduct($part2,
cartesianProduct($part3,
cartesianProduct($part4,
cartesianProduct($part5,
cartesianProduct($part6, $part7))))));
function cartesianProduct($p1, $p2) {
$ret = array();
foreach($p1 as $l1)
foreach($p2 as $l2)
$ret[] = $l1 . $l2;
return $ret;
}
I have a foreach loop and I want to see if there is a next element in the loop so I can compare the current element with the next. How can I do this? I've read about the current and next functions but I can't figure out how to use them.
A unique approach would be to reverse the array and then loop. This will work for non-numerically indexed arrays as well:
$items = array(
'one' => 'two',
'two' => 'two',
'three' => 'three'
);
$backwards = array_reverse($items);
$last_item = NULL;
foreach ($backwards as $current_item) {
if ($last_item === $current_item) {
// they match
}
$last_item = $current_item;
}
If you are still interested in using the current and next functions, you could do this:
$items = array('two', 'two', 'three');
$length = count($items);
for($i = 0; $i < $length - 1; ++$i) {
if (current($items) === next($items)) {
// they match
}
}
#2 is probably the best solution. Note, $i < $length - 1; will stop the loop after comparing the last two items in the array. I put this in the loop to be explicit with the example. You should probably just calculate $length = count($items) - 1;
You could probably use while loop instead of foreach:
while ($current = current($array) )
{
$next = next($array);
if (false !== $next && $next == $current)
{
//do something with $current
}
}
If the indexes are continuous:
foreach ($arr as $key => $val) {
if (isset($arr[$key+1])) {
echo $arr[$key+1]; // next element
} else {
// end of array reached
}
}
As php.net/foreach points out:
Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during or after the foreach without resetting it.
In other words - it's not a very good idea to do what you're asking to do. Perhaps it would be a good idea to talk with someone about why you're trying to do this, see if there's a better solution? Feel free to ask us in ##PHP on irc.freenode.net if you don't have any other resources available.
You could get the keys/values and index
<?php
$a = array(
'key1'=>'value1',
'key2'=>'value2',
'key3'=>'value3',
'key4'=>'value4',
'key5'=>'value5'
);
$keys = array_keys($a);
foreach(array_keys($keys) as $index ){
$current_key = current($keys); // or $current_key = $keys[$index];
$current_value = $a[$current_key]; // or $current_value = $a[$keys[$index]];
$next_key = next($keys);
$next_value = $a[$next_key] ?? null; // for php version >= 7.0
echo "{$index}: current = ({$current_key} => {$current_value}); next = ({$next_key} => {$next_value})\n";
}
result:
0: current = (key1 => value1); next = (key2 => value2)
1: current = (key2 => value2); next = (key3 => value3)
2: current = (key3 => value3); next = (key4 => value4)
3: current = (key4 => value4); next = (key5 => value5)
4: current = (key5 => value5); next = ( => )
if its numerically indexed:
foreach ($foo as $key=>$var){
if($var==$foo[$key+1]){
echo 'current and next var are the same';
}
}
The general solution could be a caching iterator. A properly implemented caching iterator works with any Iterator, and saves memory. PHP SPL has a CachingIterator, but it is very odd, and has very limited functionality. However, you can write your own lookahead iterator like this:
<?php
class NeighborIterator implements Iterator
{
protected $oInnerIterator;
protected $hasPrevious = false;
protected $previous = null;
protected $previousKey = null;
protected $hasCurrent = false;
protected $current = null;
protected $currentKey = null;
protected $hasNext = false;
protected $next = null;
protected $nextKey = null;
public function __construct(Iterator $oInnerIterator)
{
$this->oInnerIterator = $oInnerIterator;
}
public function current()
{
return $this->current;
}
public function key()
{
return $this->currentKey;
}
public function next()
{
if ($this->hasCurrent) {
$this->hasPrevious = true;
$this->previous = $this->current;
$this->previousKey = $this->currentKey;
$this->hasCurrent = $this->hasNext;
$this->current = $this->next;
$this->currentKey = $this->nextKey;
if ($this->hasNext) {
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
}
}
}
public function rewind()
{
$this->hasPrevious = false;
$this->previous = null;
$this->previousKey = null;
$this->oInnerIterator->rewind();
$this->hasCurrent = $this->oInnerIterator->valid();
if ($this->hasCurrent) {
$this->current = $this->oInnerIterator->current();
$this->currentKey = $this->oInnerIterator->key();
$this->oInnerIterator->next();
$this->hasNext = $this->oInnerIterator->valid();
if ($this->hasNext) {
$this->next = $this->oInnerIterator->current();
$this->nextKey = $this->oInnerIterator->key();
} else {
$this->next = null;
$this->nextKey = null;
}
} else {
$this->current = null;
$this->currentKey = null;
$this->hasNext = false;
$this->next = null;
$this->nextKey = null;
}
}
public function valid()
{
return $this->hasCurrent;
}
public function hasNext()
{
return $this->hasNext;
}
public function getNext()
{
return $this->next;
}
public function getNextKey()
{
return $this->nextKey;
}
public function hasPrevious()
{
return $this->hasPrevious;
}
public function getPrevious()
{
return $this->previous;
}
public function getPreviousKey()
{
return $this->previousKey;
}
}
header("Content-type: text/plain; charset=utf-8");
$arr = [
"a" => "alma",
"b" => "banan",
"c" => "cseresznye",
"d" => "dio",
"e" => "eper",
];
$oNeighborIterator = new NeighborIterator(new ArrayIterator($arr));
foreach ($oNeighborIterator as $key => $value) {
// you can get previous and next values:
if (!$oNeighborIterator->hasPrevious()) {
echo "{FIRST}\n";
}
echo $oNeighborIterator->getPreviousKey() . " => " . $oNeighborIterator->getPrevious() . " -----> ";
echo "[ " . $key . " => " . $value . " ] -----> ";
echo $oNeighborIterator->getNextKey() . " => " . $oNeighborIterator->getNext() . "\n";
if (!$oNeighborIterator->hasNext()) {
echo "{LAST}\n";
}
}
You could get the keys of the array before the foreach, then use a counter to check the next element, something like:
//$arr is the array you wish to cycle through
$keys = array_keys($arr);
$num_keys = count($keys);
$i = 1;
foreach ($arr as $a)
{
if ($i < $num_keys && $arr[$keys[$i]] == $a)
{
// we have a match
}
$i++;
}
This will work for both simple arrays, such as array(1,2,3), and keyed arrays such as array('first'=>1, 'second'=>2, 'thrid'=>3).
A foreach loop in php will iterate over a copy of the original array, making next() and prev() functions useless. If you have an associative array and need to fetch the next item, you could iterate over the array keys instead:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = $items[array_keys($items)[$index + 1]];
}
Since the resulting array of keys has a continuous index itself, you can use that instead to access the original array.
Be aware that $next will be null for the last iteration, since there is no next item after the last. Accessing non existent array keys will throw a php notice. To avoid that, either:
Check for the last iteration before assigning values to $next
Check if the key with index + 1 exists with array_key_exists()
Using method 2 the complete foreach could look like this:
foreach (array_keys($items) as $index => $key) {
// first, get current item
$item = $items[$key];
// now get next item in array
$next = null;
if (array_key_exists($index + 1, array_keys($items))) {
$next = $items[array_keys($items)[$index + 1]];
}
}
$next_data = $data;
$prev_key = null;
$prev_value = null;
foreach($data as $key => $value)
{
array_shift($next_data);
$next_key = key($next_data);
$next_value = $next_data[$next_key] ?? null;
// Do something here...
$prev_key = $key;
$prev_value = $value;
}
or if the array is associative then you could use current() similar to Andrei Krasutski's solution and key()
$values = [];
array_push($values, ["XYZ"=>100]);
array_push($values, ["ABC"=>10]);
array_push($values, ["XYZ"=>130]);
array_push($values, ["DEF"=>4]);
array_push($values, ["XYZ"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo key($currentValue) . "=" . current($currentValue) . " followed by " . key($nextValue) . "/" . current($nextValue) . "<br>\n";
} else {
echo key($currentValue) . "=" . current($currentValue);
}
}
See https://onlinephp.io/c/dc58d for a running example.
or if the array is using named pairs:
$values = [];
array_push($values, ["type"=>"XYZ", "value"=>100]);
array_push($values, ["type"=>"ABC", "value"=>10]);
array_push($values, ["type"=>"XYZ", "value"=>130]);
array_push($values, ["type"=>"DEF", "value"=>"Lorem"]);
array_push($values, ["type"=>"XYZ", "value"=>5]);
$count = count($values);
foreach ($values as $index => $currentValue) {
if ($index < $count - 1) {
$nextValue = $values[$index + 1];
echo $currentValue['type'] . "=" . $currentValue['value']
. " followed by " . $nextValue['type'] . "/" . $nextValue['value'] . "<br>\n";
} else {
echo $currentValue['type'] . "=" . $currentValue['value'];
}
}