views:

341

answers:

2

Ok, so I had this neat little idea the other night to create a helper class for DOMDOCUMENT that mimics, to some extent, jQuery's ability to manipulate the DOM of an HTML or XML-based string. Instead of css selectors, XPath is used. For example:

$Xml->load($source)
    ->path('//root/items')
    ->each(function($Context)
    {
        echo $Context->nodeValue;
    });

This would invoke a callback function on every resulting node. Unfortunately, PHP version < 5.3.x doesn't support lambda functions or closures, so I'm forced to do something a bit more like this for the time being:

$Xml->load($source)
    ->path('//root/items')
    ->walk('printValue', 'param1', 'param2');

Everything is working great at the moment and I think this project would be useful to a lot of people, but I'm stuck with one of the functions. I am attempting to mimic jQuery's 'replace' method. Using the following code, I can accomplish this quite easily by applying the following method:

$Xml->load($source)
    ->path('//root/items')
    ->replace($Xml->createElement('foo', 'bar')); // can be an object, string or XPath pattern

The code behind this method is:

public function replace($Content)
{
 foreach($this->results as $Element)
 {
  $Element->parentNode->appendChild($Content->cloneNode(true));
  $Element->parentNode->removeChild($Element);
 }

 return $this;
}

Now, this works. It replaces every resulting element with a cloned version of $Content. The problem is that it adds them to the bottom of the parent node's list of children. The question is, how do I clone this element to replace other elements, while still retaining the original position in the DOM?

I was thinking about reverse-engineering the node I was to replace. Basically, copying over values, attributes and element name from $Content, but I am unable to change the actual element name of the target element.

Reflection could be a possibility, but there's gotta be an easier way to do this.

Anybody?

A: 

Lookup if $element has a nextsibbling prior to removing if so do an insertBefore that next sibling otherwise simply append.

public function replace($Content)
{
        foreach($this->results as $Element)
        {
             if ($Element->nextSibling) {
        $NextSiblingReference = $Element->nextSibling;
        $Element->parentNode->insertBefore($Content->cloneNode(true),$NextSiblingReference);
       }
       else {
        $Element->parentNode->appendChild($Content->cloneNode(true)); 
       }
                $Element->parentNode->removeChild($Element);
        }

        return $this;
}

Totally untested though.

Or as AnthonyWJones suggested replaceChild , big oomph how did i miss that moment :)

Martijn Laarman
Hrm, I'll give it a whirl. Thx!
Wilhelm Murdoch
+2  A: 

Use replaceChild instead of appendChild/removeChild.

AnthonyWJones
Your suggestion worked quite well. Although, I had to use 'importNode' in combination with 'appendChild' and 'cloneNode'. Works beautifully!Cheers!
Wilhelm Murdoch
If you are using importNode you don't need to clone it as well, importNode has, out of necessity, to clone the node.
AnthonyWJones