7

I have an XML:

<root>
   <level name="main">
      <level name="sub_1">
         <content id="abc123" />
      </level>
   </level>
</root>

I would like to search for the node with id that is abc123 and delete the <content> and its parent <level>

So the end result would be:

<root>
  <level name="main">
  </level> 
</root>

I have tried this in PHP without result, what am I doing wrong?

 $doc = new DOMDocument;
 $doc->loadxml($xml_from_file); 
 $xpath = new DOMXPath($doc);
 $node_list = $xpath->query("content[@id='abc123']/parent::*"); 
 $node = $node_list->item(0); 
 $doc->removeChild($node);
2
  • DOMDocument::loadXML takes XML. Use DOMDocument::load for filename. Commented Sep 26, 2015 at 0:42
  • Thanks. I am actually loading it from a string within loadXML Commented Sep 26, 2015 at 0:55

3 Answers 3

11

Here are two issues with your source.

The expression does only match child nodes. You need to start it with // to match any node: //content[@id='abc123']/parent::*.

The found node is not a child of the document so, you need to remove it from its own parent: $node->parentNode->removeChild($node);.

I suggest using a foreach to avoid problems if the node doesn't exists.

$document = new DOMDocument;
$document->loadxml($xmlString); 
$xpath = new DOMXPath($document);

foreach ($xpath->evaluate("//content[@id='abc123']/parent::*") as $node) {
  $node->parentNode->removeChild($node);
}

echo $document->saveXml();

Edit: PHP 8 got several new DOM standard methods. One of them is remove():

$document = new DOMDocument;
$document->loadxml($xmlString); 
$xpath = new DOMXPath($document);

foreach ($xpath->evaluate("//content[@id='abc123']/parent::*") as $node) {
  // new shortcut method
  $node->remove();
}

echo $document->saveXml();
Sign up to request clarification or add additional context in comments.

1 Comment

How to convert this into a well-formed XML?
4
<?php

$xml_from_file = '<root>
   <level name="main">
      <level name="sub_1">
         <content id="abc123" />
      </level>
   </level>
</root>';

 $doc = new DOMDocument;
 $doc->loadxml($xml_from_file); 
 $xpath_selector = new DOMXPath($doc);
//Here you forget at the begin the //
 $node_list = $xpath_selector->query("//content[@id='abc123']/parent::*"); 
//here you get the reference to the parent of content
 $node = $node_list->item(0); 
//but for remove the child you need to go to the parent node
 $node->parentNode->removeChild($node);
 echo $doc->saveXML();

?> 

Output:

<root>
  <level name="main">
  </level> 
</root>

Comments

-2

This is kind of a hack but I got it to work with your example. One problem is likely with your xpath query--note the // at the beginning.

$xml_string = '<root>
    <level name="main">
        <level name="sub_1">
            <content id="abc123" />
        </level>
    </level>
</root>';

// using SimpleXMLElement instead of DOMDocument
$xml = new SimpleXMLElement($xml_string);

// standardize the string version of the xml so str_replace works
$xml_string = $xml->asXML();

// search for the target; note the // at the beginning of the query
$target = $xml->xpath("//content[@id='abc123']/parent::*");

// use simple string replacement to remove the node
echo str_replace($target[0]->asXML(), '', $xml_string);

Not very elegant, but it seemed to take care of your problem.

1 Comment

When dealing with XML it is generally a bad idea to try to manipulate it with string functions, as the structure is intricate and subtle (think of entities, cdata sections, comments, dtd). It is almost always better to use XML classes, like available for DOM, XPath, XQuery or XSLT.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.