Remove a child with a specific attribute, in SimpleXML for PHP

Viewed 111056

I have several identical elements with different attributes that I'm accessing with SimpleXML:

<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>

I need to remove a specific seg element, with an id of "A12", how can I do this? I've tried looping through the seg elements and unsetting the specific one, but this doesn't work, the elements remain.

foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
18 Answers

While SimpleXML provides a way to remove XML nodes, its modification capabilities are somewhat limited. One other solution is to resort to using the DOM extension. dom_import_simplexml() will help you with converting your SimpleXMLElement into a DOMElement.

Just some example code (tested with PHP 5.2.5):

$data='<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>';
$doc=new SimpleXMLElement($data);
foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12') {
        $dom=dom_import_simplexml($seg);
        $dom->parentNode->removeChild($dom);
    }
}
echo $doc->asXml();

outputs

<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>

By the way: selecting specific nodes is much more simple when you use XPath (SimpleXMLElement->xpath):

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above

Just unset the node:

$str = <<<STR
<a>
  <b>
    <c>
    </c>
  </b>
</a>
STR;

$xml = simplexml_load_string($str);
unset($xml –> a –> b –> c); // this would remove node c
echo $xml –> asXML(); // xml document string without node c

This code was taken from How to delete / remove nodes in SimpleXML.

There is a way to remove a child element via SimpleXml. The code looks for a element, and does nothing. Otherwise it adds the element to a string. It then writes out the string to a file. Also note that the code saves a backup before overwriting the original file.

$username = $_GET['delete_account'];
echo "DELETING: ".$username;
$xml = simplexml_load_file("users.xml");

$str = "<?xml version=\"1.0\"?>
<users>";
foreach($xml->children() as $child){
  if($child->getName() == "user") {
      if($username == $child['name']) {
        continue;
    } else {
        $str = $str.$child->asXML();
    }
  }
}
$str = $str."
</users>";
echo $str;

$xml->asXML("users_backup.xml");
$myFile = "users.xml";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, $str);
fclose($fh);

A new idea: simple_xml works as a array.

We can search for the indexes of the "array" we want to delete, and then, use the unset() function to delete this array indexes. My example:

$pos=$this->xml->getXMLUser();
$i=0; $array_pos=array();
foreach($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile as $profile) {
    if($profile->p_timestamp=='0') { $array_pos[]=$i; }
    $i++;
}
//print_r($array_pos);
for($i=0;$i<count($array_pos);$i++) {
    unset($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile[$array_pos[$i]]);
}

I had a similar task - remove child elements, that are already present with the specified attribute. In other words, remove duplicates in xml. I have the following xml structure:

<rups>
    <rup id="1">
         <profiles> ... </profiles>
         <sections>
             <section id="1.1" num="Б1.В" parent_id=""/>
             <section id="1.1.1" num="Б1.В.1" parent_id="1.1"/>
             ...
             <section id="1.1" num="Б1.В" parent_id=""/>
             <section id="1.1.2" num="Б1.В.2" parent_id="1.1"/>
             ...
         </sections>
    </rup>
    <rup id="2">
         ...
    </rup>
    ...
 </rups>

For example, rups/rup[@id='1']/sections/section[@id='1.1'] elements are duplicated and I only need to leave the first one. I'm using a reference to array of elements, loop-for and unset():

$xml = simplexml_load_file('rup.xml');
foreach ($xml->rup as $rup) {
    $r_s = [];
    $bads_r_s = 0;
    $sections = &$rup->sections->section;
    for ($i = count($sections)-1; $i >= 0; --$i) {
        if (in_array((string)$sections[$i]['id'], $r_s)) {
            $bads_r_s++;
            unset($sections[$i]);
            continue;
        }
        $r_s[] = (string)$sections[$i]['id'];
    }
}
$xml->saveXML('rup_checked.xml');

Your initial approach was right, but you forgot one little thing about foreach. It doesn't work on the original array/object, but creates a copy of each element as it iterates, so you did unset the copy. Use reference like this:

foreach($doc->seg as &$seg) 
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
Related