Source for file CSV.php
Documentation is available at CSV.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Tomas Von Veschler Cox |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Tomas V.V.Cox <cox@idecnet.com> |
// +----------------------------------------------------------------------+
// $Id: CSV.php,v 1.13 2003/01/04 11:54:55 mj Exp $
* File class for handling CSV files (Comma Separated Values), a common format
* - Usage example and Doc
* - Use getPointer() in discoverFormat
* - Add a line counter for being able to output better error reports
* - Store the last error in GLOBALS and add File_CSV::getLastError()
* - Support Mac EOL format
* - Other methods like readAll(), writeAll(), numFields(), numRows()
* - Try to detect if a CSV has header or not in discoverFormat()
* (they has been analyzed but for the moment the impact in the speed for
* properly handle this uncommon cases is too high and won't be supported)
* - A field which is composed only by a single quoted separator (ie -> ;";";)
* is not handled properly
* - When there is exactly one field minus than the expected number and there
* is a field with a separator inside, the parser will throw the "wrong count" error
* @author Tomas V.V.Cox <cox@idecnet.com>
* This raiseError method works in a different way. It will always return
* false (an error occurred) but it will call PEAR::raiseError() before
* it. If no default PEAR global handler is set, will trigger an error.
* @param string $error The error message
* @return bool always false
// If a default PEAR Error handler is not set trigger the error
// XXX Add a PEAR::isSetHandler() method?
if ($GLOBALS['_PEAR_default_error_mode'] == PEAR_ERROR_RETURN ) {
PEAR ::raiseError ($error, null , PEAR_ERROR_TRIGGER , E_USER_WARNING );
PEAR ::raiseError ($error);
* Checks the configuration given by the user
* @param array &$conf The configuration assoc array
* @param string &$error The error will be written here if any
function _conf (&$conf, &$error)
return $error = "Invalid configuration";
if (isset ($conf['sep'])) {
if (strlen($conf['sep']) != 1 ) {
return $error = 'Separator can only be one char';
return $error = 'Missing separator (the "sep" key)';
if (!isset ($conf['fields']) || !is_numeric($conf['fields'])) {
return $error = 'The number of fields must be numeric (the "fields" key)';
if (isset ($conf['quote'])) {
if (strlen($conf['quote']) != 1 ) {
return $error = 'The quote char must be one char (the "quote" key)';
if (!isset ($conf['crlf'])) {
* Return or create the file descriptor associated with a file
* @param string $file The name of the file
* @param array &$conf The configuration
* @param string $mode The open node (ex: FILE_MODE_READ or FILE_MODE_WRITE)
* @return mixed A file resource or false
function getPointer($file, &$conf, $mode = FILE_MODE_READ )
static $resources = array ();
if (isset ($resources[$file])) {
return $resources[$file];
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
$fp = &File::_getFilePointer ($file, $mode);
PEAR ::popErrorHandling ();
if (PEAR ::isError ($fp)) {
* @param string $field The data to unquote
* @param string $quote The quote char
* @return string the unquoted data
// Incase null fields (form: ;;)
if ($quote && $field{0 } == $quote && $field{strlen($field)-1 } == $quote) {
* Reads a row of data as an array from a CSV file. It's able to
* read memo fields with multiline data.
* @param string $file The filename where to write the data
* @param array &$conf The configuration of the dest CSV
* @return mixed Array with the data read or false on error/no more data
while (($ch = fgetc($fp)) !== false ) {
if ($c != $quote && $c != $conf['sep'] && $c != "\n") {
if ($c == $quote && $quote &&
($prev == $conf['sep'] || $prev == "\n" || $prev === null ))
if ($c == $conf['sep'] && $prev == $conf['quote']) {
$sub = ($prev == "\r") ? 2 : 1;
if ((strlen($buff) >= $sub) &&
($buff{strlen($buff) - $sub} == $quote))
if (!$in_quote && ($c == $conf['sep'] || $c == "\n")) {
// More fields than expected
if (($c == $conf['sep']) && ((count($ret) + 1 ) == $f)) {
"expected ". $conf['fields']);
// Less fields than expected
if (($c == "\n") && ($i != $f)) {
"' expected ". $conf['fields']);
return !feof($fp) ? $ret : false;
* Reads a "row" from a CSV file and return it as an array
* @param string $file The CSV file
* @param array &$conf The configuration of the dest CSV
* @return mixed Array or false
function read($file, &$conf)
// The size is limited to 4K
if (!$line = fgets($fp, 4096 )) {
$fields = explode($conf['sep'], $line);
$last = & $fields[count($fields) - 1 ];
// Fallback to read the line with readQuoted when guess
// that the simple explode won't work right
if (($last{strlen($last) - 1 } == "\n"
&& $last{0 } == $conf['quote']
(count($fields) != $conf['fields'])
// XXX perhaps there is a separator inside a quoted field
//preg_match("|{$conf['quote']}.*{$conf['sep']}.*{$conf['quote']}|U", $line)
foreach ($fields as $k => $v) {
if (count($fields) != $conf['fields']) {
"' expected ". $conf['fields']);
* Internal use only, will be removed in the future
* @param string $str The string to debug
if (strpos($str, "\r") !== false ) {
if (strpos($str, "\n") !== false ) {
if (strpos($str, "\t") !== false ) {
* Writes a struc (array) in a file as CSV
* @param string $file The filename where to write the data
* @param array $fields Ordered array with the data
* @param array &$conf The configuration of the dest CSV
* @return bool True on success false otherwise
function write($file, $fields, &$conf)
if (count($fields) != $conf['fields']) {
"' expected ". $conf['fields']);
for ($i = 0; $i < count($fields); $i++ ) {
$write .= $conf['quote'] . $fields[$i] . $conf['quote'];
if ($i < (count($fields) - 1 )) {
* Discover the format of a CSV file (the number of fields, the separator
* and if it quote string fields)
* @param string the CSV file name
* @return mixed Assoc array or false
if (!$fp = @fopen($file, 'r')) {
$seps = array ("\t", ';', ':', ',');
// Take the first 10 lines and store the number of ocurrences
// for each separator in each line
for ($i = 0; ($i < 10 ) && ($line = fgets($fp, 4096 )); $i++ ) {
foreach ($seps as $sep) {
// Group the results by amount of equal ocurrences
foreach ($matches as $sep => $res) {
foreach ($res as $k => $num) {
$times[$num] = (isset ($times[$num])) ? $times[$num] + 1 : 1;
$fields[$sep] = key($times);
$amount[$sep] = $times[key($times)];
$conf['fields'] = $fields + 1;
// Test if there are fields with quotes arround in the first 5 lines
for ($i = 0; ($i < 5 ) && ($line = fgets($fp, 4096 )); $i++ ) {
if (preg_match(" |$sep([$quotes]).*([$quotes])$sep|U" , $line, $match)) {
if ($match[1 ] == $match[2 ]) {
if (preg_match(" |^([$quotes]).*([$quotes])$sep|" , $line, $match)
|| preg_match(" |([$quotes]).*([$quotes])$sep\s$|Us" , $line, $match))
if ($match[1 ] == $match[2 ]) {
// XXX What about trying to discover the "header"?
Documentation generated on Mon, 11 Mar 2019 14:12:52 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|