Using item() to insert data into XML document - php

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-

Related

Remove empty elements from XML in php

Say I have this XML and I need to remove empty elements (elements that don't contain data at all) such as:
...
<date>
<!-- keep oneDay -->
<oneDay>
<startDate>1450288800000</startDate>
<endDate>1449086400000</endDate>
</oneDay>
<!-- remove range entirely -->
<range>
<startDate/>
<endDate/>
</range>
<!-- remove deadline entirely -->
<deadline>
<date/>
</deadline>
<data>
...
The output then should be
...
<oneDay>
<startDate>1450288800000</startDate>
<endDate>1449086400000</endDate>
</oneDay>
...
I'm looking for a dynamic solution that would work on any cases like this regardless of the literal name of the element.
SOLUTION (UPDATED)
It turns out that using //*[not(normalize-space())] returns all elements without non-empty text content (no need for recursion).
foreach($xpath->query('//*[not(normalize-space())]') as $node ) {
$node->parentNode->removeChild($node);
}
Check out #har07's solution for more details
SOLUTION
The xPath approach provided by #manuelbc works but only on child elements (meaning that the children will be gone but the parent nodes of those will stay... empty as well).
However, this will work recursively until the XML document is out of empty nodes.
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->loadxml('<XML STRING GOES HERE>');
$xpath = new DOMXPath($doc);
while (($notNodes = $xpath->query('//*[not(node())]')) && ($notNodes->length)) {
foreach($notNodes as $node) {
$node->parentNode->removeChild($node);
}
}
$doc->formatOutput = true;
echo $doc->saveXML();
You can do it with XPath
<?php
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->loadxml('<date>
<!-- keep oneDay -->
<oneDay>
<startDate>1450288800000</startDate>
<endDate>1449086400000</endDate>
</oneDay>
<!-- remove range entirely -->
<range>
<startDate/>
<endDate/>
</range>
<!-- remove deadline entirely -->
<deadline>
<date/>
</deadline>
<data>');
$xpath = new DOMXPath($doc);
foreach( $xpath->query('//*[not(node())]') as $node ) {
$node->parentNode->removeChild($node);
}
$doc->formatOutput = true;
echo $doc->savexml();
See original solution here:
Remove empty tags from a XML with PHP
The XPath in the other answer only returns empty elements in the sense that the element has no child node of any kind (no element node, no text node, nothing). To get all empty elements according to your definition, that is element without non-empty text content, try using the following XPath instead :
//*[not(normalize-space())]
eval.in demo
output :
<?xml version="1.0"?>
<data>
<!-- keep oneDay -->
<oneDay>
<startDate>1450288800000</startDate>
<endDate>1449086400000</endDate>
</oneDay>
<!-- remove range entirely -->
<!-- remove deadline entirely -->
</data>

Combining several XML documents sequential with PHP

I am trying to merge xml-documents from folder "files" into one DOMDocument, and create a table of contents.
The documents have the following structure:
<chapter title="This is first chapter">
<section title="This is the first section">
<paragraph title="This is the first paragraph">This is the paragraph content</paragraph>
</section>
</chapter>
The following code is used for merging the XML-files:
foreach(glob("files/*xml") as $filename) {
$count++;
if ($count == 1)
{
$first = new DOMDocument("1.0", 'UTF-8');
$first->formatOutput = true;
$first->load($filename);
$xml = new DOMDocument("1.0", 'UTF-8');
$xml->formatOutput = true;
}
else {
$second = new DOMDocument("1.0", 'UTF-8');
$second->formatOutput = true;
$second->load($filename);
$second = $second->documentElement;
foreach($second->childNodes as $node)
{
$importNode = $first->importNode($node,TRUE);
$first->documentElement->appendChild($importNode);
}
$first->saveXML();
$xml->appendChild($xml->importNode($first->documentElement,true));
}
}
print $xml->saveXML();
Everything seems to work OK, except a problem with <chapter>-elements. This is what happens when two documents (let's say two identical versions of the XML i presented in the beginning of my question) are merged:
<chapter title="This is first chapter">
<section title="This is the first section">
<paragraph title="This is the first paragraph">This is the paragraph content</paragraph>
</section>
<chapter title="This is second chapter">
<section title="This is the first section">
<paragraph title="This is the first paragraph">This is the paragraph content</paragraph>
</section>
</chapter>
</chapter>
I think the reason for this problem, is that there are no root element for the merged documents. So, is there for example a way to add a <doc> tag or something for the merged XML's?
Look at it from another view point. You create a new document that combines all the chapters of you book. So create a book element and import the chapters into it.
// create a new document
$dom = new DOMDocument();
// and add the root element
$dom->appendChild($dom->createElement('book'));
// for each document/xml to add
foreach ($chapters as $chapter) {
// create a dom
$addDom = new DOMDocument();
// load the chapter
$addDom->load($chapter);
// if here is a root node in the loaded xml
if ($addDom->documentElement) {
// append to the result dom
$dom->documentElement->appendChild(
// after importing the document element to the result dom
$dom->importNode($addDom->documentElement, TRUE)
);
}
}
echo $dom->saveXml();

appendChild using DOMDocument/PHP/XML

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.

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 set a node of an XML file?

I've created an XML document.
So, now, I want to find the good node and set the values of this node, but after any research about this topic, I don't know how to do it.
This is my document :
<?xml version="1.0" encoding="utf-8"?>
<scripts>
<script nom="myTools.class.php">
<titre>Useful php classes</titre>
<date>18/07/2011</date>
<options>
<option name="topic">Tutorials</option>
<option name="desc">Tutorial for you</option>
</options>
</script>
<script nom="index.php">
<titre>blabla</titre>
<date>15/07/2011</date>
<options>
<option name="topic">The homepage</option>
</options>
</script>
</scripts
>
So, I would to build an html form with theses values, but at this moment, I can't get and set that I want :(
I want get the first "script" node :
<script nom="myTools.class.php"> //How to set the "nom" attribute ?
<titre>Useful php classes</titre> //How to get this value and set it ?
<date>18/07/2011</date>
<options>
<option name="topic">Tutorials</option>
<option name="desc">Tutorial for you</option>
</options>
</script>
I have no problem to loop all the document, but not with only my "own choices"
Have you an idea ?
use XPath
first get the dom document
$dom=new DOMDocument();
$dom->loadXML('file'); // file is the name of XML file if u have a string of XML called $string then use $dom->loadXML($string)
$xpath=new DOMXPath($dom);
$path='//scripts/script[1]'; // that would get the first node
$elem=$xpath->query($path);
now $elem[0] is your first script node
if u want to get elements by their attribute then use $path='//scripts/script[#nom='attribute value']';
now using this path will return a nodeset with script elements having a nom attribute of ur given value
you can see more here
in response to bahamut100's comment
the xpath fot the option element is //options/option
now if u meant getting an option node by attribute value then do this
$path='//options/option[#attrib_name=attrib_value]';
$elem=$xpath->query($path);
but if u meant getting the attributes of a node then first u have to reach that node. in ur case u have to reach to the option node first
$path='//options/option';
$option=$xpath->query($path);
now $option is a node list
so for getting the first element's attibutes use
$attribute=$option[0]->attributes;
now $attribute is a NamedNodeMap so for getting the value of first attribute use
$value=$attribute->item(0);
XPath is one way of doing it:
$dom = new DOMDocument();
$dom->loadXML(... your xml here ...);
$xp = new DOMXPath($dom);
$results = $xp->query('//script[#nom='myTools.class.php']/titre');
$old_title = $results[0]->nodeValue;
$results[0]->nodeValue = 'New title here';

Categories