PHP Delete XML Node by Attribute - php

Have tried numerous examples on SO but none have worked.
Goal: Remove a node (unit) and it's children, by specific id=
filename.xml
<archive>
<unit id="0424670018">
<data>Blah blah blah #1</data>
<gdate>2018-05-28 00:42:46</gdate>
</unit>
<unit id="0450170018">
<data>Blah blah blah #2</data>
<gdate>2018-05-28 00:45:01</gdate>
</unit>
</archive>
Code used, not sure why it does not work when loaded:
$id = '0450170018';
$file = 'filename.xml';
$xml = simplexml_load_string($file);
foreach($xml->archive as $fileload){
if($fileload->unit['#id'] == $id){
$dom = dom_import_simplexml($fileload);
$dom->parentNode->removeChild($dom);
}
}

You can fetch the node using Xpath. It allows to fetch the matching node(s) directly.
$id = '0450170018';
$document = new DOMDocument();
$document->load('filename.xml');
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate("//unit[#id='$id']") as $unitNode) {
$unitNode->parentNode->removeChild($unitNode);
}
$document->save('filename.xml');

You need to use "DOMDocument" class
$doc = new DOMDocument;
$doc->load('filename.xml');
$xml = $doc->documentElement;
$id = '0450170018';
$domNodeList = $xml->getElementsByTagname('unit');
foreach ( $domNodeList as $domElement ) {
$valueID = $domElement->getAttribute('id');
if($valueID == $id)
{
$xml->removeChild($domElement);
}
}
$doc->save('filename.xml');

Related

Why does not display the attribute html via xpath php

Why does not display the attribute html via xpath php
<?php
$content = '<div class="keep-me">Keep this div</div><div class="remove-me" id="test">Remove this div</div>';
$badClasses = array('');
$dom = new DOMDocument;
libxml_use_internal_errors(true);
$dom->loadHTML($content);
libxml_clear_errors();
$xPath = new DOMXpath($dom);
foreach($badClasses as $badClass){
$domNodeList = $xPath->query('//div[#class="remove-me"]/#id');
$domElemsToRemove = ''; // container of deleted elements
foreach ( $domNodeList as $domElement ) {
$domElemsToRemove .= $dom->saveHTML($domElement); // concat them
$domElement->parentNode->removeChild($domElement); // then remove
}
}
$content = $dom->saveHTML();
echo htmlentities($domElemsToRemove);
?>
Works - //div[#class="remove-me"] or //div[#class="remove-me"]/text()
Not working - //div[#class="remove-me"]/#id
Maybe there is a way easier
The XPath //div[#class="remove-me"]/#id is correct, but you need to just loop over the returned elements and add the nodeValue to a list of matching ID's...
$xPath = new DOMXpath($dom);
$domNodeList = $xPath->query('//div[#class="remove-me"]/#id');
$ids = []; // container of deleted elements
foreach ( $domNodeList as $domElement ) {
$ids[] = $domElement->nodeValue;
}
print_r($ids);
If the aim is to fetch the ID of any element with class "remove-me" as is how I interpret the question then perhaps you can try like this - untested btw...
.... other code before
$xp=new DOMXpath( $dom );
$col= $xp->query( '*[#class="remove-me"]' );
if( $col->length > 0 ){
foreach($col as $node){
$id=$node->hasAttribute('id') ? $node->getAttribute('id') : 'banana';
echo $id;
}
}
however looking at the code in the question suggests that you wish to delete nodes - in which case build an array of nodes ( nodelist ) and iterate through it from the end to the front - ie: backwards...

Delete Node isn't working with Simple XML (PHP)

I want to delete a node if the title of an node is matching a filter (array). I use unset() and I already tried $node and $item but both arguments won't delete my node...
What is wrong in this code? - I do enter the if condition, because I see in if in my console!
$dom = new DOMDocument('1.0', 'utf-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load("shop1.xml");
$pathXML = "/products/product";
$titleArray = array("Test", "Battlefield 1");
$doc = simplexml_import_dom($dom);
$items = $doc->xpath($pathXML);
foreach ($items as $item) {
$node = dom_import_simplexml($item);
$title = $node->getElementsByTagName('title')->item(0)->textContent;
echo $title . "\n";
foreach ($titleArray as $titles) {
echo $titles . "\n";
if (mb_stripos($title, $titles) !== false) {
echo "in if\n\n";
unset($item);
}
}
}
$dom->saveXML();
$dom->save("shop1_2.xml");
XML File:
<products>
<product>
<title>Battlefield 1</title>
<url>https://www.google.de/</url>
<price>0.80</price>
</product>
<product>
<title>Battlefield 2</title>
<url>https://www.google.de/</url>
<price>180</price>
</product>
</products>
Greetings and Thank You!
All you're doing is unsetting a local variable. Instead you need to alter the DOM:
$dom = new DOMDocument('1.0', 'utf-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->load("shop1.xml");
$xpathQuery = "/products/product";
$titleArray = array("Test", "Battlefield 1");
$xp = new DomXpath($dom);
$items = $xp->query($xpathQuery);
foreach ($items as $item) {
$title = $item->getElementsByTagName('title')->item(0)->textContent;
echo "$title\n";
if (in_array($title, $titleArray)) {
$item->parentNode->removeChild($item);
}
}
$dom->saveXML();
$dom->save("shop1_2.xml");

Trying to use PHP DOM to replace node text without changing child nodes

I am trying to use the dom object to simplify the implementation of a glossary tooltip. What I need to do is to replace a text element in a paragraph, but NOT in an anchor tag that may be embedded in the paragraph.
$html = '<p>Replace this tag not this tag</p>';
$document = new DOMDocument();
$document->loadHTML($html);
$document->preserveWhiteSpace = false;
$document->validateOnParse = true;
$nodes = $document->getElementByTagName("p");
foreach ($nodes as $node) {
$node->nodeValue = str_replace("tag","element",$node->nodeValue);
}
echo $document->saveHTML();
I get:
'...<p>Replace this element not this element</p>...'
I want:
'...<p>Replace this element not this tag</p>...'
How do I implement this such that only the parent node text is changed and the child node (a tag) is not changed?
Try this:
$html = '<p>Replace this tag not this tag</p>';
$document = new DOMDocument();
$document->loadHTML($html);
$document->preserveWhiteSpace = false;
$document->validateOnParse = true;
$nodes = $document->getElementsByTagName("p");
foreach ($nodes as $node) {
while( $node->hasChildNodes() ) {
$node = $node->childNodes->item(0);
}
$node->nodeValue = str_replace("tag","element",$node->nodeValue);
}
echo $document->saveHTML();
Hope this helps.
UPDATE
To answer #paul's question in the comments below, you can create
$html = '<p>Replace this tag not this tag</p>';
$document = new DOMDocument();
$document->loadHTML($html);
$document->preserveWhiteSpace = false;
$document->validateOnParse = true;
$nodes = $document->getElementsByTagName("p");
//create the element which should replace the text in the original string
$elem = $document->createElement( 'dfn', 'tag' );
$attr = $document->createAttribute('title');
$attr->value = 'element';
$elem->appendChild( $attr );
foreach ($nodes as $node) {
while( $node->hasChildNodes() ) {
$node = $node->childNodes->item(0);
}
//dump the new string here, which replaces the source string
$node->nodeValue = str_replace("tag",$document->saveHTML($elem),$node->nodeValue);
}
echo $document->saveHTML();

Need to show child data on parent id

i'm struggling with Xpath, i have an xml list and i need to get the child data based on the parent id ...
My xml file :
<projecten>
<project id="1">
<titel>Shop 1</titel>
<siteurl>http://test.be</siteurl>
<screenshot>test.jpg</screenshot>
<omschrijving>comment 1</omschrijving>
</project>
<project id="2">
<titel>Shop 2</titel>
<siteurl>http://test2.be</siteurl>
<screenshot>test2.jpg</screenshot>
<omschrijving>comment</omschrijving>
</project>
</projecten>
the code i use to get for example the project 1 data (does not work):
$xmlDoc = new DOMDocument();
$xmlDoc->load(data.xml);
$xpath = new DOMXPath($xmlDoc);
$projectId = '1';
$query = '//projecten/project[#id='.$projectId.']';
$details = $xpath->query($query);
foreach( $details as $detail )
{
echo $detail->titel;
echo $detail->siteurl;
echo $detail->screenshot;
echo $detail->omschrijving;
}
But this does not show anything, if someone can point me out ... thanks
In addition to the solution already given you can also use:
foreach ($xpath->query(sprintf('/projecten/project[#id="%d"]', $id)) as $projectNode) {
echo
$projectNode->getElementsByTagName('titel')->item(0)->nodeValue,
$projectNode->getElementsByTagName('siteurl')->item(0)->nodeValue,
$projectNode->getElementsByTagName('screenshot')->item(0)->nodeValue,
$projectNode->getElementsByTagName('omschrijving')->item(0)->nodeValue;
}
or fetch the DOMText node values directly with Xpath
foreach ($xpath->query(sprintf('/projecten/project[#id="%d"]', $id)) as $projectNode) {
echo
$xpath->evaluate('string(titel)', $projectNode),
$xpath->evaluate('string(siteurl)', $projectNode),
$xpath->evaluate('string(screenshot)', $projectNode),
$xpath->evaluate('string(omschrijving)', $projectNode);
}
or import the node to SimpleXml
foreach ($xpath->query(sprintf('/projecten/project[#id="%d"]', $id)) as $projectNode) {
$detail = simplexml_import_dom($projectNode);
echo
$detail->titel,
$detail->siteurl,
$detail->screenshot,
$detail->omschrijving;
}
or even concatenate all the values directly in the XPath:
$xpath = new DOMXPath($dom);
echo $xpath->evaluate(
sprintf(
'concat(
/projecten/project[#id = %1$d]/titel,
/projecten/project[#id = %1$d]/siteurl,
/projecten/project[#id = %1$d]/screenshot,
/projecten/project[#id = %1$d]/omschrijving
', $id
)
);
Accessing the child nodes as you do:
echo $detail->title;
Is not valid, if you use DOM* functions. This would probably work if you were using SimpleXML.
For DOM* try this:
$dom = new DOMDocument;
$dom->loadXml('<projecten>
<project id="1">
<titel>Shop 1</titel>
<siteurl>http://test.be</siteurl>
<screenshot>test.jpg</screenshot>
<omschrijving>comment 1</omschrijving>
</project>
<project id="2">
<titel>Shop 2</titel>
<siteurl>http://test2.be</siteurl>
<screenshot>test2.jpg</screenshot>
<omschrijving>comment</omschrijving>
</project>
</projecten>
');
$id = 2;
$xpath = new DOMXPath($dom);
foreach ($xpath->query(sprintf('/projecten/project[#id="%s"]', $id)) as $projectNode) {
// repeat this for every needed node
$titleNode = $xpath->query('titel', $projectNode)->item(0);
if ($titleNode instanceof DOMElement) {
echo $titleNode->nodeValue;
}
// or us a loop for all child nodes
foreach ($projectNode->childNodes as $childNode) {
echo $childNode->nodeValue;
}
}

Create a complex structure with DOMDocument

I have a little problem with creating an complex XML structure with PHP and Dom Document.
I want the structure to be like this:
<page PathToWeb="www.mysite.com">
<Questions>
<Question id="my id" member="true">
<Question id="my id2" member="true">
<Question id="my id3" member="true">
</Questions>
</page>
and the code i have so far is
<?php
/*Create DOM*/
$xml = new DOMDocument;
$xml->load('myxml.xml'); /* wich is just just blank <?xml?\> <page> </page>*/
$xpath = new DOMXPath($xml);
/*Set the base path*/
$hrefs = $xpath->evaluate("/page");
/*Add Path to web to the root /page*/
$href = $hrefs->item(0);
$href->setAttribute("PathToWeb",$PathToWeb);
/*Complex XML Creation with Xpath*/
/*ELEMENT APPEND (create questions into /page)*/
$href = $hrefs->item(0);
$element = $xml->createElement('Questions');
$href->appendChild($element);
/*XPATH EVALUATE*/
$hrefs = $xpath->evaluate("/page/Questions");
/*ELEMENT 1 APPEND*/
$href = $hrefs->item(0);
$element = $xml->createElement('Question');
$href->appendChild($element);
$hrefs = $xpath->evaluate("/page/Questions/Question");
$href = $hrefs->item(0);
$href->setAttribute("id","my id");
/*ELEMENT 2 APPEND*/
$href = $hrefs->item(0);
$element = $xml->createElement('Question');
$href->appendChild($element);
$hrefs = $xpath->evaluate("/page/Questions/Question");
$href = $hrefs->item(0);
$href->setAttribute("id","my id");
/*ELEMENT 3 APPEND*/
$href = $hrefs->item(0);
$element = $xml->createElement('Question');
$href->appendChild($element);
$hrefs = $xpath->evaluate("/page/Questions/Question");
$href = $hrefs->item(0);
$href->setAttribute("id","my id");
$href = $hrefs->item(0);
$href->setAttribute("member","true");
$string2 = $xml->saveXML();
?>
What is creating is:
<page PathToWeb="www.mysite.com">
<Questions><Question id="my id" member="true"><Question/></Question></Questions>
</page>
Editing only the first Question ...
How can i solve this?
Your code looks somewhat more complicated than it needs to be.
Because appendChild returns the appended node and setAttribute returns the set Attribute Node, you could also create the entire tree without any temp variables and also without any Xpath simply by chaining method calls and traversing the DOM tree:
$dom = new DOMDocument('1.0', 'utf-8');
$dom->appendChild($dom->createElement('page'))
->setAttribute('PathToWeb', 'www.mysite.com')
->parentNode
->appendChild($dom->createElement('Questions'))
->appendChild($dom->createElement('Question'))
->setAttribute('id', 'my_id')
->parentNode
->setAttribute('member', 'true')
->parentNode
->parentNode
->appendChild($dom->createElement('Question'))
->setAttribute('id', 'my_id2')
->parentNode
->setAttribute('member', 'true')
->parentNode
->parentNode
->appendChild($dom->createElement('Question'))
->setAttribute('id', 'my_id3')
->parentNode
->setAttribute('member', 'true');
$dom->formatOutput = true;
echo $dom->saveXml();
Understanding that DOM is a tree hierarchy of DOMNodes is essential when wanting to work with DOM. See DOMDocument in php for some explanation on that.
$xml = new DOMDocument('1.0','UTF-8');
$root = $xml->createElement('page');
$root->setAttribute("PathToWeb",$PathToWeb);
$wrap = $xml->createElement('Questions');
$root->appendChild($wrap);
for ($i = 1;$i<4;$i++)
{
$element = $xml->createElement('question');
$element->setAttribute("id","my id" . $i);
$element->setAttribute("member","true");
$wrap->appendChild($element);
}
$xml->appendChild($root);
$xml->formatOutput = true;
$xml->save('myxml.xml');// Thanks to Gordon
<?php
$xml = new DOMDocument;
$xml->load('myxml.xml'); /* wich is just just blank <?xml?> <page> </page>*/
$xpath = new DOMXPath($xml);
/*Set the base path*/
$base = $xpath->evaluate("/page")->item(0);
$base->setAttrubute("PathToWeb", $PathToWeb);
$questions = $xml->createElement('Questions');
$base->appendChild($questions);
for($i = 0; $i < 2; $i++) {
$question= $xml->createElement('Question');
$questions->appendChild($question);
$question->setAttribute("id","my id");
$question->setAttribute("member", "true");
}
$string2 = $xml->saveXML();
?>
This might help you to solve your problem and make your code much more compact and easier to deal with:
appendChild PHP Manual returns the new node. You can then directly work with it. No need to use xpath after appending the child to get access to it.
And if you add/set the attributes you want to set before adding the element node to the document, you most often don't even need to:
/*ELEMENT APPEND (create questions into /page)*/
$href = $hrefs->item(0);
$element = $xml->createElement('Questions');
$questions = $href->appendChild($element);
# ^^^
/*ELEMENT 1 APPEND*/
$element = $xml->createElement('Question');
$element->setAttribute("id","my id"); # prepare before adding
$questions->appendChild($element);
...
It's quite the same for the root element of your document (<page>). You do not need to use xpath to access it and manipulate it. It's documentElement PHP Manual:
/*Create DOM*/
$xml = new DOMDocument;
$xml->load('myxml.xml'); /* wich is just just blank <?xml?> <page> </page>*/
/*Add Path to web to the root /page*/
$href = $xml->documentElement;
$href->setAttribute("PathToWeb",$PathToWeb);

Categories