How to Use Xpath and Dom to Replace a Node/Element in PHP

How can I use XPath and DOM to replace a node/element in php?

Since the answer in the linked duplicate is not that comprehensive, I'll give an example:

$dom = new DOMDocument;
$dom->loadXml($html); // use loadHTML if its invalid (X)HTML

// create the new element
$newNode = $dom->createElement('div', 'this is new');
$newNode->setAttribute('id', 'new_div');

// fetch and replace the old element
$oldNode = $dom->getElementById('old_div');
$oldNode->parentNode->replaceChild($newNode, $oldNode);

// print xml
echo $dom->saveXml($dom->documentElement);

Technically, you don't need XPath for this. However, it can happen that your version of libxml cannot do getElementById for non-validated documents (id attributes are special in XML). In that case, replace the call to getElementById with

$xp = new DOMXPath($dom);
$oldNode = $xp->query('//div[@id="old_div"]')->item(0);

Demo on codepad


To create a $newNode with child nodes without having to to create and append elements one by one, you can do

$newNode = $dom->createDocumentFragment();
$newNode->appendXML('
<div id="new_div">
<p>some other text</p>
<p>some other text</p>
<p>some other text</p>
<p>some other text</p>
</div>
');

Using DOMXPath to replace a node while maintaining its position

Use replaceChild instead of appendChild/removeChild.

Replace content of node using PHP and XPath

Instead of replacing the child node, loop over it's children, drop them and insert the new content as child node. Something like

foreach ($oldNode->childNodes as $child)
$oldNode->removeChild($child);
$oldNode->appendChild($replacement);

This will replace the contents (children) instead of the node itself.

php: dom replace node by root node from another dom

you didn't make one more step to get documentElement

$dom->documentElement->replaceChild($replace, $b);

and the result will be

<?xml version="1.0"?>
<a><b>Bi</b></a>

UPD:

In accordance with quite correct Yoshi comment, it is better to write this in such way

$b->parentNode->replaceChild($replace, $b);

Replace value in XML feed with php xpath

The error message is from a bug in PHP. Do not use the second argument for DOMDocument::createElement(). Create and append a text node to make sure that special characters are escaped into entities.

https://stackoverflow.com/a/27225157/2265374

Anything in a DOM is a node. Not only the element, but attribute and texts, too. You can work on the text nodes inside the value elements directly:

$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);

foreach ($xpath->evaluate('//property[@name = "region"]/value/text()') as $text) {
$text->data = str_replace("Cote d'azur","Côte d'Azur", $text->data);
}

echo $document->saveXml();

How can I replace a DOMNode with a DOMNodeList in PHP?

Thanks to some conversation in the OP comments, I was able to come up with the following replacement strategy which remains performant and compatible with all of the examples I posed.

$temp = new DOMDocument('1.0', 'UTF-8');
$temp->loadHTML('<fubar id="replacement">'.$val.'</fubar>');
$replacement = $temp->getElementById('replacement');

// If element is a text node just add a new node with the value, otherwise if it's an element with child nodes, iterate over them adding them to a fragment which can be imported as a whole.
if ($replacement->nodeType === XML_TEXT_NODE || ($replacement->nodeValue && $replacement->childNodes->length === 1 && $replacement->childNodes->item(1) === NULL)) {
// Text Node
$new_node = $element->ownerDocument->createTextNode($replacement->nodeValue);
} else {
// Node List
$new_node = $element->ownerDocument->createDocumentFragment();
$children = $replacement->childNodes->length - 1;
for ($i = 0; $i <= $children; $i++) {
$child = $element->ownerDocument->importNode($replacement->childNodes->item($i), TRUE);
$new_node->appendChild($child);
}
}
$element->parentNode->replaceChild($new_node,$element);
unset($replacement);
unset($temp);

--- N.B. ---

I struggled a LOT with the iteration over childNodes. I was able to see the childNodes existed in $replacement but they seemed to always be empty.

That is until I realized that the documentFragment needed to be created in the original element's doc rather than the temp one, AND the new child appended AFTER importing to the doc.

The root cause was that the child node ($replacement->childNodes->item($i)) couldn't be appended to a doc that it already existed in.

DOMElement replace HTML value

Have you tried the DOMDocument::saveXML function? (http://php.net/manual/en/domdocument.savexml.php)

It has a second argument $node with which you can specify which node to print the HTML/XML of.

So, for example:

<?php

$doc = new DOMDocument('1.0');
// we want a nice output
$doc->formatOutput = true;

$root = $doc->createElement('body');
$root = $doc->appendChild($root);

$title = $doc->createElement('h1', 'Home');
$root->appendChild($title);

$text = $doc->createTextNode('test{{test}}');
$text = $root->appendChild($text);

echo $doc->saveXML($root);

?>

This will give you:

<body>
<h1>Home</h1>
test{{test}}
</body>

If you do not want the <body> tag, you could cycle through all of its childnodes:

<?php

foreach($root->childNodes as $child){
echo $doc->saveXML($child);
}

?>

This will give you:

<h1>Home</h1>test{{test}}

Edit: you can then of course replace {{test}} by the regex that you are already using:

<?php

$xml = '';
foreach($root->childNodes as $child){
$xml .= preg_replace(
'/(?<replaceable>{{([a-z0-9_]+)}})/mi', '',
$doc->saveXML($child)
);
}

?>

This will give you:

<h1>Home</h1>test

Note: I haven't tested the code, but this should give you the general idea.

Replace node XMLDOM and PHP

$node = $xpath->query("/$racine/annonce[@id = '$annonce_no']"); does not give you a DOM node, it is a DOM node list. So you at least need $node = $xpath->query("/$racine/annonce[@id = '$annonce_no']")->item(0); to have an object that exposes a replaceChild method. However, the other variables assigned with e.g. $flagplus_new = $_POST['flagplus']; are not nodes at all you could pass to that method, nor are the variables $flagplus = $result->textContent; which are all string values. If you want to replace an element's content with a new value then selecting that element and setting the textContent seems like the right approach.

So assuming e.g.

$node = $xpath->query("/$racine/annonce[@id = '$annonce_no']")->item(0);

gives you the right element then you can select the description child with e.g.

$descEl = $xpath->query('description', $node)->item(0);

and changes its contents with e.g.

$descEl->textContent = $newDescription;

The only drawback is that the documentation http://php.net/manual/en/class.domnode.php says "5.6.1 The textContent property has been made writable (formerly it has been readonly). ".



Related Topics



Leave a reply



Submit