I am using PHP and an external XML file to display an HTML page which allows a user to update an item's status using various form/input buttons.
The current format is that you can look at one HTML page with a master list of items and their status, then click on one to move to a new HTML page letting you specify the status of that item. So I can click on "Widget X", then see a new page (status.php?id=X), then click on a button labeled "ready", and PHP will use $_GET["id"] to find the relevant XML node and replace it's attribute with "ready".
For some reason I absolutely cannot get PHP to use the data from $_GET to specify an XML node. Here is the current troubleshooting code:
<?php
$idx = $_GET["id"]; /*Get id# from previous page*/
$xml = simplexml_load_file('test.xml'); /*Load external .xml*/
$array = array(100, 200, 300); /*Basic array for comparison*/
print_r($xml); /*Display contents of test.xml*/
$xml->item[$idx]->status = 'waiting'; /*change item $idx's status to 'waiting'*/
echo '<br/>';
print_r($xml); /*Display changed xml*/
/*Display changed basic array values to confirm $idx has a value*/
$array[$idx] = 123;
echo '<br/>';
print_r($array);
$array["$idx"] = 456;
echo '<br/>';
print_r($array);
$array["{$idx}"] = 789;
echo '<br/>';
print_r($array);
?>
And here is the contents of test.xml:
<?xml version="1.0" standalone="yes"?>
<widgets>
<item>
<name>Widget X</name>
<status>ready</status>
</item>
<item>
<name>Widget Z</name>
<status>waiting</status>
</item>
</widgets>
The basic array has no problem using $idx = $_GET["id"]; to choose which array element to change. But $xml->item[$idx]->status does not work. If, however, I change to $idx = 0, then $xml->item[$idx]->status does work.
Is this a bug, or am I missing something about how $_GET or SimpleXML works?
Accessing nth element of XML in PHP with SimpleXml
It seems SimpleXML distinguishes between numeric and non-numeric array
offsets in a slightly different way to a normal PHP array, so you need
to cast your variable to an integer first. (All input from the query
string is a string until you tell PHP otherwise.)
$var = intval($_GET['var']); echo $xml->foo[$var]->bar; This will turn
the string '1' into the integer 1, and should give the result you
require.
Related
I am trying to add an array $item to an XML file in order to then be able to read all of the items in a later time.
I have the following PHP to perform this action:
<?php
$item = array();
$item['rating'] = $_GET['rating'];
$item['comment'] = $_GET['comment'];
$item['item_id'] = $_GET['item_id'];
$item['status'] = "pending";
//Defining $xml
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($item, array($xml, 'addChild'));
$xml = $xml->asXML();
$dom = new DOMDocument;
$dom->preserveWhiteSpace = FALSE;
$dom->loadXML($xml);
//Save XML as a file
$dom->save('reviews.xml');
However, when run what I get this in my XML file:
< ?xml version="1.0"?>
Basically my array is no where to be seen.
A var_dump of $item gives
array(4) { ["rating"]=> string(1) "8" ["comment"]=> string(17) "I Really Like it!" ["item_id"]=> string(1) "9" ["status"]=> string(7) "pending" }
How could I modify my code in order to have it save an array (and if there are many keep them all) in the file reviews.xml?
Also How could I make it so that later on I would be able to access the data; for instance changing the status from pending to approved?
EDIT:
Using the following code I have been able to save my item to the file:
$item = array();
$item[$_GET['rating']] = 'rating';
$item[$_GET['comment']] = 'comment';
$item[$_GET['item_id']] = 'item_id';
$item['pending'] = 'status';
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($item, array($xml, 'addChild'));
$xml->asXML('reviews.xml');
However I am still unable to append new data to the root rather than overwriting the current saved data.
As I was saying in my comment... The code you provided errors with WARNING DOMDocument::loadXML(): Empty string supplied as input. You never assigned anything to $xml'...
Proper error reporting/logging would help spot these mistakes.
<?php
$item = array();
$item['rating'] = 'a';
$item['comment'] = 'b';
$item['item_id'] = 'c';
$item['status'] = "pending";
//Defining $xml
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($item, array($xml, 'addChild'));
//THIS IS THE LINE YOU WERE MISSING
$xml = $xml->asXML();
$dom = new DOMDocument;
$dom->preserveWhiteSpace = FALSE;
$dom->loadXML($xml);
//Save XML as a file
$dom->save('reviews.xml');
If you echoed it out...
var_dump($dom->saveHTML());
> string(80)
> "<root><a>rating</a><b>comment</b><c>item_id</c><pending>status</pending></root>"
Please avoid updating your existing question with additional questions.
A database would make the task easier. Using a flat file works fine though, XML, or some other format. You will need to be able to retrieve a record by item_id, at which point you modify it, then replace it. That is the gist of it.
So here's an overhaul of your code, with some changes to both your approach and the scheme of your XML, based on your various comments and updates.
So first, instead of creating XML that looks like this:
<root>
<rating>a</rating>
<comment>b</comment>
<item_id>c</item_id>
<status>pending</status>
</root>
You're going to store the XML like this:
<root>
<item id="c">
<rating>a</rating>
<comment>b</comment>
<status>pending</status>
</item>
</root>
This is based on a few of your comments:
You are wanting to add to the XML file rather than overwrite the existing file content. That suggests that you want to store multiple items. This would also explain why you have a property item_id. So rather than having a mess of XML like :
<root>
<rating>a</rating>
<comment>b</comment>
<item_id>c</item_id>
<status>pending</status>
<rating>d</rating>
<comment>e</comment>
<item_id>f</item_id>
<status>pending</status>
<rating>g</rating>
<comment>h</comment>
<item_id>i</item_id>
<status>pending</status>
</root>
where it is impossible to know which item is which, you store each set of item properties on an <item> element. Since you are going to want to easily grab an item based on its item_id in order to update that item, making item_id an attribute of the <item> makes more sense than making it a child of the <item>.
You want to be able to update the status. This is where having the item_id stored on the item comes in handy. If someone submits a request with an existing item_id, you can update that item, including its status element. Or you could do it whenever you need to from some other process, etc.
Here's the code I drummed up for this. Note that it currently isn't set up to look for an existing element with that item id, but that should be possible using existing SimpleXML functions/methods.
$item = array();
$item_id = "c";
$item['rating'] = 'a';
$item['comment'] = 'b';
$item['status'] = "pending";
$xml = simplexml_load_file('ratings.xml');
//if ratings.xml not found or not valid xml, create clean XML with <root/>
if($xml === false) {
$xml = new SimpleXMLElement('<root/>');
}
$xml_item = $xml->addChild("item");
$xml_item->addAttribute("id", $item_id);
foreach($item as $name => $value) {
$xml_item->addChild($name, $value);
}
$xml->asXML('ratings.xml');
Notice that one of the major changes I made to your existing code is changing from using array_walk_recursive to a simple foreach. array_walk_recursive for this purpose is a short cut that causes more issues than it solves. For instance, you had to swap your key and value on the $item array, which is confusing. It also isn't necessary for what you currently are doing, since you don't have a multi-dimensional array. And even if you did, array_walk_recursive isn't the right choice to handle looping over the array recursively because it would add each array member to the root of the XML, not add sub-arrays as children of their parent entry as they show up in the actual array. Point being, it's confusing, it doesn't add any value, and using a foreach is a lot more clear on what you are actually doing.
I've also changed
$item['item_id'] = 'c';
to
$item_id = 'c';
and then added it to the item element as an attribute like:
$xml_item->addAttribute("id", $item_id);
This is consistent with the new schema I outlined earlier.
Finally, instead of passing the XML to DOMDocument, I'm just using
$xml->asXML('ratings.xml');
SimpleXML already removes any extra whitespace, so there is no need to use DOMDocument to achieve this.
Based on some of the counterintuitive parts of your original code, it looks like you may have done a decent amount of copy and pasting to get it going. Which is where most of us start, but it's a good idea to be upfront about things like "I don't understand quite what this code is doing, I just grabbed it from a script that did some of what I need." It will save us all a lot of time and grief if we're not assuming you are using the code you have because you need to or it was a conscious decision, etc, and that we have to work within the constraints of that code.
I hope this gets you off to a good start.
Update
I was messing around with it, and came up with the following for updating existing <item> if an item with id set to $item_id already exists. It's a bit clunky, but it tested and it works.
This assumes the $item_id and $item array get set as normal, as well as retrieving the exiting XML, as covered above. I'm providing the lines just before the changes for reference:
$xml = simplexml_load_file('ratings.xml');
//if ratings.xml not found or not valid xml, create clean XML with <root/>
if($xml === false) {
$xml = new SimpleXMLElement('<root/>');
}
//query with xpath for existing item with $item_id
$item_with_id = $xml->xpath("/root/item[#id='{$item_id}']");
// if the xpath returns a result, update that item with new values.
if(count($item_with_id) > 0) {
$xml_item = $item_with_id[0];
foreach($item as $name => $value) {
$xml_item->$name = $value;
}
} else {
// if the xpath returns no results, create new item element.
$xml_item = $xml->addChild("item");
$xml_item->addAttribute("id", $item_id);
foreach($item as $name => $value) {
$xml_item->addChild($name, $value);
}
}
I'm trying to get the content of a tag that is within another tag together with a specifig ID tag. The source is the below xml response:
The Xml response is here
<PricePlans>
<PricePlan>
<Description>Check Email</Description>
<ID>1</ID>
<Price>2</Price>
<Time>900</Time>
</PricePlan>
<PricePlan>
<Description>High Speed</Description>
<ID>2</ID>
<Price>5</Price>
<Time>3600</Time>
</PricePlan>
</PricePlans>
my php code is here:
echo "Desc" ." ".$xml->PricePlan->Description ."</br>";
This code gives me the content of the first "Description" tag (Check Email) but I want the description of a priceplan with a specifig "ID" tag (e.g. ID 2 - "High Speed")
The xml response might have even more "PricePlan" tags but each has a unique value in the "ID" tag.
You can access them the same as array:
echo($xml->PricePlan[0]->Description);
//Check Email
echo($xml->PricePlan[1]->Description);
//High Speed
foreach ($xml->PricePlan as $pricePlan) {
echo($pricePlan->Description);
}
//Check Email
//High Speed
If you need to find element by value in ID you may use xpath:
$el = $xml->xpath('/PricePlans/PricePlan/ID[text()=2]/..');
To take the Description of an element with a particular ID you can use xpath.
$id = 2;
// xpath method always returns an array even when it matches only one element,
// so if ID is unique you can always take the 0 element
$description = $xml->xpath("/PricePlans/PricePlan/ID[text()='$id']/../Description")[0];
echo $description; // this echoes "High Speed"
I have an xml file that is created thru php. The basic structure is:
<catgories> *this is root*
<category> *element*
<title>xy</title> *attribute*
<desc>yu</desc> *attribute*
<categoryLeaf> *child element of category*
<title>rt</title> *attribute of categoryLeaf*
</categoryLeaf>
<endtvod></endtvod>
</category>
</categories>
The xml is created on the fly at one time with the exception of the categoryLeaf and it's attributes which are created in a separate function. The xml is created correctly. In order to get the categoryLeaf I am parsing an html and extracting information and storing that information in an array with the follwoing:
if (!empty($dom)) { //IF NOT EMPTY FIND CATEGORIES
$clsort = $xpath->query('//div[#class="sort"]');
foreach($clsort as $clsorts){
$li= $clsorts->getElementsByTagName('li');
foreach ($li as $lis){
$links=$lis->getElementsByTagname('a');
foreach ($links as $link){
$href=$link->getAttribute('href');
$text=$link->nodeValue;
if ($text=="#"){
break;
}else{
$textarray=$text;
ECHO $textarray."<br>";
CategoryLeafXml($textarray);
}
}
}
}
}
else
{
echo "EMPTY CONTAINER". "<br>";
}
The desired information is obtained thru the code above and added to the textarray. I can echo it and each shows up. I pass the array to a function that is to open the xml, insert the categoryLeaf along with the title attribute and save thru code:
Function CategoryLeafXml($textarray){
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->formatOutput = true;
$xpath = new DOMXPath($xml);
$xml-> loadXML ('.\\xml\\test.xml');
$arrcount=count($textarray);
echo $arrcount."<br>";
$categoryLeaf=$xml->createElement("categoryLeaf");
for ($i=0; $i<$arrcount;$i++){
echo $textarray."<br>";
$endtvod=$xml->getElementsByTagName('endtvod')->item(0); //LOCATE tag
$category=$xml->getElementsByTagName('category');
$categories=$xml->getElementsByTagName('categories');
$newleaf=$xml->insertBefore($categoryLeaf, $endtvod);
$title = $xml->createAttribute("title");
$title->value= $textarray;
$categoryLeaf->appendChild($title);
$xml->save('.\\xml\\test.xml');
}
}
What is suppose to occur is the xml structure is created, the html is parse into the textarray, the textarray is passed to the function where the element categoryLeaf is inserted before the element tvod. The textarray is the attribute value for the categoryLeaf/title. I am having 2 problems, (1)the xml is opened and the categoryLeaf is created, but when saved the result is:
<?xml version="1.0" encoding="UTF-8"?>
<categoryLeaf title="value of textarray"/>
The xml is overwritten leaving only the one inserted element with only the last array value.
(2) The array count always shows 1 and the for loop only runs one time writing only the array's last value. I anticipated a categoryLeaf being added for value of the array count but it is only seeing that value as being one. I know there are about 25 entries in the array.
You are only creating a single categoryLeaf node through:
$categoryLeaf=$xml->createElement("categoryLeaf")
and changing the attribute n times.
You have to create the categoryLeaf inside the for loop.
I also do not understand why you are doing this:
$category=$xml->getElementsByTagName('category');
$categories=$xml->getElementsByTagName('categories');
That does not seem to do anything inside the function.
Just try something like this:
Function CategoryLeafXml($textarray){
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->formatOutput = true;
$xpath = new DOMXPath($xml);
$xml-> loadXML ('.\\xml\\test.xml');
$endtvod = $xml->getElementsbyTagName('endtvod')->item(0)
foreach ($textarray as $text){
$categoryLeaf=$xml->createElement("categoryLeaf");
$categoryLeaf->setAttribute('title', $text);
$endtvod->parentNode->insertBefore($categoryLeaf, $enttvod)
}
$xml->save('.\\xml\\test.xml');
}
i've tried to find this out by myself before asking but cant really figure it out.
What I have is a loop, it's actually a loop which reads XML data with simplexml_load_file
Now this XML file has data which I want to read and put into an array.. a two dimensional array actually..
So the XML file has a child called Tag and has a child called Amount.
The amount is always differnt, but the Tag is usually the same, but can change sometimes too.
What I am trying to do now is:
Example:
This is the XML example:
<?xml version="1.0"?>
<Data>
<Items>
<Item Amount="9,21" Tag="tag1"/>
<Item Amount="4,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
<Item Amount="1,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
</Data>
</Items>
Now i have a loop which reads this, sees what tag it is and adds up the amounts.
It works with 2 loops and two different array, and I would like to have it all in one array in single loop.
I tried something like this:
$tags = array();
for($k = 0; $k < sizeof($tags); $k++)
{
if (strcmp($tags[$k], $child['Tag']) == 0)
{
$foundTAG = true;
break;
}
else
$foundTAG = false;
}
if (!$foundTAG)
{
$tags[] = $child['Tag'];
}
and then somewhere in the code i tried different variations of adding to the array ($counter is what counts the Amounts together):
$tags[$child['Tag']][$k] = $counter;
$tags[$child['Tag']][] = $counter;
$tags[][] = $counter;
i tried few other combinations which i already deleted since it didnt work..
Ok this might be a really noob question, but i started with PHP yesterday and have no idea how multidimensional arrays work :)
Thank you
this is how you can iterate over the returned object from simple xml:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
foreach($xml->Items->Item as $obj){
foreach($obj->Attributes() as $key=>$val){
// php will automatically cast each of these to a string for the echo
echo "$key = $val\n";
}
}
so, to build an array with totals for each tag:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
$tagarray=array();
// iterate over the xml object
foreach($xml->Items->Item as $obj){
// reset the attr vars.
$tag="";
$amount=0;
// iterate over the attributes setting
// the correct vars as you go
foreach($obj->Attributes() as $key=>$val){
if($key=="Tag"){
// if you don't cast this to a
// string php (helpfully) gives you
// a psuedo simplexml_element object
$tag=(string)$val[0];
}
if($key=="Amount"){
// same as for the string above
// but cast to a float
$amount=(float)$val[0];
}
// when we have both the tag and the amount
// we can store them in the array
if(strlen($tag) && $amount>0){
$tagarray[$tag]+=$amount;
}
}
}
print_r($tagarray);
print "\n";
This will break horribly should the schema change or you decide to wear blue socks (xml is extremely colour sensitive). As you can see dealing with the problem child that is xml is tedious - yet another design decision taken in a committee room :-)
OK sorry about this, but getting knickers in twist with what is really a very simple task. I have a simple xml structure:
<site>
<page>
<pagename>page1</pagename>
<title>title1</title>
<id>abc
<plot>
this is text
</plot>
</id>
</page>
<page>
<pagename>page2</pagename>
<title>titlee2</title>
<id>xyz
<plot>
this is text
</plot>
</id>
</page>
</site>
I pass 3 variables by ajax post from a form the variables are pagename, id and plot.
What I cannot seem to do is write the right query that checks the pagename node against the pagename variable, then checks for the id node in that page. If the id node exists then I update the plot node, if the id does not exist I create the id node and the plot node.
I can write/update the nodes, somebody did help find the right page but then I get lost. So in my example xml above if I pass the variables pagename=page1 and id=abc then I just update the plot, but if pagename is page1 and id=def (which does not exist) the I need to create the id and the plot. as said can do the addChild, update bit, just cannot get the query right to check that the id exists for that page - note $id = $site->xpath('//id[text()="'.$posted_variable.'"]'); doesn't work for me because the id might be present in more than one page node, but it cannot be duplicated in the same page node.
Many thanks for help
OK I have it working, may not be pretty but it does work -
$xml = simplexml_load_file('xml.xml');
$site = new SimpleXMLElement($xml->asXML());
$paget = "index";
$text = $_POST['tosave'];
$text = htmlentities($text, ENT_QUOTES, 'UTF-8');
// EDIT / UPDATE //
$pages = $site->xpath('//page/pagename[text()="'.$paget.'"]/..');
if($pages){
$a = array();
foreach( $pages[0]->id as $ID ){
array_push($a,$ID);
}
if(in_array($_POST['containerid'],$a)){
foreach ($a as $key => $value) {
if($value==$_POST['containerid']){
$pages[0]->id[$key]->plot = trim($text) ;
}
}
} else {
$pages[0]->addChild('id',$_POST['containerid'])->addChild('plot',trim($text));
}
}
$site->asXML("xml.xml");
exit;