silverstripe-framework/dev/CSVParser.php
Sean Harvey d838529953 BUGFIX Object subclasses with a constructor that didn't already will now call parent to respect inheritance
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.3@83587 467b73ca-7a2a-4603-9d3b-597d59a354a9
2011-02-02 14:27:31 +13:00

237 lines
5.4 KiB
PHP

<?php
/**
* Class to handle parsing of CSV files, where the column headers are in the first row.
* The idea is that you pass it another object to handle the actual procesing of the data in the CSV file.
*
* Usage:
* <code>
* $parser = new CSVParser('myfile.csv');
* $parser->mapColumns(
* 'first name' => 'FirstName'
* 'lastname' => 'Surname',
* 'last name' => 'Surname'
* ));
* foreach($parser as $row) {
* // $row is a map of column name => column value
* $obj = new MyDataObject();
* $obj->update($row);
* $obj->write();
* }
* </code>
*
* @package sapphire
* @subpackage bulkloading
*/
class CSVParser extends Object implements Iterator {
protected $filename;
protected $fileHandle;
/**
* Map of source columns to output columns
* Once they get into this variable, all of the source columns are in lowercase
*/
protected $columnMap = array();
/**
* The header row used to map data in the CSV file
* To begin with, this is null. Once it has been set, data will get returned from the CSV file
*/
protected $headerRow = null;
/**
* A custom header row provided by the caller
*/
protected $providedHeaderRow = null;
/**
* The data of the current row
*/
protected $currentRow = null;
/**
* The current row number
* 1 is the first data row in the CSV file; the header row, if it exists, is ignored
*/
protected $rowNum = 0;
/**
* The character for separating columns
*/
protected $delimiter = ",";
/**
* The character for quoting colums
*/
protected $enclosure = '"';
/**
* Open a CSV file for parsing.
* You can use the object returned in a foreach loop to extract the data
* @param $filename The name of the file. If relative, it will be relative to the site's base dir
* @param $delimiter The character for seperating columns
* @param $enclosure The character for quoting or enclosing columns
*/
function __construct($filename, $delimiter = ",", $enclosure = '"') {
$filename = Director::getAbsFile($filename);
$this->filename = $filename;
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
parent::__construct();
}
/**
* Re-map columns in the CSV file.
* This can be useful for identifying synonyms in the file
* For example:
* <code>
* $csv->mapColumns(array(
* 'firstname' => 'FirstName',
* 'last name' => 'Surname',
* ));
* </code>
*/
function mapColumns($columnMap) {
if($columnMap) {
$lowerColumnMap = array();
foreach($columnMap as $k => $v) {
$lowerColumnMap[strtolower($k)] = $v;
}
$this->columnMap = array_merge($this->columnMap, $lowerColumnMap);
}
}
/**
* If your CSV file doesn't have a header row, then you can call this function to provide one.
* If you call this function, then the first row of the CSV will be included in the data returned.
*/
function provideHeaderRow($headerRow) {
$this->providedHeaderRow = $headerRow;
}
/**
* Open the CSV file for reading
*/
protected function openFile() {
ini_set('auto_detect_line_endings',1);
$this->fileHandle = fopen($this->filename,'r');
if($this->providedHeaderRow) {
$this->headerRow = $this->remapHeader($this->providedHeaderRow);
}
}
/**
* Close the CSV file and re-set all of the internal variables
*/
protected function closeFile() {
if($this->fileHandle) fclose($this->fileHandle);
$this->fileHandle = null;
$this->rowNum = 0;
$this->currentRow = null;
$this->headerRow = null;
}
/**
* Get a header row from the CSV file
*/
protected function fetchCSVHeader() {
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
$this->headerRow = $this->remapHeader($srcRow);
}
/**
* Map the contents of a header array using $this->mappedColumns
*/
protected function remapHeader($header) {
$mappedHeader = array();
foreach($header as $item) {
if(isset($this->columnMap[strtolower($item)])) $item = $this->columnMap[strtolower($item)];
$mappedHeader[] = $item;
}
return $mappedHeader;
}
/**
* Get a row from the CSV file and update $this->currentRow;
*/
protected function fetchCSVRow() {
if(!$this->fileHandle) $this->openFile();
if(!$this->headerRow) $this->fetchCSVHeader();
$this->rowNum++;
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
if($srcRow) {
$row = array();
foreach($srcRow as $i => $value) {
// Allow escaping of quotes and commas in the data
$value = str_replace(
array('\\'.$this->enclosure,'\\'.$this->delimiter),
array($this->enclosure,$this->delimiter),$value);
if(array_key_exists($i, $this->headerRow)) {
if($this->headerRow[$i]) $row[$this->headerRow[$i]] = $value;
} else {
user_error("No heading for column $i on row $this->rowNum", E_USER_WARNING);
}
}
$this->currentRow = $row;
} else {
$this->closeFile();
}
return $this->currentRow;
}
/**
* @ignore
*/
function __destruct() {
$this->closeFile();
}
//// ITERATOR FUNCTIONS
/**
* @ignore
*/
function rewind() {
$this->closeFile();
$this->fetchCSVRow();
}
/**
* @ignore
*/
function current() {
return $this->currentRow;
}
/**
* @ignore
*/
function key() {
return $this->rowNum;
}
/**
* @ignore
*/
function next() {
$this->fetchCSVRow();
return $this->currentRow;
}
/**
* @ignore
*/
function valid() {
return $this->currentRow ? true : false;
}
}
?>