2

I want to completely remove the size="id" attribute from every <door> element.

<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249" size="30"/>
<door id="1041" entry="6523" size="3094"/>
-- and 1000 more....
</doors>

The PHP code:

$xml = new SimpleXMLElement('http://mysite/doors.xml', NULL, TRUE);
$ids_to_delete = array( 1, 1506 );
foreach ($ids_to_delete as $id) {
    $result = $xml->xpath( "//door[@size='$id']" );
    foreach ( $result as $node ) {
        $dom = dom_import_simplexml($node);
        $dom->parentNode->removeChild($dom);
    }
}

$xml->saveXml();

I get no errors but it does not delete the size attribute. Why?

hakre
  • 193,403
  • 52
  • 435
  • 836
Smugglaren
  • 99
  • 1
  • 7

2 Answers2

3

I get no errors but it does not delete the size attribute. Why?

There are mulitple reasons why it does not delete the size attribute. The one that popped first into my mind was that attributes are no child nodes. Using a method to remove a child does just not fit to remove an attribute.

Each element node has an associated set of attribute nodes; the element is the parent of each of these attribute nodes; however, an attribute node is not a child of its parent element.

From: Attribute Nodes - XML Path Language (XPath), bold by me.

However, you don't see an error here, because the $result you have is an empty array. You just don't select any nodes to remove - neither elements nor attributes - with your xpath. That is because there is no such element you look for:

//door[@size='1']

You're searching for the id in the size attribute: No match.

These are the reasons why you get no errors and it does not delete any size attribute: 1.) you don't delete attributes here, 2.) you don't query any elements to delete attributes from.

How to delete attributes in SimpleXML queried by Xpath?

You can remove the attribute nodes by selecting them with an Xpath query and then unset the SimpleXMLElement self-reference:

// all size attributes of all doors
$result = $xml->xpath("//door/@size");
foreach ($result as $node) {
    unset($node[0]);
}

In this example, all attribute nodes are queried by the Xpath expressions that are size attributes of door elements (which is what you ask for in your question) and then those are removed from the XML.

//door/@size

(see Abbreviated Syntax)

Now here the full example:

<?php
/**
 * @link https://eval.in/215817
 */

$buffer = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249" size="30"/>
<door id="1041" entry="6523" size="3094"/>
-- and 1000 more....
</doors>
XML;

$xml = new SimpleXMLElement($buffer);

// all size attributes of all doors
$result = $xml->xpath("//door/@size");
foreach ($result as $node) {
    unset($node[0]);
}

$xml->saveXml("php://output");

Output (Online Demo):

<?xml version="1.0" encoding="UTF-8"?>
<doors>
<door id="1" entry="3249"/>
<door id="1041" entry="6523"/>
-- and 1000 more....
</doors>
Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
1

You can do your whole query in DOMDocument using DOMXPath, rather than switching between SimpleXML and DOM:

$dom = new DOMDocument;
$dom->load('my_xml_file.xml');

# initialise an XPath object to act on the $dom object
$xp = new DOMXPath( $dom );

# run the query
foreach ($xp->query( "//door[@size]" ) as $door) {
    # remove the attribute
    $door->removeAttribute('size');
}

print $dom->saveXML();

Output for the input you supplied:

<?xml version="1.0" encoding="UTF-8"?>
<doors>
    <door id="1" entry="3249"/>
    <door id="1041" entry="6523"/>
</doors>

If you do want only to remove the size attribute for the IDs in your list, you should use the code:

foreach ($ids_to_delete as $id) {
    # searches for elements with a matching ID and a size attribute
    foreach ($xp->query("//door[@id='$id' and @size]") as $door) {
        $door->removeAttribute('size');
    }
}

Your code wasn't working for several reasons:

  • it looks like your XPath was wrong, since your array is called $ids_to_delete and your XPATH is looking for door elements with the size attribute equal to the value from $ids_to_delete;

  • you're converting the nodes to DOMDocument objects ($dom = dom_import_simplexml($node);) to do the deletion, but $xml->saveXml();, which I presume you printed somehow, is a SimpleXML object;

  • you need to remove the element attribute; removeChild removes the whole element.

i alarmed alien
  • 9,412
  • 3
  • 27
  • 40