appendChild using DOMDocument/PHP/XML - php

I am trying to update my XML file based on an HTML form processed by PHP but the new XML snippet I am trying to append to specific areas of my current XML just keeps getting added to the end of my document.
$specific_node = "0"; //this is normally set by a select input from the form.
$doc = new DOMDocument();
$doc->load( 'rfp_files.xml' );
$doc->formatOutput = true;
//below is where my issue is having problems the variable '$specific_node' can be one of three options 0,1,2 and what I am trying to do is find the child of content_sets. So the first second or third child elemts and that is where I will add my new bit of XML
$r = $doc->getElementsByTagname('content_sets')->item($specific_node);
//This is where I build out my new XML to append
$fileName = $doc->createElement("file_name");
$fileName->appendChild(
$doc->createTextNode( $Document_Array["url"] )
);
$b->appendChild( $fileName );
//this is were I add the new XML to the child node mention earlier in the script.
$r->appendChild( $b );
XML Example:
<?xml version="1.0" encoding="UTF-8"?>
<content_sets>
<doc_types>
<article>
<doc_name>Additional</doc_name>
<file_name>Additional.docx</file_name>
<doc_description>Test Word document. Please remove when live.</doc_description>
<doc_tags>word document,test,rfp template,template,rfp</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</doc_types>
<video_types>
<article>
<doc_name>Test Video</doc_name>
<file_name>test_video.avi</file_name>
<doc_description>Test video. Please remove when live.</doc_description>
<doc_tags>test video,video, avi,xvid,svid avi</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</video_types>
<image_types>
<article>
<doc_name>Test Image</doc_name>
<file_name>logo.png</file_name>
<doc_description>Logo transparent background. Please remove when live.</doc_description>
<doc_tags>png,logo,logo png,no background,graphic,hi res</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</image_types>
</content_sets>

This is getting the root element:
$specific_node = "0";
$r = $doc->getElementsByTagname('content_sets')->item($specific_node);
So you are appending a child onto the root which is why you always see it added near the end of the document. You need to get the children of the root element like this:
$children = $doc->documentElement->childNodes;
This can return several types of node, but you are only interested in 'element' type nodes. It's not very elegant, but the only way I've found to get a child element by position is looping like this...
$j = 0;
foreach ($doc->documentElement->childNodes as $r)
if ($r->nodeType === XML_ELEMENT_NODE && $j++ == $specific_node)
break;
if ($j <= $specific_node)
// handle situation where $specific_node is more than number of elements
You could use getElementsByTagName() if you can pass the name of the node required instead of the ordinal position, or change the XML so that the child elements all have the same name and use an attribute to differentiate them.

Related

use setAttribute and createElement | PHP XML

This code:
$product = $dom->createElement('o');
$product->setAttribute('id', $productSku);
$product->setAttribute('url', $productWebsite.$productUrl);
$product->setAttribute('price', substr_replace($productPrice,'.',-2,0).$productCurrency);
$product->setAttribute('avail', $productAvail);
$product->setAttribute('weight', $productWeight);
$product->setAttribute('stock', $productStock);
$product->setAttribute('basket', $productBasket);
Generate for me:
<offers><o id="12264" url="http" price="209.22" avail="1" weight="1" stock="183" basket="1">
Now I add:
//tag
$title = $dom->createElement('name', clean($productTitle));
$product->appendChild($title);
and under this xml line I have
<name>some name</name>
everything is okay! But I need add next tags under this all lines to get result:
<imgs>
<main url="https://firstimage1"/>
</imgs>
so now I try:
$product = $dom->createElement('imgs');
$product->setAttribute('main', $productImagePath.$productImage);
What is issue?
This code replace first tag o with imgs and tag main add to first tag with <o some like this by end of line:
<offers><imgs id="12264" url="http" price="209.22" avail="1" weight="1" stock="183" basket="1" main="https:urlphoto">
This is wrong. I need to get full effect:
<offers><o id="12264" url="http" price="209.22" avail="1" weight="1" stock="183" basket="1">
<name>some name</name>
<imgs>
<main url="https://firstimage1"/>
</imgs>
</o>
</offers>
full code:
$product = $dom->createElement('o');
$product->setAttribute('id', $productSku);
$product->setAttribute('url', $productWebsite.$productUrl);
$product->setAttribute('price', substr_replace($productPrice,'.',-2,0).$productCurrency);
$product->setAttribute('avail', $productAvail);
$product->setAttribute('weight', $productWeight);
$product->setAttribute('stock', $productStock);
$product->setAttribute('basket', $productBasket);
//tag
$title = $dom->createElement('name', clean($productTitle));
$product->appendChild($title);
// next element (issue here) this replace first tags
$product = $dom->createElement('imgs');
$product->setAttribute('main', $productImagePath.$productImage);
$root->appendChild($product);
}
$dom->appendChild($root);
$dom->save($filePath);
}
#update:
I try change:
// next element
// next element
$product2 = $dom->createElement('imgs');
$product3 = $dom->createElement('main');
$product3->setAttribute('url', $productImagePath.$productImage);
and by end of file:
$root->appendChild($product);
$root->appendChild($product2);
$root->appendChild($product3);
But now I have:
<offers>
<o id="12264" url="http" price="209.22" avail="1" weight="1" stock="183" basket="1">
<name>4711 Eau De Cologne 800ml</name>
</o>
<imgs/>
#update 2:
#Thank you for answear. This working correct! Can I ask you about last thing.
I need add again:
<attrs>
<a name="Producent">
<![CDATA[Avery]]>
</a>
<a name="EAN">
<![CDATA[9084692100225]]>
</a>
<a name="Kod producenta">
<![CDATA[AVSG710022]]>
</a>
</attrs>
For this I duplicate function:
// create+append "attrs" to "o"
$offer->appendChild(
$attrs = $document->createElement('attrs')
);
// create+append "a" to "attrs"
$attrs->appendChild(
$attr = $document->createElement('a')
);
// add properties to the "attrs" element
$attr->setAttribute('producent', $productBrand);
$attr->setAttribute('ean', $productEan);
$attr->setAttribute('kod_producent', $productSku);
But currently I've:
<attrs>
<a producent="4711" ean="4011700740031" kod_producent="12264"/>
</attrs>
You named the imgs element node variable $product and overwrite the o element - its parent node. Make sure to use semantic and unique names for your variables.
Use methods on the DOM document to create the nodes and append them to their parent node. appendChild() returns the appended node, so you can use it to chain calls and only need to store the node into a variable for extended modification.
Do not use the second argument of createElement(). It does not fully escape special characters. Set the textContent property or append a text node.
You can append the element node directly after creating it - here is no need to wait. You can modify the appended node at any time - this are the same methods you use to manipulate existing nodes.
Here is an example that creates your target XML structure (with reduced attributes).
$document = new DOMDocument('1.0', 'UTF-8');
// create+append the document element "offers"
$document->appendChild(
$offers = $document->createElement('offers')
);
// create+append the "o" element
$offers->appendChild(
$offer = $document->createElement('o')
);
// add properties to the "o" element
$offer->setAttribute('id', 'SKU123');
$offer->setAttribute('url', 'https://example.com#product');
// create+append the "name" to "o"
$offer
->appendChild($document->createElement('name'))
// chain call and set the text content
->textContent = 'Example product name';
// create+append "imgs" to "o"
$offer->appendChild(
$images = $document->createElement('imgs')
);
// create+append "main" to "imgs"
$images->appendChild(
$image = $document->createElement('main')
);
// add properties to the "main" element
$image->setAttribute('url', 'https://example.com#product-image');
$document->formatOutput = true;
echo $document->saveXML();
Output:
<?xml version="1.0" encoding="UTF-8"?>
<offers>
<o id="SKU123" url="https://example.com#product">
<name>Example product name</name>
<imgs>
<main url="https://example.com#product-image"/>
</imgs>
</o>
</offers>

Removing the child node of an XML file using DOM and PHP

I'm trying to delete a child node within a XML document using DOM and PHP but I can't quite figure out how to do it. I do not have access to simpleXML.
XML Layout:
<list>
<as>
<a>
<a1>delete</a1>
</a>
<a>
<a1>keep</a1>
</a>
</as>
<list>
PHP Code:
$xml = "file.xml";
$dom = DOMDocument::load($xml);
$list = $dom->getElementsByTagName('as')->item(0);
//Cycle through <as> elements (there are multiple in the full file)
foreach($list->childNodes as $child) {
$subChild = substr($child->tagName, 0, -1);
$a = $dom->getElementsByTagName($subChild);
//Cycle through <a> elements
foreach($a as $node)
{
//Get status for status check
$check= $node->getElementsByTagName("a1")->item(0)->nodeValue;
if(strcmp($check,'delete')==0)
{
//code to delete here (I wish to delete the <a> that this triggers
}
}
}
http://www.php.net/manual/en/class.domnode.php
http://www.php.net/manual/en/domnode.removechild.php
You need the parent of a node to remove it, and you've got it as a property of the node that you want to remove, so no biggie. The result would be:
$node->parentNode->removeChild($node);

Using item() to insert data into XML document

I am trying to insert data into my XML document into a specific node of content_set I thought I had to use item() but every time I submit my form the data gets put in at the end of my document but before the closing content_sets
PHP:
//This is where I thought I would choose what node the data is put into based on the value of the select in my html, with 0 being the doc_types and 1 being video_types
$file_type = $_POST['file_type'];
$doc = new DOMDocument();
$doc->load( 'myfile_files.xml' );
$doc->formatOutput = true;
$r = $doc->getElementsByTagname('content_sets')->item($file_type);
$b = $doc->createElement("article");
$titleName = $doc->createElement("doc_name");
$titleName->appendChild(
$doc->createTextNode( $Document_Array["name"] )
);
$b->appendChild( $titleName );
$r->appendChild( $b );
$doc->save("myfile_files.xml");
XML:
<?xml version="1.0" encoding="UTF-8"?>
<content_sets>
<doc_types>
<article>
<doc_name>Test Proposal</doc_name>
<file_name>tes_prop.docx</file_name>
<doc_description>Test Word document. Please remove when live.</doc_description>
<doc_tags>word document,test,rfp template,template,rfp</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</doc_types>
<video_types>
<article>
<doc_name>Test Video</doc_name>
<file_name>test_video.avi</file_name>
<doc_description>Test video. Please remove when live.</doc_description>
<doc_tags>test video,video, avi,xvid,svid avi</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</video_types>
</content_sets>
HTML:
<p>Content Type:<br/>
<select name="file_type">
<option value="0">Document</option>
<option value="1">Video</option>
<option value="2">Image</option>
</select></p>
I thried to include the most imporant parts of the script but can post all of it if needed.
Thanks!
1) Use XPath to find the node you wish to change
2) Once you have the node, simply assign a new value to it.
3) Write the file when you're done
Check out these links for more details:
Change XML node element value in PHP and save file
http://quest4knowledge.wordpress.com/2010/09/04/php-xml-create-add-edit-modify-using-dom-simplexml-xpath/
http://www.ibm.com/developerworks/opensource/library/x-xpathphp/?ca=drs-

update XML using php issues with getElementsByTagName and identifying the correct childnode

how do I identify the correct XML node based off a $_POST variable from a user submitted form. Below is my current XML with a note on were I want the new XML data to be placed and the PHP that takes the form data and prepares it to be inserted into the XML document.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<content_sets>
<!-- The new article node will be placed inside of one of the content_sets child nodes. Either doc_types, video_types, image_types. -->
<doc_types>
<article>
<doc_name>Test Proposal</doc_name>
<file_name>tes_prop.docx</file_name>
<doc_description>Test Word document. Please remove when live.</doc_description>
<doc_tags>word document,test,rfp template,template,rfp</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</doc_types>
<video_types>
<article>
<doc_name>Test Video</doc_name>
<file_name>test_video.avi</file_name>
<doc_description>Test video. Please remove when live.</doc_description>
<doc_tags>test video,video, avi,xvid,svid avi</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</video_types>
<image_types>
<article>
<doc_name>Test Image</doc_name>
<file_name>logo.png</file_name>
<doc_description>Logo transparent background. Please remove when live.</doc_description>
<doc_tags>png,logo,logo png,no background,graphic,hi res</doc_tags>
<last_update>01/26/2013 23:07</last_update>
</article>
</image_types>
</content_sets>
PHP on submit:
$file_type = $_POST['file_type'];
//This is where the node name comes from
$doc = new DOMDocument();
$doc->load( 'rfp_files.xml' );
$doc->formatOutput = true;
$r = $doc->getElementsByTagName("content_sets")->getElementsByTagName($file_type);
*****//The above code is where my issue is coming from. I am not identifying the child node of content_sets correctly.
$b = $doc->createElement("article");
$titleName = $doc->createElement("doc_name");
$titleName->appendChild(
$doc->createTextNode( $Document_Array["name"] )
);
$b->appendChild( $titleName );
$r->appendChild( $b );
$doc->save("rfp_files.xml");
I did not show the form or the rest of article's child nodes. If needed I can post more of my code.
When using getElementsByTagName(), you need to use the item() method so you can retrieve a specific node in the node list - even if there is only one item in the node list, you still have to do this.
getElementsByTagName() will always return a DOM Node List, so you either have to loop through the list, or you have to retrieve a specific item via the item() method - does that make sense? There is an example here: http://php.net/manual/en/domnodelist.item.php

How to keep DOMDocument from saving < as &lt

I'm using simpleXML to add in a child node within one of my XML documents... when I do a print_r on my simpleXML object, the < is still being displayed as a < in the view source. However, after I save this object back to XML using DOMDocument, the < is converted to < and the > is converted to >
Any ideas on how to change this behavior? I've tried adding dom->substituteEntities = false;, but this did no good.
//Convert SimpleXML element to DOM and save
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = false;
$dom->substituteEntities = false;
$dom->loadXML($xml->asXML());
$dom->save($filename);
Here is where I'm using the <:
$new_hint = '<![CDATA[' . $value[0] . ']]>';
$PrintQuestion->content->multichoice->feedback->hint->Passage->Paragraph->addChild('TextFragment', $new_hint);
The problem, is I'm using simple XML to iterate through certain nodes in the XML document, and if an attribute matches a given ID, a specific child node is added with CDATA. Then after all processsing, I save the XML back to file using DOMDocument, which is where the < is converted to &lt, etc.
Here is a link to my entire class file, so you can get a better idea on what I'm trying to accomplish. Specifically refer to the hint_insert() method at the bottom.
http://pastie.org/1079562
SimpleXML and php5's DOM module use the same internal representation of the document (facilitated by libxml). You can switch between both apis without having to re-parse the document via simplexml_import_dom() and dom_import_simplexml().
I.e. if you really want/have to perform the iteration with the SimpleXML api once you've found your element you can switch to the DOM api and create the CData section within the same document.
<?php
$doc = new SimpleXMLElement('<a>
<b id="id1">a</b>
<b id="id2">b</b>
<b id="id3">c</b>
</a>');
foreach( $doc->xpath('b[#id="id2"]') as $b ) {
$b = dom_import_simplexml($b);
$cdata = $b->ownerDocument->createCDataSection('0<>1');
$b->appendChild($cdata);
unset($b);
}
echo $doc->asxml();
prints
<?xml version="1.0"?>
<a>
<b id="id1">a</b>
<b id="id2">b<![CDATA[0<>1]]></b>
<b id="id3">c</b>
</a>
The problem is that you're likely adding that as a string, instead of as an element.
So, instead of:
$simple->addChild('foo', '<something/>');
which will be treated as text:
$child = $simple->addChild('foo');
$child->addChild('something');
You can't have a literal < in the body of the XML document unless it's the opening of a tag.
Edit: After what you describe in the comments, I think you're after:
DomDocument::createCDatatSection()
$child = $dom->createCDataSection('your < cdata > body ');
$dom->appendChild($child);
Edit2: After reading your edit, there's only one thing I can say:
You're doing it wrong... You can't add elements as a string value for another element. Sorry, you just can't. That's why it's escaping things, because DOM and SimpleXML are there to make sure you always create valid XML. You need to create the element as an object... So, if you want to create the CDATA child, you'd have to do something like this:
$child = $PrintQuestion.....->addChild('TextFragment');
$domNode = dom_import_simplexml($child);
$cdata = $domNode->ownerDocument->createCDataSection($value[0]);
$domNode->appendChild($cdata);
That's all there should be to it...

Categories