PHP's SimpleXML doesn't save edited data - php

I'm trying to edit some xml data. After this I want to save the data to file.
The problem is that the edited data isn't saved by simplexml but the node has changed.
$spieler = $xml->xpath("/planer/spieltag[#datum='" .$_GET['date']. "']/spielerliste/spieler");
for ( $i = 1; $i < 13; $i++ ){
if (!empty($_POST['spieler' .$i ])){
$spieler[$i-1] = $_POST['spieler' .$i];
}
}
var_dump($spieler);
$xml->asXML("data.xml");
var_dump() shows the new data, but asXML() doesn't.

Make sure your script has write permission to data.xml

The XPath result array elements aren't PHP ($ref = &$var) references to the actual tree nodes, so this line
$spieler[$i-1] = $_POST['spieler' .$i];
isn't modifying anything in the tree, you're simply overwriting an entry in a completely independent array.

Related

Missing data foreach XML

something happening in my script and i cant figure out whats going on, basically i have a array of products, and with this array im populating the items in the foreach loop to a xml file, the problema is that in the first nodes appears empty data, and im sure that i dont have emmpty data in the array, i already tried just printing the data in plaintext and works fine, only when im looping and creating the xml file.
My script:
$xml = new SimpleXMLElement('<products/>');
for ($i = 0; $i <= sizeof($catalog); ++$i) {
$track = $xml->addChild('product');
$track->addChild('name', $i . $catalog[$i]["name"]);
$track->addChild('brand', $catalog[$i]["brand"]);
}
Header('Content-type: text/xml');
print($xml->asXML());

Using PHP to convert XML to CSV but with a twist

I'm trying to convert some XML files I have to CSV using PHP SimpleXML class. However, I'm unable to achieve the result I want, because one parent could have several child elements with the same name. My current XML file is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<club>
<name>Green Riders</name>
<membership>Free</membership>
<boardMember>
<name>James F.</name>
<position>CEO</position>
</boardMember>
<boardMember>
<name>Helen D.</name>
<position>Associate Director</position>
</boardMember>
</club>
<club>
<name>Broken Dice</name>
<membership>Paid</membership>
<boardMember>
<name>Patrick B.</name>
<position>CEO</position>
</boardMember>
</club>
</root>
The CSV output I was hoping to achieve is as such:
club,name,membership,boardMember>Name,boardMember>position
Green Riders,Free,James F.,CEO
Green Riders,Free,Helen D., Associate Director
Broken Dice,Paid,Patrick B., CEO
Is there anyway to achieve this without hard-coding the element names into the script (i.e. make it work on any generic XML file)?
I'm really hoping this is possible, given that I'll be having more than 25 XML variants; so would really be inefficient to write a dedicated script for each.
Thanks!
Since every child node's data need to be a row in the csv including the root root data, First you can capture & store the root data, then traverse the children and print their data with the root's data preceding them.
Please check the following code:
$xml = simplexml_load_file("your_xml_file.xml") or die("Error: Cannot create object");
$csv_delimeter = ",";
$csv_new_line = "\n";
foreach($xml->children() as $n) {
$club_data = array();
$club_data[] = $n->name;
$club_data[] = $n->membership;
if (isset($n->boardMember)) {
foreach ($n->boardMember as $boardMember) {
$boardMember_data = $club_data;
$boardMember_data[] = $boardMember->name;
$boardMember_data[] = $boardMember->position;
echo implode($csv_delimeter, $boardMember_data).$csv_new_line;
}
}
else {
echo implode($csv_delimeter, $club_data).$csv_new_line;
}
}
After testing with the example xml data, it generated the following type of output:
Green Riders,Free,James F.,CEO
Green Riders,Free,Helen D., Associate Director
Broken Dice,Paid,Patrick B., CEO
You can set different values based on your scenario for:
$csv_delimeter = ",";
$csv_new_line = "\n";
As there are no strict rules in csv output - like delimeter can be ",", ",", ";" or "|" and also new line can be "\n\r"
The codes prints csv rows one-by-one on the fly, but if you are to save csv data in a file, then instead of writing rows one-by-one, better approach would be create the entire array and write it once(as disk access is costly) unless the xml data is large. You will get plenty of simple php array-to-csv function examples in the net.
It is not really possible. XML is a nested structure and you miss the information. You can define some default mapping for XML structures, but that gets really complex really fast. So it is far easier (and less time consuming) to define the mapping by hand.
A Reusable Conversion
function readXMLAsRecords(string $xml, array $map) {
// load the xml
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
// iterate the elements defining the rows
foreach ($xpath->evaluate($map['row']) as $row) {
$line = [];
// get the field values from the current $row
foreach ($map['columns'] as $name => $expression) {
$line[$name] = $xpath->evaluate($expression, $row);
}
// return a line
yield $line;
}
}
The Mapping
With DOMXpath::evaluate() Xpath expressions can return strings. So we need one expression that returns the boardMember nodes and a list of expressions for the fields.
$map = [
'row' => '/root/club/boardMember',
'columns' => [
'club_name' => 'string(parent::club/name)',
'club_membership' => 'string(parent::club/membership)',
'board_member_name' => 'string(name)',
'board_member_position' => 'string(position)'
]
];
To CSV
readXMLAsRecords() returns a generator, you can use foreach on it:
$csv = fopen('php://stdout', 'w');
fputcsv($csv, array_keys($map['columns']));
foreach (readXMLAsRecords($xml, $map) as $record) {
fputcsv($csv, $record);
}
Output:
club_name,club_membership,board_member_name,board_member_position
"Green Riders",Free,"James F.",CEO
"Green Riders",Free,"Helen D.","Associate Director"
"Broken Dice",Paid,"Patrick B.",CEO

Flexibility for a code to generate baum hierarchy for a given CSV file

I need to convert CSV file's content into an hierarchy for my website with the help of Baum Library for Hierarchy generation...
Below is the format for CSV file which user will upload for parsing into Baum Hierarchy. Columns in it represents level of hierarchy.
CSV File Screenshot
Now i have parsed this CSV file into 2 dimensional array ( $companyDetailsIn2dArray ) and the code i have wrote supports generation till 3rd level only and i want it to be "flexible for any level" i.e. for levels more than 3. Now to do so, with the code, i have wrote encourages me to write recursion function for it but i am confused, from where should i begin this. Temporary variables($lastDeptNodeI; $lastDeptNodeJ; $lastMacNodeI; $lastMacNodeJ) that i have used for storing last created nodes limits me to write recursion function...
Please help me writing recursion function or suggest me any other way to obtain Baum Hierarchy with CSV file...
Following is my code...
$lastDeptNodeI =-1; //variable used to store array location(row) for last created 1st level node i.e. Dept node
$lastDeptNodeJ =-1; //variable used to store array location(column) for last created 1st level node i.e. Dept node
$lastMacNodeI =-1; //variable used to store array location(row) for last created 2nd level node i.e. Machine node
$lastMacNodeJ =-1; //variable used to store array location(column) for last created 2nd level node i.e. Machine node
$root = Company::create(['name' => $newCompanyName]); //Creating node
$root->makeRoot(); //Making Root Node
for($i=0;$i<sizeof($companyDetailsIn2dArray);$i++){
for($j=0;$j<sizeof($companyDetailsIn2dArray[$i]);$j++){
if($companyDetailsIn2dArray[$i][$j] != "") {
if ($j == 0) { //if it is Dept!
$newNode[$i][$j] = Company::create(['name' => $companyDetailsIn2dArray[$i]{$j}]);
$newNode[$i][$j]->makeChildOf($root);
$lastDeptNodeI = $i;
$lastDeptNodeJ = $j;
$lastMacNodeI = -1;
$lastMacNodeJ = -1;
} elseif ($j == 1) { // if it is machine
$newNode[$i][$j] = Company::create(['name' => $companyDetailsIn2dArray[$i]{$j}]);
if($lastDeptNodeI!=-1 || $lastDeptNodeJ!=-1) {
$newNode[$i][$j]->makeChildOf($newNode[$lastDeptNodeI][$lastDeptNodeJ]);
$lastMacNodeI = $i;
$lastMacNodeJ = $j;
}
else{
$newNode[$i][$j]->makeChildOf($root);
$lastMacNodeI = $i;
$lastMacNodeJ = $j;
}
} elseif ($j == 2)
{ //if it is Meter!
$newNode[$i][$j] = Company::create(['name' => $companyDetailsIn2dArray[$i]{$j}]);
if($lastMacNodeI!=-1 || $lastMacNodeJ!=-1){
$newNode[$i][$j]->makeChildOf($newNode[$lastMacNodeI][$lastMacNodeJ]);
}
elseif($lastDeptNodeI!=-1 || $lastDeptNodeJ!=-1){
$newNode[$i][$j]->makeChildOf($newNode[$lastDeptNodeI][$lastDeptNodeJ]);
$lastMacNodeI = $i;
$lastMacNodeJ = $j;
}
else{
$newNode[$i][$j]->makeChildOf($root);
$lastDeptNodeI = $i;
$lastDeptNodeJ = $j;
$lastMacNodeI = -1;
$lastMacNodeJ = -1;
}
}
}
}
}
echo "File parsed Successfully!";
I think you could add the nodes in a depth-first search kind of way, when you find a node add the children as well.
If you want to go full recursive you will need 2 recursive functions:
The first would be the function that adds a node to your tree, but
after adding a node it would find all of its child (1 level down)
nodes, and call itself for those.
The signature of this function could look something like this: addNode(parent_node, i_of_current_node, j_of_current_node, content_of_current_node)
The recursion ends when there are no children.
I'd write a helper function to find the children of a node too. You just look for the next nonempty cell with the same j as j_of_current_node, and get the non empty cells from j_of_current_node + 1 between i_of_current_node and i_of_next_node.
The second recursive function would read the companyDetailsIn2dArray
column by column.
The signature of this function could be something like:
readColumn(j_of_current_column, i_of_first_node_in_prev_column)
The recursion ends when j_of_current_column is larger then the size of your array.
As you read a column, you call your addNode function for every none empty cell like this: addNode($root, $i, $j, $companyDetailsIn2dArray[$i][$j])
The addNode creates the new node under root, finds the children, then calls itself for the child nodes like addNode($newNode, $childI, $childJ, $companyDetailsIn2dArray[$childI][$childJ]).
This way, once you read the first column you have added every node in the file under the $i of the first nonempty cell in the first column. You process the first column by calling readColumn(0, i_size_of_array)
When processing any subsequent columns (the same way as the first) you only read it until you reach the i of the first nonempty cell in the previous column, because you have already added nodes with higher i index as those had a parent in the previous column. You process the columns after the first by calling readColumn(j_of_current_column + 1, i_of_first_node_in_current_column).

Looping through files, one file processes, then that's it, no error reporting

So I have a problem with a script I'm working on. I have a folder full of JSON files called roster0.json, roster1, etc. etc.
$dir = "responses/";
$files = glob($dir . "roster*");
$failed = array();
$failcnt = 0;
if (isset($files)) {
$data = null;
for ($i = 0; $i < count($files); $i++) {
$data = json_decode(utf8_decode(file_get_contents($files[$i])));
if(isset($data)){
// Process stuff
When I var_dump($files) I get an array with over 100 paths "responses/roster0.json".
When I test $data I get a proper array of data.
However, once the loop goes to the next file, it never loads it, and never processes it.
Here's the crazy part. If I change the start of the for loop, e.g. $i = 20. It will load the 21st file in the directory and parse it and insert it into the db properly!
Ignoring the failcnt stuff at the bottom, here's the current version of the script in it's entirety. http://pastebin.com/yqyKi5Ag
PS - I have full WARNING/ERROR reporting on in PHP and not getting any error messages...HELP! Thanks!
When I was writing the insert string the ID was being duplicated and thus was invalid. Switched to auto-inc and tada. It works. Thanks for the assistance. –

PHP SimpleXML: Remove items with for

I just can remove an item from a simpleXML element with:
unset($this->simpleXML->channel->item[0]);
but I can't with the a for:
$items = $this->simpleXML->xpath('/rss/channel/item');
for($i = count($items); $i > $itemsNumber; $i--) {
unset($items[$i - 1]);
}
some items are removed from $items (Netbeans Debug can confirm that) but when I get the path again (/rss/channel/item) nothing was deleted.
What's wrong?
SimpleXML does not handle node deletion, you need to use DOMNode for this.
Happily, when you import your nodes into DOMNode, the instances point to the same tree.
So, you can do that :
<?php
$items = $this->simpleXML->xpath('/rss/channel/item');
foreach ($items as $item) {
$node = dom_import_simplexml($item);
$node->parentNode->removeChild($node);
}
You're currently only, as you know, unsetting the item from the array.
To get the magical unsetting to work on the SimpleXMLElement, you have to either do as Xavier Barbosa suggested or give PHP a little nudge into firing off the correct unsetting behaviour.
The only change in the code snippet below is the additions of [0]. Heavy emphasis on the word magical.
$items = $this->simpleXML->xpath('/rss/channel/item');
for($i = count($items); $i > $itemsNumber; $i--) {
unset($items[$i - 1][0]);
}
With that said, I would recommend (as Xavier and Josh have) moving into DOM-land for manipulating the document.
Well I was racking my brain trying to figure out how to delete the last child from an xml document. Then I insert a new element at the top. This way there is always a set amount of items in my rss feed. I could not get the xpath stuff to work. That could be because of the free server I am using but anyways. This is what I did. My xml document is an rss feed so I have 6 elements before the items start. ie. title,description under the channel.
$file = 'newrss.xml';//get file
$fp = fopen($file, "rb") or die("cannot open file");//open the file
$str = fread($fp, filesize($file));//read the file
$xml = new DOMDocument();//new xml DOMDocument
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$xml->loadXML($str) or die("Error");//Load Document
// get document element
$root = $xml->documentElement;
$fnode = $root->firstChild;
$ori = $fnode->childNodes->item(6);//The 6th item starts the item nodes
//Get the number of items in my xml.
$nodeLength = $fnode->getElementsByTagName('item')->length;//count nodes
$itemNum=$nodeLength+5;//I added 5 so it starts from the first item
$lNode = $fnode->childNodes->item($itemNum);//Get the last child node
$fnode->removeChild($lNode);//finally remove that node.
I know this is not pretty but it works good. It took me forever to figure this out so I hope it will help someone else since I see this question a lot. If you are not interested in adding your new item to the top of the rss list then you could skip the $ori variable. Furthermore if you do leave out the $ori variable you will have to adjust the $itemNum so you remove the correct item.

Categories