Custom Search
 


Fix the problem with PHP5 XML removeChild() method



I recently needed to use PHP XML DOM extension for a project. It started smoothly as I'm familiar with Microsoft XML DOM which is based on W3C standard and PHP XML DOM extension is based on the same standard.

The initial joy only lasted for two days and I ran into a three-day headache of desperately trying to make the removeChild() method work.

removeChild() method removes a child node from its parent node. If the parent node has a list of child nodes, we iterate thru the child node list (by using foreach loop or for loop) and remove the one(s) you want. Here comes the pitfall.

Normally, when iterating thru child node list, we either read data or do modifications on some nodes or attributes.

If you only do read-only stuff within the loop, or do minor changes on some node attributes, you are safe.

If you add new node or remove existing nodes from the child node list, the trouble starts.

The example XML

Here is the XML I'm going to use to show the problem and the solution. Save it to products.xml file.

<?xml version="1.0" encoding="iso-8859-1" ?>
<root>
<product id="1">
  <name>Aniseed Syrup</name>
  <CustomerID>22</CustomerID>
  </product>
<product id="1">
  <name>Aniseed Syrup</name>
  <CustomerID>29</CustomerID>
</product>
<product id="1">
  <name>Aniseed Syrup</name>
  <CustomerID>30</CustomerID>
</product>
<product id="2">
  <name>Chocolade</name>
  <CustomerID>29</CustomerID>
</product>
<product id="2">
  <name>Chocolade</name>
  <CustomerID>30</CustomerID>
</product>
<product id="3">
  <name>Tofu</name>
  <CustomerID>29</CustomerID>
  </product>
<product id="3">
  <name>Tofu</name>
  <CustomerID>30</CustomerID>
</product>
</root>

Code #1 - NOT working

In PHP code below, I want to remove all product nodes where the id attribute is equal to 1.

<?
// Code in here does not work

$dom = new DomDocument();
$dom->load("products.xml"); 

$root $dom->documentElement;

$products $root->getElementsByTagName("product");

foreach(
$products as $p)
{
    
$pid $p->getAttribute("id");
    
    if (
$pid == 1)
    {    
        
$parent $p->parentNode;
        
$parent->removeChild($p);        

        
// We can also use $root to remove the child
        // as root is the parent of $p
        //$root->removeChild($p);
    
}
}

header('Content-Type: text/xml');
echo 
$dom->saveXML();
?>

After running the code, surprisingly, not all products of id 1 are removed.

Code #2 - NOT working

Here is an equivalent code to remove all product nodes where the id attribute is equal to 1. Instead of using foreach loop, we used for loop and item method.

<?
// Code in here does not work

$dom = new DomDocument();
$dom->load("products.xml"); 

$root $dom->documentElement;

$products $root->getElementsByTagName("product");
$length $products->length;

// Iterate forwards
for ($i=0;$i++;$i<$length)
{
    
$p $products->item($i);    
    
$pid $p->getAttribute("id");
    
    if (
$pid == 1)
    {            
        
$parent $p->parentNode;
        
$parent->removeChild($p);
    }
}

header('Content-Type: text/xml');
echo 
$dom->saveXML();
?>

Same as before, products of id 1 are removed.

Code #3 - WORKING

Here is an equivalent code to remove all product nodes where the id attribute is equal to 1. This time, we used for loop but iterating backwards. Bravo! It's working like a charm!

<?
// Code in here works

$dom = new DomDocument();
$dom->load("products.xml"); 

$root $dom->documentElement;

$products $root->getElementsByTagName("product");
$length $products->length;

// Iterate backwards by decrementing the loop counter 
for ($i=$length-1;$i>=0;$i--)
{
    
$p $products->item($i);    
    
$pid $p->getAttribute("id");
    
    if (
$pid == 1)
    {            
        
$parent $p->parentNode;
        
$parent->removeChild($p);        
    }
}

header('Content-Type: text/xml');
echo 
$dom->saveXML();
?>

All products of id 1 are removed. I don't know why iterating backwards makes the difference, probably because when iterating over a parent container, it's not safe to remove items from or add items to it.

The lesson is, replace the foreach loop with a FOR loop, and iterate backwards by decrementing the loop counter. Hope this article has saved you a day or two.

Happy coding!


Copyright© GeeksEngine.com



Other Recent Articles from the Web Development category:

1.Java / JSP lost session value on redirect - FIXED
2.How to integrate PHP HTML Help .chm file with Crimson Editor
3.How to Connect to a MySQL Database from PHP
4.Use MySQL String Functions to Build Printable ASCII Character Chart
5.Five ways to create include path for PHP
6.How to use Date and Time data as integer value in PHP and MySQL
7.Absolute Path and Relative Path Explained

Copyright © 2017 GeeksEngine.com. All Rights Reserved.

This website is hosted by HostGator.

No portion may be reproduced without my written permission. Software and hardware names mentioned on this site are registered trademarks of their respective companies. Should any right be infringed, it is totally unintentional. Drop me an email and I will promptly and gladly rectify it.

 
Home | Feedback | Terms of Use | Privacy Policy