Deleting elements from xml file with foreach in php - php

Hi i have a code like this:
$doc = new DOMDocument();
$doc->Load('courses.xml');
foreach ($doc->getElementsByTagName('courses') as $tagcourses)
{
foreach ( $tagcourses ->getElementsByTagName('course') as $tagcourse)
{
if(($tagcourse->getAttribute('instructorId')) == $iid){
$tagcourses->removeChild($tagcourse);
}
}
}
$doc->Save('courses.xml');
And i have a xml file:
<courses>
<course courseId="1" instructorId="1">
<course_code>456</course_code>
<course_name>bil</course_name>
</course>
<course courseId="2" instructorId="2">
<course_code>234</course_code>
<course_name>math</course_name>
</course>
<course courseId="3" instructorId="2">
<course_code>341</course_code>
<course_name>cs</course_name>
</course>
<course courseId="4" instructorId="2">
<course_code>244</course_code>
<course_name>phyc</course_name>
</course>
</courses>
In this code i tried to remove elements which has instructor id that specified with iid.The problem is all courses that has this instructor id must be removed.But in my program just the first course that has this iid is being removed.Can you suggest a solution?Thanks.

The getElementsByTagName() is returning a live nodelist. If you remove an element from it in a loop, the loop is then iterating over a different set of elements than it started with, and the results are unpredictable. Instead, store the nodes you want to remove on an array, then iterate over that and remove them.
$doc = new DOMDocument();
$doc->Load('courses.xml');
$to_remove = array();
foreach ($doc->getElementsByTagName('courses') as $tagcourses)
{
foreach ( $tagcourses ->getElementsByTagName('course') as $tagcourse)
{
if(($tagcourse->getAttribute('instructorId')) == $iid){
$to_remove[] = $tagcourse;
}
}
}
// Remove the nodes stored in your array
// by removing it from its parent
foreach ($to_remove as $node)
{
$node->parentNode->removeChild($node);
}
$doc->Save('courses.xml');

Related

How to get nearest child node and not the nested ones using DOM or XPATH in PHP

Having some XML product feed like this:
<SHOP>
<SHOPITEM id="2927" import-code="PREMIER">
<NAME>productname</NAME>
<DESCRIPTION>Blah, blah, blah ...</DESCRIPTION>
<RELATED_PRODUCTS>
<CODE>PXP-01-01</CODE>
<CODE>PXP-01-02</CODE>
<CODE>PXP-01-03</CODE>
</RELATED_PRODUCTS>
<FLAGS>
<FLAG>
<CODE>news</CODE>
<ACTIVE>1</ACTIVE>
</FLAG>
<FLAG>
<CODE>action</CODE>
<ACTIVE>0</ACTIVE>
</FLAG>
</FLAGS>
<CODE>PXS-01-MNCRFT</CODE>
<EAN>0702811692053</EAN>
<WEIGHT>0.5</WEIGHT>
<PRICE>123</PRICE>
<VAT>21</VAT>
</SHOPITEM>
</SHOP>
and need to get product code in a PHP loop. Seems easy, but using $item->getElementsByTagName("CODE") is impossible because I don´t know the order id of the right one CODE node. I have tried using XPATH, but I´m totally lost and do not know how to construct the query to get only the CODE value in SHOP > SHOPITEM > CODE and all the others in SHOP > SHOPITEM > RELATED_PRODUCTS > CODE or in FLAG > CODE or in ALTERNATIVE_PRODUCTS > CODE ... How to exclude these or better ask exactly for the right one?
Using xpath, try it this way
$dom = new DOMDocument();
$dom->loadXML($xml_string);
$xpath = new DOMXPath($dom);
$flags = $xpath->query('//SHOPITEM//FLAGS');
foreach ($flags as $flag)
{
foreach ($xpath->query('.//FLAG/CODE',$flag) as $target) {
echo $target->nodeValue ."\n";}
};
Output:
news
action
Next:
$codes = $xpath->query('//SHOPITEM/CODE');
foreach ($codes as $code)
{
foreach ($codes as $code) {
echo $code->nodeValue ."\n";}
};
Output:
PXS-01-MNCRFT
Finally:
$rps = $xpath->query('//SHOPITEM/RELATED_PRODUCTS');
foreach ($rps as $rp)
{
foreach ($xpath->query('.//CODE',$rp) as $target) {
echo $target->nodeValue ."\n";}
};
Output:
PXP-01-01
PXP-01-02
PXP-01-03

Remove child from XML with PHP DOM

I want to remove first video element (video src=time.mp4) from this xml (filename.xml) and save the xml into filename4.smil :
<?xml version="1.0" encoding="utf-8"?>
<smil>
<stream name="mysq"/>
<playlist name="Default" playOnStream="mysq" repeat="true" scheduled="2010-01-01 01:01:00">
<video src="time.mp4" start="0" length="-1"> </video>
<video src="sample.mp4" start="0" length="-1"> </video>
</playlist>
</smil>
i am using this code, but is not working:
<?php
$doc = new DOMDocument;
$doc->load("filename.xml");
$thedocument = $doc->documentElement;
//this gives you a list of the messages
$list0 = $thedocument->getElementsByTagName('playlist');
$list = $list0->item(0);
$nodeToRemove = null;
foreach ($list as $domElement){
$videos = $domElement->getElementsByTagName( 'video' );
$video = $videos->item(0);
$attrValue = $video->getAttribute('src');
if ($attrValue == 'time.mp4') {
$nodeToRemove = $videos; //will only remember last one- but this is just an example :)
}
}
//Now remove it.
if ($nodeToRemove != null)
$thedocument->removeChild($nodeToRemove);
$doc->save('filename4.smil');
?>
Assuming that there is only 1 playlist item and you want to remove the first video element from that, here are 2 methods.
This one uses getElementsByTagName() as you are in your code, but simple picks the first item from each list and then removes the item (you have to use parentNode to remove the child node).
$playlist = $doc->getElementsByTagName('playlist')->item(0);
$video = $playlist->getElementsByTagName( 'video' )->item(0);
$video->parentNode->removeChild($video);
This version uses XPath, which is more flexible, it looks for the playlist elements with a video element somewhere inside. Again, just taking the first one and removing it...
$xp = new DOMXPath($doc);
$video = $xp->query('//playlist//video')->item(0);
$video->parentNode->removeChild($video);
The problem with
$thedocument->removeChild($nodeToRemove);
is that you are trying to remove a child element from the base document. As this node is nested in the hierarchy, it won't be able to remove it, you need to remove it from it's direct parent.
Using Xpath expressions you can fetch video nodes with a specific src attribute, iterate them and remove them.
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$expression = '/smil/playlist/video[#src="time.mp4"]';
foreach ($xpath->evaluate($expression) as $video) {
$video->parentNode->removeChild($video);
}
var_dump($document->saveXML());
It is possible to fetch nodes by position as well: /smil/playlist/video[1].

PHP get nodes value with nested nodes XML

I have a xml file:
<Epo>
<Doc upd="add">
<Fld name="IC"><Prg><Sen>A01B1/00 <Cmt>(1585, 779)</Cmt></Sen></Prg></Fld>
<Fld name="CC"><Prg><Sen>A01B1/00 <Cmt>(420, 54%)</Cmt>;</Sen><Sen>B25G1/102 <Cmt>(60, 8%)</Cmt>;</Sen><Sen>A01B1/02 <Cmt>(47, 6%)</Cmt></Sen></Prg></Fld></Doc>
<Doc upd="add">
<Fld name="IC"><Prg><Sen>A01B1/02 <Cmt>(3847, 1718)</Cmt></Sen></Prg></Fld>
<Fld name="CC"><Prg><Sen>A01B1/02 <Cmt>(708, 41%)</Cmt>;</Sen><Sen>A01B1/022 <Cmt>(347, 20%)</Cmt>;</Sen><Sen>A01B1/028 <Cmt>(224, 13%)</Cmt></Sen></Prg></Fld></Doc>
</Epo>
I want to get node value, for example : A01B1/00 (1585, 779) - A01B1/00 (420, 54%); B25G1/102 (60, 8%); A01B1/02 (47, 6%)
Then formating them into table's column. how can I do that?
My code:
<?php
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->load('test.xml'); //IPCCPC-epoxif-201905
$xpath = new DOMXPath($doc);
$titles = $xpath->query('//Doc/Fld');
foreach ($titles as $title){
echo $title->nodeValue ."<hr>";
}
?>
I cannot separate evrey node. Please help me.
I've tried to split it down to fetch all the various levels of content, but I think the main problem was just getting the current node text without the child elements text content. Using DOMDocument, the nodeValue is the same as textContent which (from the manual)...
textContent The text content of this node and its descendants.
Using DOMDocument isn't the easiest to use when just accessing a relatively simple hierarchy and requires you to continually make calls (in this case) to getElementsByTagName() to fetch the enclosed elements, the following source shows how you can get at each part of the document using this method...
foreach ( $doc->getElementsByTagName("Doc") as $item ) {
echo "upd=".$item->getAttribute("upd").PHP_EOL;
foreach ( $item->getElementsByTagName("Fld") as $fld ) {
echo "name=".$fld->getAttribute("name").PHP_EOL;
foreach ( $fld->getElementsByTagName("Sen") as $sen ) {
echo trim($sen->firstChild->nodeValue) ." cmt = ".
$sen->getElementsByTagName("Cmt")[0]->firstChild->nodeValue.PHP_EOL;
}
}
}
Using the SimpleXML API can however give a simpler solution. Each level of the hierarchy is accessed using object notation, and so ->Doc is used to access the Doc elements off the root node, and the foreach() loops just work off that. You can also see that using just the element name ($sen->Cmt) will give you just the text content of that node and not the descendants (although you have to cast it to a string to get it's value from the object) ...
$doc = simplexml_load_file("test.xml");
foreach ( $doc->Doc as $docElemnt ) {
echo "upd=".(string)$docElemnt['upd'].PHP_EOL;
foreach ( $docElemnt->Fld as $fld ) {
echo "name=".(string)$fld['name'].PHP_EOL;
foreach ( $fld->Prg->Sen as $sen ) {
echo trim((string)$sen)."=".trim((string)$sen->Cmt).PHP_EOL;
}
}
}

How to retrieve all the data inside all the elements in xml?

I am having an issue getting xml data in php
My xml is fairly complicated and there are several nested children in the tag.
xml
?xml version="1.0" encoding="UTF-8"?>
<book id="5">
<title id="76">test title</title>
<figure id="77"></figure>
<ch id="id78">
<aa id="80"><emph>content1</emph></aa>
<ob id="id_84" page-num="697" extra-info="4"><emph type="bold">opportunity.</emph></ob>
<ob id="id_85" page-num="697" extra-info="5"><emph type="bold">test data.</emph></ob>
<para id="id_86" page-num="697">2008.</para>
<body>
..more elements
<content>more contents..
</content>
</body>
</ch>
MY codes
//I need to load many different xml files.
$xml_file = simplexml_load_file($filename);
foreach ($xml_file->children() as $child){
echo $child->getName().':'. $child."<br>";
}
The codes above would only display
book, title, figure, ch but not the elements inside the ch tag. How do I display all the element inside each tag? Any tips? Thanks a lot!
Two things:
You need to match your <ob> </objective> tags.
Your foreach needs to be recursive. You should check if each item in your foreach has a child, then recursively foreach over that elements. I'd recommend using a separate function for this that you recursively call.
Example:
$xml_file = simplexml_load_file($filename);
parseXML($xml_file->children());
function parseXML($xml_children)
{
foreach ($xml_children as $child){
echo $child->getName().':'. $child."<br>";
if ($child->count() > 0)
{
parseXML($child->children());
}
}
}
You need to do resursive call
parseAllXml($xml_file);
function parseAllXml($xmlcontent)
{
foreach($xmlcontent->children() as $child)
{
echo $child->getName().':'. $child."<br>";
$is_further_child = ( count($child->children()) >0 )?true:false;
if( $is_further_child )
{
parseAllXml($child);
}
}
}

count number of items in xml with php

I have to parse xml files that look like this : http://goo.gl/QQirq
How can I count number of items/records in this xml- by 'item' I mean a 'productItem' element, ie there are 5 items in the example xml. I don't specify the tag name 'productItem' when parsing the xml, so I can't count occurrences of 'productItem'. Here is the code I have:
<?php
$doc = new DOMDocument();
$doc->load("test.xml");
$xpath = new DOMXpath( $doc );
$nodes = $xpath->query( '//*| //#*' );
$nodeNames = array();
foreach( $nodes as $node )
{
$nodeNames = $node->nodeName;
$name=$node->nodeName;
$value=$node->nodeValue;
echo ''.$name.':'.$value.'<br>';
}
?>
How can I count number of items and display them one by one, like this ideally : http://goo.gl/O1FI8 ?
Why don't you use DOMDocument::getElementsByTagName?
//get the number of product items
echo $doc->getElementsByTagName('productitem')->length;
//traverse the collection of productitem
foreach($doc->getElementsByTagName('productitem') as $element){
//$element is a DOMElement
$nodeNames = $element->nodeName;
$name=$element->nodeName;
$value=$element->nodeValue;
echo ''.$name.':'.$value.'<br>';
}
As you want to traverse your document, use XPath is just greedy. Moreover you will instantiate each node of the document even if you only want one or two.
You can use hasChildNodes methode and childNodes attribute to traverse your document
function searchInNode(DOMNode $node){
if(isGoodNode($node)){//if your node is good according to your database
mapTheNode($node);
}
if($node->hasChildNodes()){
foreach($node->childNodes as $nodes){
searchInNode($nodes);
}
}
}
searchInNode($domdocument);

Categories