SimpleXML remove nodes - php

I've got a foreach loop that is only running once and it has me stumped.
1: I load an array of status values (either "request", "delete", or "purchased")
2: I then load an xml file and need to loop through the "code" nodes and update their status, BUT if the new code is "delete" I want to remove it before moving onto the next one
XML structure is....
<content>
.... lots of stuff
<codes>
<code date="xxx" status="request">xxxxx</code>
.. repeat ...
</codes>
</content>
and the php code is ...
$newstatus = $_POST['updates'];
$file = '../apps/templates/'.$folder.'/layout.xml';
$xml2 = simplexml_load_file($file);
foreach($xml2->codes->code as $code){
if($code['status'] == "delete") {
$dom=dom_import_simplexml($code);
$dom->parentNode->removeChild($dom);
}
}
$xml2->asXml($file);
I've temporarily removed the updating so I can debug the delete check.
This all works BUT it only removes the 1st delete and leaves all the other deletes even though it's a foreach loop??.
Any help greatly appreciated.

Deleting multiple times in the same iteration is unstable. E.g. if you remove the second element, the third becomes the second and so on.
You can prevent that by storing the elements to delete into an array first:
$elementsToRemove = array();
foreach ($xml2->codes->code as $code) {
if ($code['status'] == "delete") {
$elementsToRemove[] = $code;
}
}
And then you remove the element based on the array which is stable while you iterate over it:
foreach ($elementsToRemove as $code) {
unset($code[0]);
}
You could also put the if-condition into an xpath query which does return the array directly (see the duplicate question for an example) or by making use of iterator_to_array().

SimpleXML node lists are plain arrays of references, and like with any deleting of items while forward iterating through an array, the array position pointer can get mixed up because the expected next item has disappeared.
The simple way to remove a bunch of children in SimpleXML without using an extra array is to iterate in reverse (=decrementing the index), taking the looping in your example to:
// FOR EACH NODE IN REVERSE
$elements=$xml2->xpath('codes/code');
$count=count($elements);
for($j=$count-1;$j>=0;$j--){
// IF TO DELETE
$code=$elements[$j];
if($code['status']=="delete"){
// DELETE ELEMENT
$dom=dom_import_simplexml($code);
$dom->parentNode->removeChild($dom);
}
}
Of course, if your other processing requires forward iterating through the elements, then using an array is the best.

Related

JSON unset element at specific index in php

I have two REST urls that I'm using. The first one has event profiles and in my code I'm looping through the and searching each of those in the second rest url.
Use array_filter() to keep the elements that match the filtering criteria, rather than unsetting elements during a loop.
foreach ($policyPayloadCopy["EventProfile"] as &$profile) {
if (count($profile["EventRuleIDList"]["EventRuleID"]) > 1) {
$profile["EventRuleIDList"]["EventRuleID"] = array_filter($profile["EventRuleIDList"]["EventRuleID"], function($rule) use ($checkedArr) {
return in_array($rule["Severity"], $checkedArr);
});
}
}
The reference variable &$profile means that the assignment to $profile["EventRuleIDList"]["EventRuleID"] affects the original $policyPayloadCopy array rather than a copy.

Remove first element from simple array in loop

This question has been asked a thousand times, but each question I find talks about associative arrays where one can delete (unset) an item by using they key as an identifier. But how do you do this if you have a simple array, and no key-value pairs?
Input code
$bananas = array('big_banana', 'small_banana', 'ripe_banana', 'yellow_banana', 'green_banana', 'brown_banana', 'peeled_banana');
foreach ($bananas as $banana) {
// do stuff
// remove current item
}
In Perl I would work with for and indices instead, but I am not sure that's the (safest?) way to go - even though from what I hear PHP is less strict in these things.
Note that after foreach has run, I expected var_dump($bananas) to return an empty array (or null, but preferably an empty array).
1st method (delete by value comparison):
$bananas = array('big_banana', 'small_banana', 'ripe_banana', 'yellow_banana', 'green_banana', 'brown_banana', 'peeled_banana');
foreach ($bananas as $key=>$banana) {
if($banana=='big_banana')
unset($bananas[$key]);
}
2nd method (delete by key):
$bananas = array('big_banana', 'small_banana', 'ripe_banana', 'yellow_banana', 'green_banana', 'brown_banana', 'peeled_banana');
unset($bananas[0]); //removes the first value
unset($bananas[count($bananas)-1]); //removes the last value
//unset($bananas[n-1]); removes the nth value
Finally if you want to reset the keys after deletion process:
$bananas = array_map('array_values', $bananas);
If you want to empty the array completely:
unset($bananas);
$bananas= array();
it still has the indexes
foreach ($bananas as $key => $banana) {
// do stuff
unset($bananas[$key]);
}
for($i=0; $i<count($bananas); $i++)
{
//doStuff
unset($bananas[$i]);
}
This will delete every element after its use so you will eventually end up with an empty array.
If for some reason you need to reindex after deleting you can use array_values
How about a while loop with array_shift?
while (($item = array_shift($bananas)) !== null)
{
//
}
Your Note: Note that after foreach has run, I expected var_dump($bananas) to return an empty array (or null, but preferably
an empty array).
Simply use unset.
foreach ($bananas as $banana) {
// do stuff
// remove current item
unset($bananas[$key]);
}
print_r($bananas);
Result
Array
(
)
This question is old but I will post my idea using array_slice for new visitors.
while(!empty($bananas)) {
// ... do something with $bananas[0] like
echo $bananas[0].'<br>';
$bananas = array_slice($bananas, 1);
}

Checking the first element of array, regardless of array indexes

I have a need to check if the elements in an array are objects or something else. So far I did it like this:
if((is_object($myArray[0]))) { ... }
However, on occasion situations dictate that the input array does not have indexes that start with zero (or aren't even numeric), therefore asking for $myArray[0] will generate a Notice, but will also return the wrong result in my condition if the first array element actually is an object (but under another index).
The only way I can think of doing here is a foreach loop where I would break out of it right on the first go.
foreach($myArray as $element) {
$areObjects = (is_object($element));
break;
}
if(($areObjects)) { ... }
But I am wondering if there is a faster code than this, because a foreach loop seems unnecessary here.
you can use reset() function to get first index data from array
if(is_object(reset($myArray))){
//do here
}
You could get an array of keys and get the first one:
$keys = array_keys($myArray);
if((is_object($myArray[$keys[0]]))) { ... }
try this
reset($myArray);
$firstElement = current($myArray);
current gets the element in the current index, therefore you should reset the pointer of the array to the first element using reset
http://php.net/manual/en/function.current.php
http://php.net/manual/en/function.reset.php

PHP array of numbers with range() and foreach loop

I have a really strange problem with range();
According to docs :
Create an array containing a range of elements
But when I do :
foreach (range(900,950,1) as $art_id){
//ob_start();
//do stuff
//do a lot more stuff
echo $art_id;
//ob_get_clean(); }
or even
$arts_id = range (900, 920);
foreach ($arts_id as $art_id){
//ob_start();
//do stuff
//do a lot more stuff
echo $art_id;
//ob_get_clean(); }
The output is strangly repeating itself in a series like
"900,900,901,900,901,902,900,901,9002,903,900..."
meaning it is comming back to the first ID after each loop.
(1st iteration -> 900
2nd iteration -> 900,901
3rd iteration -> 900,901,902
...)
When I just put a manual array it works perfectly in order and no duplicates :
$arts_id = array(900,901,902,903,904,905,906,907,908,909,910...);
What Am I doing wrong (again ?? )
EDIT I
here is the whole script :
http://pastebin.com/ZHm3ub6n
It is actually a slightly modified version of the slashdot scraping example included in the simplehtmldom script . Nothing special.
It is executed inside WP but OUTSIDE the loop ..
It must be in the rest of your code, because this works fine.
please share more of the script.
It looks like the foreach is nested within a similair foreach,
$arts_id = range (900, 920);
foreach ($arts_id as $art_id){
foreach (range (900,$art_id) as $art_id2){
echo $art_id2."<br/>";
}
}
This produces an output you've described
EDIT
Personally i'd add the the function scraping_slashdot a reset of the variable $ret just in case.
for example:
$ret = array();
Currently the echo of $output is within the loop, which creates an output like the following:
Article 1
Article 1, Article 2
Article 1, Article 2, Article 3
etc.
place echo $output outside the loop, or $ouptut = ''; inside the loop.

DOM removing selected child nodes

I have a dom element with html inside chat contains some html elements I'd like to remove, while still keeping some tags that are ok.
I try to iterate through child elements all child elements and delete those that need to be removed
foreach ($node->getElementsByTagName('*') as $element)
if ($element->nodeName != 'br')
$node->removeChild($element);
But this throws a Not Found Error exception which not being caught causes a fatal error.
How would I solve this problem ?
Use the following instead to remove the node:
$element->parentNode->removeChild($element);
getElementsByTagName('*') finds all descendent elements, not child elements. So some of the $element you want to remove are not children of $node, hence the failure.
I'm not 100% sure what your intention is here, but most likely you just want to remove certain immediate children. In this case, do the following:
$nodestoremove = array();
foreach ($node->childNodes as $n) {
if ($n->nodeType===XML_ELEMENT_NODE and $n->nodeName!=='br') {
$nodestoremove[] = $n;
}
}
foreach ($nodestoremove as $n) {
$node->removeChild($n);
}
unset($nodestoremove); // so nodes can be garbage-collected
echo $node->C14N(); // xml fragment after removal
Note that we make two passes: one to identify the nodes to delete, and a second pass to delete. This is because childNodes is an active list, so we can't iterate through it forwards as we delete. (Although we could iterate through it backwards.)

Categories