If you need to set auto_detect_line_endings to deal with Mac line endings, it may seem obvious but remember it should be set before fopen, not after:
This will work:
<?php
ini_set('auto_detect_line_endings',TRUE);
$handle = fopen('/path/to/file','r');
while ( ($data = fgetcsv($handle) ) !== FALSE ) {
//process
}
ini_set('auto_detect_line_endings',FALSE);
?>
This won't, you will still get concatenated fields at the new line position:
<?php
$handle = fopen('/path/to/file','r');
ini_set('auto_detect_line_endings',TRUE);
while ( ($data = fgetcsv($handle) ) !== FALSE ) {
//process
}
ini_set('auto_detect_line_endings',FALSE);
?>
fgetcsv
(PHP 4, PHP 5)
fgetcsv — Gets line from file pointer and parse for CSV fields
Description
Similar to fgets() except that fgetcsv() parses the line it reads for fields in CSV format and returns an array containing the fields read.
Parameters
- handle
-
A valid file pointer to a file successfully opened by fopen(), popen(), or fsockopen().
- length
-
Must be greater than the longest line (in characters) to be found in the CSV file (allowing for trailing line-end characters). It became optional in PHP 5. Omitting this parameter (or setting it to 0 in PHP 5.0.4 and later) the maximum line length is not limited, which is slightly slower.
- delimiter
-
Set the field delimiter (one character only). Defaults as a comma.
- enclosure
-
Set the field enclosure character (one character only). Defaults as a double quotation mark.
- escape
-
Set the escape character (one character only). Defaults as a backslash (\)
Return Values
Returns an indexed array containing the fields read.
Note: A blank line in a CSV file will be returned as an array comprising a single null field, and will not be treated as an error.
Note: If PHP is not properly recognizing the line endings when reading files either on or created by a Macintosh computer, enabling the auto_detect_line_endings run-time configuration option may help resolve the problem.
fgetcsv() returns FALSE on error, including end of file.
ChangeLog
| Version | Description |
|---|---|
| 5.3.0 | The escape parameter was added |
| 4.3.5 | fgetcsv() is now binary safe |
| 4.3.0 | The enclosure parameter was added |
Examples
Example #1 Read and print the entire contents of a CSV file
<?php
$row = 1;
$handle = fopen("test.csv", "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
echo "<p> $num fields in line $row: <br /></p>\n";
$row++;
for ($c=0; $c < $num; $c++) {
echo $data[$c] . "<br />\n";
}
}
fclose($handle);
?>
Notes
Note: Locale setting is taken into account by this function. If LANG is e.g. en_US.UTF-8, files in one-byte encoding are read wrong by this function.
fgetcsv
24-Nov-2008 07:19
27-Oct-2008 02:51
In my opinion, the two examples that use the SPL Iterator Interface don't work as they should. If you call several times to the method current() then it eventually will reach the eof...
<?php
public function current() {
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
$this->rowCounter++;
return $this->currentElement;
}
?>
If you want to go next, you need to call the method next(). So here is my solution...
<?php
/**
* @author cizar@nixar.org
*/
class CsvIterator implements Iterator
{
const DEFAULT_DELIMITER = ';';
const DEFAULT_LENGTH = 4096;
/**
* The CSV file handler.
*
* @var resource
* @access private
*/
private $_handler = null;
/**
* The delimiter of the CSV file.
*
* @var string
* @access private
*/
private $_delimiter = null;
/**
* The dafs
*
* @var integer
* @access private
*/
private $_length = null;
/**
* The row counter.
*
* @var integer
* @access private
*/
private $_key = null;
/**
* The element that will be returned on each iteration.
*
* @var mixed
* @access private
*/
private $_current = null;
/**
* This is the constructor. It try to open the CSV file.
*
* @access public
* @param string $filename The fullpath of the CSV file.
* @param string $delimiter The delimiter.
* @param integer $length The amount of bytes to be read on each iteration.
*
* @throws Exception
*/
public function __construct ($filename, $delimiter = self::DEFAULT_DELIMITER, $length = self::DEFAULT_LENGTH)
{
if (($this->_handler = fopen($filename, 'r')) === false) {
throw new Exception("The file '$filename' cannot be opened");
}
$this->_delimiter = $delimiter;
$this->_length = $length;
}
/**
* This is the destructor. It close the CSV file.
*
* @access public
*/
public function __destruct ()
{
fclose($this->_handler);
}
/**
* This method move the file pointer to the next row.
*
* @access public
*/
public function next ()
{
$this->_read();
$this->_key += 1;
}
/**
* This method reset the file handler.
*
* @access public
*/
public function rewind ()
{
rewind($this->_handler);
$this->_read();
$this->_key = 0;
}
/**
* This method returns the current row number.
*
* @access public
*/
public function key ()
{
return $this->_key;
}
/**
* This methods return the current CSV row data.
*
* @access public
* @return array The row as an one-dimensional array
*/
public function current ()
{
return $this->_current;
}
/**
* This method checks if the current row is readable.
*
* @access public
* @return boolean If the current row is readable.
*/
public function valid ()
{
return $this->_current !== FALSE;
}
/**
* This method read the next row of the CSV file.
*
* @access private
*/
private function _read ()
{
$this->_current = fgetcsv($this->_handler, $this->_length, $this->_delimiter);
}
}
09-Oct-2008 06:12
Here's something I put together this morning. It allows you to read rows from your CSV and get values based on the name of the column. This works great when your header columns are not always in the same order; like when you're processing many feeds from different customers. Also makes for cleaner, easier to manage code.
So if your feed looks like this:
product_id,category_name,price,brand_name, sku_isbn_upc,image_url,landing_url,title,description
123,Test Category,12.50,No Brand,0,http://www.paperpear.com, http://www.paperpear.com/landing.php, Some Title,Some Description
You can do:
<?php
while ($o->getNext())
{
$dPrice = $o->getPrice();
$nProductID = $o->getProductID();
$sBrandName = $o->getBrandName();
}
?>
If you have any questions or comments regarding this class, they can be directed to michael.martinek@gmail.com as I probably won't be checking back here.
<?php
define('C_PPCSV_HEADER_RAW', 0);
define('C_PPCSV_HEADER_NICE', 1);
class PaperPear_CSVParser
{
private $m_saHeader = array();
private $m_sFileName = '';
private $m_fp = false;
private $m_naHeaderMap = array();
private $m_saValues = array();
function __construct($sFileName)
{
//quick and dirty opening and processing.. you may wish to clean this up
if ($this->m_fp = fopen($sFileName, 'r'))
{
$this->processHeader();
}
}
function __call($sMethodName, $saArgs)
{
//check to see if this is a set() or get() request, and extract the name
if (preg_match("/[sg]et(.*)/", $sMethodName, $saFound))
{
//convert the name portion of the [gs]et to uppercase for header checking
$sName = strtoupper($saFound[1]);
//see if the entry exists in our named header-> index mapping
if (array_key_exists($sName, $this->m_naHeaderMap))
{
//it does.. so consult the header map for which index this header controls
$nIndex = $this->m_naHeaderMap[$sName];
if ($sMethodName{0} == 'g')
{
//return the value stored in the index associated with this name
return $this->m_saValues[$nIndex];
}
else
{
//set the valuw
$this->m_saValues[$nIndex] = $saArgs[0];
return true;
}
}
}
//nothing we control so bail out with a false
return false;
}
//get a nicely formatted header name. This will take product_id and make
//it PRODUCTID in the header map. So now you won't need to worry about whether you need
//to do a getProductID, or getproductid, or getProductId.. all will work.
public static function GetNiceHeaderName($sName)
{
return strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $sName));
}
//process the header entry so we can map our named header fields to a numerical index, which
//we'll use when we use fgetcsv().
private function processHeader()
{
$sLine = fgets($this->m_fp);
//you'll want to make this configurable
$saFields = split(",", $sLine);
$nIndex = 0;
foreach ($saFields as $sField)
{
//get the nice name to use for "get" and "set".
$sField = trim($sField);
$sNiceName = PaperPear_CSVParser::GetNiceHeaderName($sField);
//track correlation of raw -> nice name so we don't have to do on-the-fly nice name checks
$this->m_saHeader[$nIndex] = array(C_PPCSV_HEADER_RAW => $sField, C_PPCSV_HEADER_NICE => $sNiceName);
$this->m_naHeaderMap[$sNiceName] = $nIndex;
$nIndex++;
}
}
//read the next CSV entry
public function getNext()
{
//this is a basic read, you will likely want to change this to accomodate what
//you are using for CSV parameters (tabs, encapsulation, etc).
if (($saValues = fgetcsv($this->m_fp)) !== false)
{
$this->m_saValues = $saValues;
return true;
}
return false;
}
}
//quick example of usage
$o = new PaperPear_CSVParser('F:\foo.csv');
while ($o->getNext())
{
echo "Price=" . $o->getPrice() . "\r\n";
}
?>
09-Sep-2008 01:35
Smarter/better fgetcsv functions, acceptying multiple-character delim, enclosure, and escape.
<?php
define('BUFFER_READ_LEN', 4096);
function fgetcsv_ex($file_handle, $delim = ',', $enclosure = '"', $escape = '"') {
$fields = null;
$fldCount = 0;
$inQuotes = false;
$complete = false;
$search_chars_list = array('\r\n', '\n', '\r');
if ( $delim && ($delim != '') )
$search_chars_list[] = $delim;
if ( $enclosure && ($enclosure != '') )
{
$search_chars_list[] = $enclosure;
$enclosure_len = strlen($enclosure);
}
else
$enclosure_len = 0;
if ( $escape && ($escape != '') )
{
$search_chars_list[] = $escape;
$escape_len = strlen($escape);
}
else
$escape_len = 0;
$search_regex = '/' . implode('|', $search_chars_list) . '/';
$cur_pos = 0;
$line = '';
$cur_value = '';
$in_value = false;
$last_value = 0;
while ( ! $complete )
{
$read_result = fread($file_handle, BUFFER_READ_LEN);
if ( $read_result )
$line .= $read_result;
else if ( strlen($line) == 0 )
return null;
else
$line .= "\n";
$line_len = strlen($line);
while ( true )
{
if (! preg_match($search_regex, $line, $matches, PREG_OFFSET_CAPTURE, $cur_pos))
{
if ($read_result) //need more chars
break;
else
return null; //Incomplete file
}
else
{
$non_escape = false;
$cur_char = $matches[0][0];
$cur_len = strlen($cur_char);
$new_pos = $matches[0][1];
if (($enclosure == $escape) && $in_value && ($cur_char == $escape)) //Escape char = enclosure char special handling
{
if (($new_pos + $cur_len + $enclosure_len) >= $line_len) //We need the next char
break;
$next_char = substr($line, $new_pos + $cur_len, $enclosure_len);
if ( (! $enclosure) || ($next_char != $enclosure) )
$non_escape = true;
}
$cur_pos = $new_pos;
if ( $in_value && (! $non_escape) )
{
$cur_value .= mb_substr($line, $last_value, $cur_pos - $last_value);
if ($cur_char == $escape) //Skip escape char
$cur_pos += $escape_len;
$last_value = $cur_pos;
}
else if ( ( $cur_char == "\n" ) || ( $cur_char == "\r" ) || ( $cur_char == "\r\n" ) )
{
$blank_start_lines = ( $cur_pos == 0 );
++$cur_pos;
$cur_pos = $cur_pos + strspn($line, "\n\r", $cur_pos);
if ( ! $blank_start_lines )
{
$complete = true;
break;
}
else
{
$last_value = $cur_pos;
continue;
}
}
else if ( $cur_char == $delim )
{
if ( is_null($fields) )
$fields = array();
$fields[] = $cur_value . trim(mb_substr($line, $last_value, $cur_pos - $last_value));
$last_value = $cur_pos + $cur_len;
$cur_value = '';
}
else if ( $cur_char == $enclosure )
{
if ( $in_value )
$cur_value .= mb_substr($line, $last_value, $cur_pos - $last_value);
$last_value = $cur_pos + $cur_len;
$in_value = ! $in_value;
}
$cur_pos += $cur_len;
}
}
}
fseek($file_handle, $cur_pos - strlen($line), SEEK_CUR);
return $fields;
}
?>
28-Aug-2008 12:45
Here is another function that splits the rows of a CSV file and uses a comma as its delimiter. The function also ignores commas inside of double quotations and removes any double quotes and ultimately prints out the data into a table. If you need to keep the double quotes you'll need to edit the code.
The function takes an array as an argument. The array I pass into the function contains the display name and CSV file name.
Enjoy!
<?php
/**
* Function to print out data from an uploaded CSV file into a table.
*
* @param array $data
* @return
*/
function printCSV ($data) {
if (is_array($data)) {
// open the csv file and read it
$dataFile = 'documents/courses/' . $data['course_file']; // change per setup - $data['course_file'] = CSV file name
$open = fopen($dataFile, 'r');
$csv = fread($open, filesize($dataFile));
print "<h2>" . $data['name'] . "</h2>"; // optional
// split up the rows
$csv = preg_split('/[\r\n]/', $csv);
$csvData = array();
$i = 0;
foreach ($csv as $line) {
// splits lines with a comma but ignores a comma wrapped with double quotes
$csvData[$i] = preg_split('/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/', $line);
$i++;
}
print "<table width='60%' cellpadding='0' cellspacing='0' class='course-table'>";
for ($j = 0; $j < count($csvData); $j++) {
// print the first line with <th> tags
if ($csvData && $j == 0) {
print "<tr>";
for ($k = 0; $k < count($csvData[$j]); $k++) {
// remove the double quotes
if (preg_match('/\"/', $csvData[$j][$k])) {
print "<th>" . trim($csvData[$j][$k], '"') . "</th>"; // edit if you wish to keep the double quotes
} else {
print "<th>" . $csvData[$j][$k] . "</th>";
}
}
print "</tr>";
// print the rest with <td> tags
} else {
print "<tr>";
for ($k = 0; $k < count($csvData[$j]); $k++) {
// remove the double quotes
if (preg_match('/\"/', $csvData[$j][$k])) {
print "<td>" . trim($csvData[$j][$k], '"') . "</td>"; // edit if you wish to keep the double quotes
} else {
print "<td>" . $csvData[$j][$k] . "</td>";
}
}
print "</tr>";
}
}
print "</table>";
} else {
return false;
}
}
?>
01-Aug-2008 02:03
If you want to get your CSV rows (in this case a file posted from a form) into one big array, you can use this code. You may have to play around with the ord() and explode separators depending on your line endings. You can change to the commented out explode to use " delimiter.
<?php
if( $_FILES['file']['tmp_name'] )
{
$csv_rows=Array();
$csv="";
$handle = fopen($_FILES['file']['tmp_name'], "r");
//load char by char, to replace line endings
while($data = fgetc($handle))
{
if(ord($data)==13)
{
$csv.="\r\n";
}
else
{
$csv.=$data;
}
}
fclose($handle);
$csv_lines=explode("\r\n", $csv);
foreach($csv_lines as $line)
{
$csv_rows[]=explode(",", $line);
//or ltrim(rtrim(explode('","', $line),'"'),'"') for delimited fields
}
}
?>
11-Jul-2008 05:40
I used this simple php code for reading a csv file with two columns (without column heads) and generating an associative array out of it...
<?php
#reads the file data.csv
$data=file("data.csv", FILE_IGNORE_NEW_LINES);
#explodes each line contents and assigns the values to an array
foreach ($data as $line)
{
$mparts=explode(',',$line);
#pushes the array values in to an associative array
$dataarray[$mparts[0]]=$mparts[1];
}
#prints the values of the new associative array
print_r($dataarray);
?>
30-May-2008 04:33
Hey everybody...
I know that a lot of people have written code to parse CSVs etc... I have written one which I believe is a lot smaller and faster than those listed here...
<?php
/*
class.parser.php
Dynamically parses a CSV file and returns the values as array for processing.
*/
class parser {
function doParse($csvFile,$sep) {
$csvFile = file($csvFile);
foreach ($csvFile as $key=>$value) {
$v = explode($sep,$value);
foreach ($v as $kk=>$lineItem) {
$csv[$key][$kk] = trim(trim($lineItem),"\"");
}
}
return $csv; // an associative array of the csv.
}
function dumpCSV($data,$sep,$heading = "") {
/*
build the "csv" from a 2 dimensional array
$result = $parser->dumpCSV($data,",",$heading);
where $data is an array formed similar to doParse (see above), and $heading
is the heading line for the CSV (titles etc).... Something like
$heading = "\"Booking Number\",\"Booking Date\"\n";
if you do not want/need a headling line, do not include $heading
*/
unset($message);
$message[] = $heading;
$x = 0;
foreach ($data as $key=>$v) {
unset($tempmsg);
foreach ($v as $item) {
$tempmsg .= "\"".$item."\"".$sep;
}
$message[$x] = trim($tempmsg,","); // gets rid of excess , @ the end of each line.
$x++;
}
//print_r($message);
// calculate accurate file size for the "downloaded file"
foreach ($message as $line) {
$bytes .= strlen($line);
}
return $message;
}
function downloadCSV($data,$filename) {
header ("Content-Type: application/vnd.ms-excel");
header ("Content-disposition: attachment; filename=\"".$filename.".csv\"");
header ("Content-length: $bytes");
foreach ($data as $line) {
echo $line."\n";
}
}
function searchCSVKey($data,$searchkey) {
foreach ($data as $key=>$v) {
foreach ($v as $item) {
if ($item == $searchkey) {
$returnvalue = $data[$key];
$returnvalue['line'] = $key;
break 2;
}
}
}
if ($returnvalue == "") {
$returnvalue['0'] = "NULL";
$returnvalue['line'] = 0;
}
return $returnvalue;
}
function getPartialCSVAlpha($data,$start) {
// gets from $start to the end of the CSV. useful for searching
for ($x = $start + 1; $x <= count($data); $x++) {
$d[] = $data[$x];
}
return $d;
}
function getPartialCSVOmega($data,$finish) {
// 1 to the $finish of the CSV and return
for ($x = 0; $x < $finish; $x++) {
$d[] = $data[$x];
}
return $d;
}
function getSection($data,$startSearch,$finishSearch) {
$r1 = $this->searchCSVKey($data,$startSearch);
$data = $this->getPartialCSVAlpha($data,$r1['line']); // gets rid of the first section not needed.
$r2 = $this->searchCSVKey($data,$finishSearch);
$data = $this->getPartialCSVOmega($data,$r2['line']); // sections down the CSV.
return $data;
}
function getSectionByLine($data,$start,$finish) {
for ($x = $start; $x <= $finish; $x++) {
$output[] = $data[$x];
}
return $output;
}
function addValues3($array1,$array2) {
foreach ($array1 as $key1=>$val1) {
foreach ($val1 as $i=>$v) {
if ((is_numeric($array1[$key1][$i]) == TRUE) && ($i > 1)) {
$output[$key1][$i] = floatval($array2[$key1][$i]) + floatval($array1[$key1][$i]);
} else {
$output[$key1][$i] = $array1[$key1][$i];
}
}
}
return $output;
}
}
// </eof> //
?>
Hope it helps all :)
24-May-2008 09:59
i wrote a decent litte class which is able to import excel 2003 .csv files kickass fast and easy. havent tested it with other excel versions, but they should work as well.
<?php
class read_csv {
function read_csv () {
// nothing
}
function read_csv_run($f="") {
if ( $f AND is_file($f) ) {
// set excel type delimiter, etc
$delimiter = ';';
$enclosure = '"';
// read file & parse
$input = file($f);
$csv = array();
foreach ( $input as $key => $value ) {
// rtrim crap at the end of the string
$tmp = explode($delimiter,rtrim($value));
// parse
$in_quote = false;
$arr = array();
foreach ( $tmp as $key => $value ) {
if ( $in_quote ) {
if ( $this->read_csv_has_quote($value,$enclosure) ) {
$in_quote = false;
$value = substr_replace($value,'',-1,1);
}
$key = (count($arr)-1);
$arr[$key] .= $delimiter.$value; // continue last array element
} else {
if ( $this->read_csv_has_quote($value,$enclosure) ) {
$in_quote = true;
$value = substr_replace($value,'',0,1);
} else if ( substr($value,0,1) == $enclosure AND substr($value,-1,1) == $enclosure ) {
// string is quoted, remove quotes
$value = substr_replace($value,'',0,1); // start
$value = substr_replace($value,'',-1,1); // end
}
$arr[] = $value; // append to array
}
}
foreach ( $arr as $key => $value ) {
$arr[$key] = str_replace($enclosure.$enclosure,$enclosure,$value);
}
// append to array
$csv[] = $arr;
} // end foreach
echo nl2br(print_r($csv,1));
} // end if
} // end func
function read_csv_has_quote ($str="",$enc="") {
$c = substr_count($str,$enc);
if ( stristr(($c/2),".") ) {
return true;
}
}
} // end class
$csv =& new read_csv();
$csv->read_csv_run("katalog_excel_D.csv");
?>
mfg RR
01-May-2008 03:55
The array_flip() function is handy for converting column names to column numbers. Assuming the first row contains column names, you can simply read it via fgetcsv(); this will give you a number-indexed array of column names. Applying array_flip() converts that into a name-indexed array of column numbers.
The following example does this, and assumes that two of the columns are named "animal" and "sound" but does not make any assumption about where those columns are.
$fp = fopen($url, "r");
$names = array_flip(fgetcsv($fp, 1000));
while (($values = fgetcsv($fp, 1000)) !== FALSE) {
print "The ".$values[$names["animal"]]." says ".$values[$names["sound"]].".\n";
}
fclose($fp);
19-Apr-2008 08:31
With this modification the last item will be added to the array: "a","b","c" is transformed to array("a","b","c") - old function returned array("a","b")
<?php
/*
Modified function from user comment by Marcos Boyington / 06-Mar-2008 03:08
This is a pretty useful update/modification to the fgetcsv function, which allows for:
* Multiple-character/multibyte delim/enclosure/escape
* Multibyte values
* Escape character specification in < PHP5
* Escape character = delim character
* Direct reading from files without bloating memory too much
*/
define('BUFFER_READ_LEN', 4096);
function fgetcsv_ex($file_handle, $delim = ',', $enclosure = '"', 