I have a multidimensional array that is like this:
Array (
[email1#fake.com] => Array (
[0] => Array (
[asin] => B004HFS6Z0
[title] => Kindle, Wi-Fi, Graphite, 6" Display
[price] => 114.00
[map] => 125.00 ) )
[email2#fake.com] => Array (
[0] => Array (
[asin] => B004CYX17O
[title] => Rizzy Home Apple 10-Piece Comforter Set, King
[price] => 502.80
[map] => 520.00 ) ) )
What I am trying to do is export the sub array under the email address into a file, then email it to the email address. So far, I've only been able to export both subarrays into a single file and haven't been able to get it to overwrite the file for the second sub array.
Here is the code I am working with:
require('export-xls.class.php');
$filename = 'email.xls';
$xls = new ExportXLS($filename);
$header[] = "ASIN";
$header[] = "Title";
$header[] = "Retail";
$header[] = "MAP";
$xls->addHeader($header);
//create temp csv file and email from subarray
foreach($map_check2 as $email => $value) {
//$fp = fopen('uploads/email.csv', 'w+');
foreach($value as $subkey => $subvalue) {
//echo $email . "<br>";
//print_r($subvalue) . "<br>";
//fputcsv($fp, $subvalue);
$xls->addRow($subvalue);
}
$xls->emailFile();
}
I need to export it to an excel file and have been using the export-xls.class to do so. I would have used the csv but periodically there will be commas in values in the array.
emailFile() is:
public function emailFile() {
#build the xls
$xls = $this->buildXLS();
$fp = fopen("uploads/email.xls", "w+");
fwrite($fp, $xls);
fclose($fp);
}
Does anyone have any thoughts? Or does that make sense?
It seems in your inner foreach loop, where you are doing $xls->addRow($subvalue), it keeps on adding all email sub-arrays. I think you should call some function inside the outer foreach but before the inner one, to clean the rows that have already been added to $xls.
Here is a test run that I could think of:
first call to the outer foreach is made - $email = email1#fake.com
first call to the inner foearch is made - calls $xls->addrow() for each of the values of the "email1#fake.com" array
first call to $xls->emailFile() is called that opens "email.xls" in w+ mode and writes the contents to the file
second call to the outer foreach is made - $email = email2#fake.com
second call to the inner foearch is made - calls $xls->addrow() for each of the values of the "email2#fake.com" array. Note - I think at this point the rows added earlier to $xls in step 2 still exist and this might be the issue. If yes, you need to some cleanup either before the inner foreach starts or in the $this->buildXLS() method
second call to $xls->emailFile() is called that opens "email.xls" in w+ mode and writes the contents to the file that includes values for both the subarrays.
I'm not sure what is the purpose of $this->buildXLS() and have ignored it for my explanation above.
Does the above make sense?
Related
I have a CSV file called employee_data.csv. It contains employee records formatted like so:
JANE WILLIAMS,6/8/1998,55846874E,4323
PETER JONES,15/01/1982,56897547Q,1234
JAMES O'BRIEN,09/05/2001,25689514W,3432
I want to delete a selected row within the csv file. To achieve this, I will simply copy the 2 rows within the csv file that I do not want to delete to a new_employee_data.csv file and delete the old one.
<?php
$dataSrc = "persistence/employee_data.csv";
$dataDest = "persistence/new_employee_data.csv";
$dataFile = fopen($dataSrc, "r") or die("Unable to open file!");
$outFile = fopen($dataDest, "w") or die("Unable to open file!");
$i=0; //index for the array
while(!feof($dataFile)) {
$csv = fgetcsv($dataFile); //read a line from the CSV file
//$csv = [ANE WILLIAMS,6/8/1998,55846874E,4323];
//add check to remove row
print_r($csv);
if($csv[2] == '55846874E') continue; //skip to next itteration
fputcsv($outFile, $csv);
}
fclose($dataFile);
fclose($outFile);
?>
The code above takes the contents of $dataFile and writes it to $outFile line by line, if the 3rd column = '55846874E' it will skip writing that line. The csv array contains the rows within the employee_data.csv file.
The elements in the $csv array are as follows.
Array ( [0] => JANE WILLIAMS [1] => 6/8/1998 [2] => 55846874E [3] => 4321 )
Array ( [0] => PETER JONES [1] => 15/01/1982 [2] => 56897547Q [3] => 1234 )
Array ( [0] => JAMES O'BRIEN [1] => 09/05/2001 [2] => 25689514W [3] => 8475 )
It removes the first row of the file - JANE WILLIAMS,6/8/1998,55846874E,4323
Now in the new_employee_data.csv is the two undeleted records.
"PETER JONES",15/01/1982,56897547Q,1234
"JAMES O'BRIEN",09/05/2001,25689514W,8475
This is exactly what I want it to do however I received this warning when I run it in the browser:
fputcsv() expects parameter 2 to be array, boolean given in line 25
It's having a problem with fputcsv($outFile, $csv); and I've no idea why, any suggestions of how to fix this?
I would change the while loop So instead of
while(!feof($dataFile)) {
$csv = fgetcsv($dataFile);
like this
while(false !== ($csv = fgetcsv($dataFile))){
You can see an example of this usage on the PHP website here
What probably happens is there is a extra return at the end of the file, so the feof doesn't catch it, and then you get boolean false for the fgetcsv. For example like this (where \n is a new line):
JANE WILLIAMS,6/8/1998,55846874E,4323\n
PETER JONES,15/01/1982,56897547Q,1234\n
JAMES O'BRIEN,09/05/2001,25689514W,3432\n
\n
\eof
So we can combine these (so you wont need the line under the while loop) and just get the data from the same place we do the loop condition, this way when it returns false it will just drop the loop. It's important to be careful with the number of = in this as a single one is assignment and the !== is strict type comparison. So we can break this down a bit and in English what this says.
pull a row and process it with fgetcsv setting $csv to it's value, parentheses get priority
if $csv is boolean false then the loop condition is false an it ends. So basically if false(boolean) not equal to the result of fgetcsv($dataFile) then it's true otherwise it's false.
It would work basically the same like this
while($csv = fgetcsv($dataFile)){
I tend to like the long hand version, because it's easier to see that we are assigning and not comparing. For example you could glance at the above version and then think it should be == instead of = so the first version just makes that a bit more obvious. Putting the false on the left hand side is basically done for the same reason (and because it's essentially a constant, so putting it on the left avoids mistakes like below false = $csv wont work).
Misplacing an = in a condition can actually be one of the harder bugs to figure out, because it's completely legal. So that is kind of a "pro tip" to put function calls and constants on the left when doing comparison.
hope that helps!
I'm pretty familiar with the Strtok() function in PHP, and I have had no problem getting the function to work properly for strings in the past. However, I currently have to read a .csv text file (which I've done successfully) where each line is made of 6 fields like so: last name, first name, address, city, district, postal code\r\n <--carriage return and linefeed at the end
I have to use Strok() to split these by the delimiters and token the words as fields (i.e. last, first, address, etc.). I plan to use an associative array using the last name as the primary key so that I can plug the data into an HTML Table, which is created and working. My issue right now is splitting the file correctly, as it has about 200 lines made of those 6 fields, and storing the strings as fields properly for an array, so the data structure is where I'm having some issues. Here's what I have so far:
$inputFile = fopen("input.csv","r");
$delimiters = ",";
$token = strtok($inputFile, $delimiters);
$n=1;
while ($token){
echo "Token $n: $token <br>";
$token = strtok($delimiters);
$n++;
}
Obviously, the table is created below it but since I haven't done the data structure quite yet, I don't have the fields for it. I think my token loop may be incorrect for this issue, but I pulled some from an earlier example in my book and an exercise I did where my token process worked but the file structure was different. Thanks for any direction or help on this.
There are CSV functions in PHP, like fgetcsv, so it really is the wrong approach to reinvent the wheel.
Note that in your code you don't actually read the content of the file, as you only get a file pointer.
If you really need to do this with strtok, and your CSV is simple, in the sense that it does not have quoted strings, which could have embedded delimiter characters, you could use:
file_get_contents() to read the file content in one string. Of course, file() would make it easier for you, as it would already split lines. But I assume that if CSV functions are not allowable for you, then this will neither.
strtok for getting the fields, but at the end of the loop, not at the start, since the initial call with the double arguments already retrieves the first value before the loop.
Code:
$input = file_get_contents("input.csv");
$delimiters = ",\n\r";
$token = strtok($input, $delimiters);
$result = [];
$row = [];
while ($token){
echo "Token $token <br>";
$row[] = $token;
if (count($row) == 6) { // write record
$result[] = $row;
$row = [];
}
$token = str_replace('\r', '', strtok($delimiters));
}
print_r($result);
Note that this does not create an associative array. If you need that, then use this code:
$columns = ['last', 'first', 'address1', 'address2', 'address3', 'zip'];
and then in your loop, replace $row[] = $token by:
$row[$columns[count($row)]] = $token;
You can see that version run on eval.in. The output for the data you provided in comments is:
Array (
[0] => Array (
[last] => SELBY
[first] => AARON
[address1] => 1519 Santiago de los Caballeros Loop
[address2] => Mwene-Ditu
[address3] => East Kasai
[zip] => 22025
)
[1] => Array (
[last] => GOOCH
[first] => ADAM
[address1] => 230 Urawa Drive
[address2] => Adoni
[address3] => Andhra Pradesh
[zip] => 2738
)
)
Again, this is not advisable. You should use fgetcsv. That also deals better with strings that could have commas, double quotes or even newlines in them.
Well, I was going to skip this question because fgetcsv(), but I was bored:
$lines = file($inputFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$delimiters = ",";
foreach($lines as $line) {
$values = array(strtok($line, $delimiters));
while($token = strtok($delimiters)){
$values[] = $token;
}
$result[] = $values;
}
Read the file lines into an array
Loop to get each line and put the first token of the line into a values array
Loop the line and get all tokens and add to values array
Add values array to result array
I added an array_combine() because you said something about an associative array. You can use something like this if needed:
$result[] = array_combine(array('last name',
'first name',
'address',
'city',
'district',
'postal code'), $values);
If you wanted last name to be the key for each result line, which is not advisable as keys are unique and I don't think you can guarantee last names being unique:
$result[$values[0]] = $values;
//or to remove it from the array but use as the key
$result[array_unshift($values)] = $values;
I am working with an problem that csv file may contain heading so, how can we skip that heading or extra information and skip to main data(csv data)
CSV Data may like :
**Heading 1**
This is some extra text before Data
date: xx-xx-xxxx
country data: A,B,C
*Then here starts the Data(comma separated values)*
Col1,col2,col3,
dataCol1,datacol2,datacol3 ....
so how can we skip to main data and process that data
any help will be appreciated.. as i tried but cant fix it
Thanks
This is by no means a perfect solution, because there are some unknowns in your question - therefore I have to make an of assumption: there will be more rows of csv column data than metadata/header lines. In order to assist in this heuristic, we will also exclude all "empty" lines.
If we can make this assumption then we can do something like the following:
<?php
// define filepath... optionally validate
// with `is_file()` and `is_writable()`
$file = __DIR__ . '/data.csv';
// create an SplFileObject
$csv = new SplFileObject($file);
// set some flags to read file transparently
// as a csv. drop `SKIP_EMPTY` will ignore all
// blank lines as specified above
$csv->setFlags(
SplFileObject::DROP_NEW_LINE |
SplFileObject::READ_AHEAD |
SplFileObject::SKIP_EMPTY |
SplFileObject::READ_CSV
);
// init an empty array to store rows
$rows = [];
// an `SplFileObject` allows iteration
// over its contents with `foreach`.
foreach ($csv as $row) {
// stash each row into a sub-array
// indexed by its length (number of columns)
$rows[count($row)][] = $row;
}
// `max()` will return the biggest sub-array
// which will be the column data according
// to our assumptions stated above
$csvData = max($rows);
var_dump($csvData);
If the contents of $file contained:
**Heading 1**
This is some extra text before Data
date: xx-xx-xxxx
country data: A,B,C
*Then here starts the Data(comma separated values)*
Col1,col2,col3
dataCol1,datacol2,datacol3
dataCol1,datacol2,datacol3
dataCol1,datacol2,datacol3
We should expect the following result:
Array
(
[0] => Array
(
[0] => country data: A
[1] => B
[2] => C
)
[1] => Array
(
[0] => Col1
[1] => col2
[2] => col3
)
[2] => Array
(
[0] => dataCol1
[1] => datacol2
[2] => datacol3
)
[3] => Array
(
[0] => dataCol1
[1] => datacol2
[2] => datacol3
)
[4] => Array
(
[0] => dataCol1
[1] => datacol2
[2] => datacol3
)
)
Which looks pretty good - except... the line country data: A,B,C has been parsed as a valid row, because it also includes two commas. That's the problem with trying to program heuristically. I don't know if this will be a problem in your specific use case. If so, some refinement of the above approach might be required.
References:
SplFileObject()
SplFileObject::setFlags()
max()
Hope this helps :)
You need to find some kind of delimiter or pattern you can use to indicate where the data starts. For example:
Do you know if the header will always be a certain number of lines?
Will it always have a blank line after it like in your example?
etc.
Once you know that, you can test to see if the file contains that pattern/delimiter, and then skip to the csv part.
thanks, but it is uncertain, heading section may be or not present... – Abzkn
That's the trick - you need to find out what conditions would be there if it was present. Then test for those conditions. For example, if you know the header is always 4 lines and the next one is a blank line, you could do something like this:
<?php
$f = file_get_contents($filename); //get everything in the file being processed
$file_lines = explode("\n", $f); //break up each line into an array we can process
$start_line = 0; //assume the header is not present and we'll start processing from line 1
if($file_lines[4] == ''){
//header is present, so start processing from line 5
$start_line = 5;
}
for($l = $start_line;$l < count($file_lines;$l++){
//process each line
}
?>
In CakePHP, with the following code i am trying to Export users email in CSV.
I am getting Errors.
Code Refrence site
Error:
Notice (8): Undefined offset: 0 [APP\View\Frontusers\admin_exportemails.ctp, line 12]
Warning (2): fputcsv() expects parameter 2 to be array, null given [APP\View\Helper\CsvHelper.php, line 36]
Notice (8): Undefined offset: 1 [APP\View\Frontusers\admin_exportemails.ctp, line 12]
Warning (2): fputcsv() expects parameter 2 to be array, null given [APP\View\Helper\CsvHelper.php, line 36]
admin_exportemails.ctp
$line= $useremails[0]['Frontuser'];
$this->CSV->addRow(array_keys($line));
foreach ($useremails as $key => $useremail)
{
$line =$useremail[$key]['Frontuser'];
$this->CSV->addRow($line);
}
$filename='useremails';
echo $this->CSV->render($filename);
You're messing up your foreach. You split it up in the $key and the $useremail sub-array, which is OK. But then you iterate over it and try to access $useremail[$key]['Frontuser'] again, which is nonexistent at that point.
foreach ($useremails as $key => $useremail)
This causes the [0] and [1] in the original $useremails array to be set as $key, but you iterate over all the items over the $useremails, so you can simply:
$line = $useremail['Frontuser'];
You don't need the $key, since that's not part of the iterated item, e.g. the first time your foreach runs, it sees this:
[Frontuser] => Array
(
[name] => Rash
[email] => rash.com
)
And on the second iteration it sees this:
[Frontuser] => Array
(
[name] => John
[email] => john#gmail.com
)
So there is no [0] or [1] index anymore.
$useremail[$key]['Frontuser'];
should be
$useremail['Frontuser'];
Actually there's no need for your code to include the key in the foreach loop at all.
That's PHP 101, so for further information please refer to the manual: http://php.net/foreach
You can simply pass your data array in that function and your csv generated in webroot folder.
Note:- 1. You should put blank csv file at webroot folder.
2. you should store all information in a sub array which contain only values not Model index.
function generate_csv($data_array=array()){
// pr($data_array);die;
foreach ($data_array as $key => $value) {
$newdata[] = $key.','.$value;
}
// pr($newdata);die;
$f = fopen(APP.'webroot/csv_file.csv', 'w+');
foreach ($newdata as $line) {
fputcsv($f, array($line), ',');
}
fseek($f, 0);
fclose($f);
}
you made mistake in the for loop and iterated it in wrong way.just ommit the below line from the foreach loop.
$line =$useremail[$key]['Frontuser'];
Here have tow options
1. save csv file in folder not sent output to browser and
2. send output to browser
function generate_csv($data_array = array()) {
//$f = fopen(APP . 'webroot/files/unsaved_bars.csv', 'w+'); // for save csv file in folder not sent output to browser
$f = fopen("php://output", "a"); // for send output to browser
$headers[] = array('id','name','other_info'); // for header row
$data_array = array_merge($headers, $data_array);
foreach ($data_array as $line) {
fputcsv($f, $line, ',');
}
//fseek($f, 0);
fclose($f);
}
I'm currently stuck trying to figure out how to sort my array files. I have a simple news posting system that stores the content in seperate .dat files and then stores them in an array. I numbered the files so that my array can sort them from lowest number to greatest; however, I have run into a small problem. To begin here is some more information on my system so that you can understand it better.
The function that gathers my files is:
function getNewsList() {
$fileList = array();
// Open the actual directory
if($handle = opendir(ABSPATH . ADMIN . "data")) {
// Read all file from the actual directory
while($file = readdir($handle)) {
if(!is_dir($file)) {
$fileList[] = $file;
}
}
}
// Return the array.
return $fileList;
}
On a seperate file is the programming that processes the news post. I didn't post that code for simplicity's sake but I will explain how the files are named. The files are numbered and the part of the post's title is used... for the numbering I get a count of the array and add "1" as an offset. I get the title of the post, encode it to make it file-name-friendly and limit the amount of text so by the end of it all I end up with:
// Make the variable that names the file that will contain
// the post.
$filename = "00{$newnumrows}_{$snipEncode}";
When running print_r on the above function I get:
Array (
[0] => 0010_Mira_mi_Soledad.dat
[1] => 0011_WOah.dat
[2] => 0012_Sinep.dat
[3] => 0013_Living_in_Warfa.dat
[4] => 0014_Hello.dat
[5] => 001_AS.dat
[6] => 002_ASASA.dat
[7] => 003_SSASAS.dat
...
[13] => 009_ASADADASADAFDAF.dat
)
And this is how my content is displayed. For some reason according to the array sorting 0010 comes before 001...? Is there a way I can get my array to sort 001 before 0010?
You can use natcasesort(array) function of php which will sort an array using a "natural order" algorithm and you will get the desired output
HTH.
:Malay
Take the filename and extract the prefix number as integer number:
// $filename has the format: "00{$newnumrows}_{$snipEncode}"
function generateSortKey($filename)
{
$separatorPos = stripos($filename, '_');
$prefix = trim(substr($filename, 0, $separatorPos));
return intval($prefix);
}
Than create an associative array from the list of files, the keys will be used as sortable value later:
function toSortableArray($files)
{
$result = array();
foreach ($files as $filename)
{
$key = generateSortKey($filename);
$value = $filename;
$result[$key] = $value;
}
return $result;
}
and at last use krsort():
$list = getNewsList();
$sortableList = toSortableArray($list);
krsort($sortableList); // after that $sortableList is
// sorted by key in descending order now
FIX: ksort() => krsort()
The issue is with underscore. Always numerical characters get sorted before underscore.
See whether you get the desired result using sort($your_array, SORT_NUMERIC).
For more info, refer PHP Manual for sort
You may also use natcasesort() (as Malay suggested) or natsort(). But both maintain index association.