I have an XML file that currently has 4 nodes with the same name:
The file looks like this: (data.xml)
<?xml version="1.0"?>
<products>
<product>
<ItemId>531670</ItemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name>
<name>Car, Marine & GPS</name>
<name>Car Installation Parts</name>
<name>Deck Installation Parts</name>
<name>Antennas & Adapters</name>
</product>
</products>
There are 4 nodes with the same name. (the name node).
I then use this PHP code to replace the node names
<?php
/**
* #param $xml string Your XML
* #param $old string Name of the old tag
* #param $new string Name of the new tag
* #return string New XML
*/
function renameTags($xml, $old, $new)
{
$dom = new DOMDocument();
$dom->loadXML($xml);
$nodes = $dom->getElementsByTagName($old);
$toRemove = array();
foreach ($nodes as $node)
{
$newNode = $dom->createElement($new);
foreach ($node->attributes as $attribute)
{
$newNode->setAttribute($attribute->name, $attribute->value);
}
foreach ($node->childNodes as $child)
{
$newNode->appendChild($node->removeChild($child));
}
$node->parentNode->appendChild($newNode);
$toRemove[] = $node;
}
foreach ($toRemove as $node)
{
$node->parentNode->removeChild($node);
}
return $dom->saveXML();
}
// Load XML from file data.xml
$xml = file_get_contents('data.xml');
$xml = renameTags($xml, 'name', 'newName');
echo $xml;
?>
This function replaces all of the name nodes with newName; however, I want to only replace one instance of the name tag because I want to rename each of the name tags.
If I call another
$xml = renameTags($xml, 'name', 'newName2');
It wont work, it will only use the first instance of $xml.
Any Idea how I can change my code to allow me to replace each name node individually?
If you simply want to rename the first node you encounter, add a break; statement at the end of your first foreach loop like this first example. However, this is horribly inefficient and a better way to do it is demonstrated in the second (CORRECT) example at the bottom.
THE WRONG WAY
Everytime you replace XML like this an angel sheds a tear ...
$src = "
<products>
<product>
<ItemId>531670</ItemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name>
<name>Car, Marine & GPS</name>
<name>Car Installation Parts</name>
<name>Deck Installation Parts</name>
<name>Antennas & Adapters</name>
</product>
</products>
";
function renameTags($xml, $old, $new)
{
$dom = new DOMDocument();
$dom->loadXML($xml);
$nodes = $dom->getElementsByTagName($old);
$toRemove = array();
foreach ($nodes as $node)
{
$newNode = $dom->createElement($new);
foreach ($node->attributes as $attribute)
{
$newNode->setAttribute($attribute->name, $attribute->value);
}
foreach ($node->childNodes as $child)
{
$newNode->appendChild($node->removeChild($child));
}
$node->parentNode->appendChild($newNode);
$toRemove[] = $node;
break;
}
foreach ($toRemove as $node)
{
$node->parentNode->removeChild($node);
}
$dom->formatOutput = TRUE;
return $dom->saveXML();
}
$xml = renameTags($src, 'name', 'newName');
echo $xml;
THE CORRECT WAY
function renameTags($xml, $old, $new)
{
$dom = new DOMDocument();
$dom->loadXML($xml);
// find the first node with the specified tag name
$oldNode = $dom->getElementsByTagName($old)->item(0);
// clone the node (deep copy)
$doppelganger = $oldNode->cloneNode(TRUE);
// import our cloned node to this dom document
$doppelganger = $dom->importNode($doppelganger, true);
// Create new node with the value from the copied node
$newNode = $dom->createElement($new, $doppelganger->nodeValue);
// update all the attributes of the new node with those from the copy
foreach ($doppelganger->attributes as $attrName => $attrNode) {
$newNode->setAttribute($attrName, $attrNode);
}
// append the newNode copy to the dom
$oldNode->parentNode->appendChild($newNode);
// remove the old node
$oldNode->parentNode->removeChild($oldNode);
$dom->formatOutput = TRUE;
return $dom->saveXML();
}
Related
I have all properties listed in XML file with this structure
<property>
<details>
<object>25.5 m2 Flat in New York</object>
</details>
</property>
<property>
<details>
<object>95.6 m2 House in New Jersey</object>
</details>
</property>
Now I want to use PHP to find the node with a specific <object> value and to remove the parent node (<property>). How can I do it?
I tried by doing the code below but I cannot manage to work.
$doc = new DOMDocument;
$doc->load('../openimmo/xml-import1.xml');
$thedocument = $doc->documentElement;
$list = $thedocument->getElementsByTagName('property');
$nodeToRemove = null;
foreach ($list as $domElement) {
$attrValue = $domElement->getElementsByTagName('object');
foreach ($attrValue as $item) {
if ($item->nodeValue == $_GET['delete']) {
$nodeToRemove = $domElement;
}
}
}
if ($nodeToRemove != null)
$thedocument->removeChild($nodeToRemove);
echo $doc->saveXML();
You can use Xpath expressions to fetch nodes. This allows you to use conditions.
$document = new DOMDocument;
//$document->load('../openimmo/xml-import1.xml');
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$objectText = '25.5 m2 Flat in New York';
$properties = $xpath->evaluate('//property[details/object = "'.$objectText.'"]');
foreach ($properties as $property) {
// remove the node (PHP 8)
$property->remove();
}
echo $document->saveXML();
The other difference is that the result of DOMXpath::evaluate() is not live. Unlike the result from DOMNode::getElementsByTagName() it does not change if the DOM changes.
PHP 8 adds DOM Living Standard methods. In PHP 7 you would have to use $property->parentNode->removeChild($property).
I'm trying to figure out how to rename a node in XML using PHP?
I Have come this far:
$dom = new DOMDocument( '1.0' );
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
// load the xml file
$dom->loadXML( '<?xml version="1.0" encoding="ISO-8859-1"?>
<library>
<data>
<invite>
<username>jmansa</username>
<userid>1</userid>
</invite>
<update>1</update>
</data>
</library>', LIBXML_NOBLANKS );
$xpath = new DOMXPath($dom);
//find all 'data' nodes.
$node = $xpath->query("//data");
// if found
if( $node->length ) {
foreach ($node as $n) {
// RENAME HERE? //
}
}
echo "<xmp>". $dom->saveXML() ."</xmp>";
Now, I want to rename <data> to <invites>. Can this be done and if yes, how?
A Node's name ("data" or "invites" respectively) cannot be renamed via the DOM because the Node::nodeName property is read-only.
You can create a new node named "invites", append it before the "data" node, move the children of "data" to the new "invites" node, remove the "data" node, and then output the tree to get your result.
Example:
<?php
// Create a test document.
$dom = new DOMDocument( '1.0' );
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
// Load the xml file.
$dom->loadXML('<?xml version="1.0" encoding="ISO-8859-1"?'.'>
<library>
<data attr1="1" attr2="2">
<invite>
<username>jmansa</username>
<userid>1</userid>
</invite>
<update>1</update>
</data>
</library>', LIBXML_NOBLANKS );
$xpath = new DOMXPath($dom);
// Convert <data> to <invites>.
if ($dataNode = $xpath->query("/library/data")->item(0))
{
// Create the <invites> node.
$invitesNode = $dom->createElement('invites');
$dataAttrs = $dataNode->attributes;
foreach ($dataAttrs as $dataAttr)
{ $invitesNode->setAttributeNodeNS($dataAttr->cloneNode()); }
$dom->documentElement->appendChild($invitesNode);
// Move the <data> children over.
if ($childNodes = $xpath->query("/library/data/*"))
{
foreach ($childNodes as $childNode)
{ $invitesNode->appendChild($childNode); }
}
// Remove <data>.
$dataNode->parentNode->removeChild($dataNode);
}
// Test the result.
echo $dom->saveXML();
?>
My solution, with extended test case:
// Changes the name of element $element to $newName.
function renameElement($element, $newName) {
$newElement = $element->ownerDocument->createElement($newName);
$parentElement = $element->parentNode;
$parentElement->insertBefore($newElement, $element);
$childNodes = $element->childNodes;
while ($childNodes->length > 0) {
$newElement->appendChild($childNodes->item(0));
}
$attributes = $element->attributes;
while ($attributes->length > 0) {
$attribute = $attributes->item(0);
if (!is_null($attribute->namespaceURI)) {
$newElement->setAttributeNS('http://www.w3.org/2000/xmlns/',
'xmlns:'.$attribute->prefix,
$attribute->namespaceURI);
}
$newElement->setAttributeNode($attribute);
}
$parentElement->removeChild($element);
}
function prettyPrint($d) {
$d->formatOutput = true;
echo '<pre>'.htmlspecialchars($d->saveXML()).'</pre>';
}
$d = new DOMDocument( '1.0' );
$d->loadXML('<?xml version="1.0"?>
<library>
<data a:foo="1" x="bar" xmlns:a="http://example.com/a">
<invite>
<username>jmansa</username>
<userid>1</userid>
</invite>
<update>1</update>
</data>
</library>');
$xpath = new DOMXPath($d);
$elements = $xpath->query('/library/data');
if ($elements->length == 1) {
$element = $elements->item(0);
renameElement($element, 'invites');
}
prettyPrint($d);
By the way, I added this solution as a comment to the PHP documentation for DOMElement.
I have been looking around here and google and found various code, but none of them work. I'm assuming it's because none of them match my exact situation. I have an xml structure like this:
<employees>
<employee EmpID="">
<first_name></first_name>
<last_name></last_name>
<ssnum></ssnum>
<status></status>
<contact_info>
<office_phone></office_phone>
<email></email>
<cell_phone></cell_phone>
</contact_info>
<access_info level="user">
<username></username>
<password></password>
</access_info>
<department></department>
<date_started></date_started>
<years></years>
<position></position>
<salary></salary>
<e_increase></e_increase>
<e_raise></e_raise>
<photo></photo>
</employee>
</employees>
I have the EmpID of the employee I want to delete stored in a variable called $ID. I want to remove that employee completely. I have tried things like:
foreach ($doc->getElementsByTagName('employee') as $employee) {
if($employee->getAttribute('EmpID') === $ID) {
foreach ($employee as $node) {
$node->parentNode->removeChild($node);
}
}
}
and other loops trying to get things similarly, as in these posts: PHP XML remove element and all children by name and Remove all children from a XML Node PHP DOM but I have not been able to get this to work successfully.
Any help would be appreciated, thanks.
$ID = '';
$dom = new DOMDocument();
$dom->loadXML($xml_string);
$xpath = new DOMXpath($dom);
$nodes = $xpath->evaluate('/employees/employee[#EmpID = "' . $ID . '"]');
foreach ($nodes as $node) {
$node->parentNode->removeChild($node);
}
there you go:
$xml = '<employees>
<employee EmpID="1">
<first_name></first_name>
<last_name></last_name>
<ssnum></ssnum>
<status></status>
<contact_info>
<office_phone></office_phone>
<email></email>
<cell_phone></cell_phone>
</contact_info>
<access_info level="user">
<username></username>
<password></password>
</access_info>
<department></department>
<date_started></date_started>
<years></years>
<position></position>
<salary></salary>
<e_increase></e_increase>
<e_raise></e_raise>
<photo></photo>
</employee>
<employee EmpID="2">
<first_name></first_name>
<last_name></last_name>
<ssnum></ssnum>
<status></status>
<contact_info>
<office_phone></office_phone>
<email></email>
<cell_phone></cell_phone>
</contact_info>
<access_info level="user">
<username></username>
<password></password>
</access_info>
<department></department>
<date_started></date_started>
<years></years>
<position></position>
<salary></salary>
<e_increase></e_increase>
<e_raise></e_raise>
<photo></photo>
</employee>
</employees>';
$doc = new DOMDocument();
$doc->loadXML($xml);
$selector = new DOMXPath($doc);
$els = $selector->query('/employees//employee');
//or
//$els = $doc->getElementsByTagName('employee');
foreach($els as $el){
if($el->getAttribute('EmpID') == 1){
$el->parentNode->removeChild($el);
}
}
$xml = $doc->saveXML();
echo($xml);
Looking up my xml I have it slightly different .. Give this a try.
foreach ($doc->getElementsByTagName('employee') as $employee) {
if($employee->getAttribute('EmpID') === $ID) {
$employee->parentNode->removeChild($employee);
// $dom->save($xmlFile);
}
}
I have an XML file that looks something like this:
<product>
<modelNumber>Data</modelNumber>
<salePrice>Data</salePrice>
</product>
<product>
<modelNumber>Data</modelNumber>
<salePrice>Data</salePrice>
</product>
Is there a simple way to change the tag names , to something else such as model, price.
Essentially, I have a bunch of XML files containing similar data, but in different formats, so I'm looking for a simple way to parse the XML file, change certain tag names, and write a new XML file with the changed tag names.
There are two issues with Kris and dfsq code:
Only first child node will be copied - solved with temporary copy of $childNodes)
Children will get xmlns tag - solved by replacing node at the beginning - so it's connected to the document
A corrected renaming function is:
function renameTag( DOMElement $oldTag, $newTagName ) {
$document = $oldTag->ownerDocument;
$newTag = $document->createElement($newTagName);
$oldTag->parentNode->replaceChild($newTag, $oldTag);
foreach ($oldTag->attributes as $attribute) {
$newTag->setAttribute($attribute->name, $attribute->value);
}
foreach (iterator_to_array($oldTag->childNodes) as $child) {
$newTag->appendChild($oldTag->removeChild($child));
}
return $newTag;
}
Next function will do the trick:
/**
* #param $xml string Your XML
* #param $old string Name of the old tag
* #param $new string Name of the new tag
* #return string New XML
*/
function renameTags($xml, $old, $new)
{
$dom = new DOMDocument();
$dom->loadXML($xml);
$nodes = $dom->getElementsByTagName($old);
$toRemove = array();
foreach ($nodes as $node)
{
$newNode = $dom->createElement($new);
foreach ($node->attributes as $attribute)
{
$newNode->setAttribute($attribute->name, $attribute->value);
}
foreach ($node->childNodes as $child)
{
$newNode->appendChild($node->removeChild($child));
}
$node->parentNode->appendChild($newNode);
$toRemove[] = $node;
}
foreach ($toRemove as $node)
{
$node->parentNode->removeChild($node);
}
return $dom->saveXML();
}
// Load XML from file data.xml
$xml = file_get_contents('data.xml');
$xml = renameTags($xml, 'modelNumber', 'number');
$xml = renameTags($xml, 'salePrice', 'price');
echo '<pre>'; print_r(htmlspecialchars($xml)); echo '</pre>';
There is some sample code that works in my question over here, but there is no direct way of changing a tag name through DOMDocument/DOMElement, you can however copy elements with a new tagname as shown.
basically you have to:
function renameTag(DOMElement $oldTag, $newTagName)
{
$document = $oldTag->ownerDocument;
$newTag = $document->createElement($newTagName);
foreach($oldTag->attributes as $attribute)
{
$newTag->setAttribute($attribute->name, $attribute->value);
}
foreach($oldTag->childNodes as $child)
{
$newTag->appendChild($oldTag->removeChild($child));
}
$oldTag->parentNode->replaceChild($newTag, $oldTag);
return $newTag;
}
I am having the XML like this
<?xml version="1.0" encoding="utf-8"?>
<root>
<mynode catid="10" catname="Animals" label="Animals" catdesc="" parent_id="2">
<mynode catid="11" catname="Lions" label="Lions" catdesc="" parent_id="10">
<mynode catid="12" catname="lion" label="lion" catdesc="" parent_id="11"/>
<mynode catid="13" catname="lioness" label="lioness" catdesc="" parent_id="11"/>
</mynode>
</mynode>
</root>
From this I want to remove
<?xml version="1.0" encoding="utf-8"?>
<root>
and
</root>
So expected result is
<mynode catid="10" catname="Animals" label="Animals" catdesc="" parent_id="2">
<mynode catid="11" catname="Lions" label="Lions" catdesc="" parent_id="10">
<mynode catid="12" catname="lion" label="lion" catdesc="" parent_id="11"/>
<mynode catid="13" catname="lioness" label="lioness" catdesc="" parent_id="11"/>
</mynode>
</mynode>
How can I do this?
Edit 1:TO Phil
$dom = new DomDocument();
//$dom->preserveWhitespace = false;
$dom->load('treewithchild.xml');
function DOMinnerHTML($element)
{
$innerHTML = "";
$children = $element->childNodes;
foreach ($children as $child)
{
$tmp_dom = new DOMDocument();
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
$innerHTML.=trim($tmp_dom->saveXML());
echo $tmp_dom->saveXML();
}
return $innerHTML;
}
$dom->preserveWhiteSpace = false;
$domTable = $dom->getElementsByTagName("mynode");
foreach ($domTable as $tables)
{
//echo $tables;
DOMinnerHTML($tables);
}
As you want the inner markup of the <root> node, that is the element who's child nodes you'll want to iterate. You can access this element using the DOMDocument::documentElement property.
Try this (tested and working)
$doc = new DOMDocument;
$doc->load('treewithchild.xml');
$inner = '';
foreach ($doc->documentElement->childNodes as $child) {
$inner .= $doc->saveXML($child);
}
echo $inner;
I expect that the root element is returned also, you have to know that for each xml file an is added impliicitly, even if it exists in your file. so try to do this
$children = $element->childNodes->childNodes;
i think that would help you.