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 codepadTo 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
How to Get Products from a Particular Category in Magento Ecommerce
PHP Error: Cannot Use Object of Type Stdclass as Array (Array and Object Issues)
How to Get an Array of Data from $_Post
Laravel League/Flysystem Getting File Url with Aws S3
How to Deal with "Method Not Found in Class" Warning for Magically Implemented Methods
Imagemagick Brew Installation with PHP Module in MAC Os X
MySQL Insert ....On Duplicate Update - Adds One to the Autoincrement
Disable Doctrine 2 Lazy Loading When Using Jms Serializer
Add Some Attribute Values to Woocommerce Variable Product Title from Chosen Variation
Change Mime Type of Output in PHP
Using Moodle Create Users and Enroll Them in Courses via SQL
How to Assign PHP Array Values to JavaScript Array
Converting Float Decimal to Fraction
Yii Framework 2.0 Login with User Database
Where to Store Database Login Credentials for a PHP Application