Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@warmans
Created June 29, 2012 19:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save warmans/3019990 to your computer and use it in GitHub Desktop.
Save warmans/3019990 to your computer and use it in GitHub Desktop.
Crap-ish CSV Parser
<?php
/**
* Map CSV columns to fields
*
* somefile.csv content = "some guy, 5 Road Street, Town, Postcode"
*
* $mapper = new CsvMapper('/tmp/somefile.csv');
* $mapper->mapField('A', 'name')->filter('name', function($value){ return ucwords($value); });
* $mapper->mapAggregateField(array('B', C', D'), 'address', ', ');
* $mapper->mapLiteral('source', 'unknown');
*
* foreach($mapper as $row):
* echo $row['name'], ' | ', $row['address'], ' | ', $row['source'];
* endforeach;
*
* //Result: Some Guy | 5 Road Street, Town, Postcode | unknown
*
*/
class CsvMapper implements Iterator {
private $_file;
private $_mapping = array();
public function __construct($filePath)
{
$this->_file = new SplFileObject($filePath);
$this->_file->setFlags(SplFileObject::READ_CSV);
}
//a single column mapping to a single field
public function mapField($column, $field)
{
$this->_mapping[] = array('column'=>(is_numeric($column) ? $column : $this->_alphaToNum($column)), 'field'=>$field, 'delimiter'=>'');
return $this;
}
//several columns that are joined together to form the field value
public function mapAggregateField(array $columns, $field, $delimiter='::')
{
$numericColumns = array();
foreach($columns as $column):
$numericColumns[] = (is_numeric($column) ? $column : $this->_alphaToNum($column));
endforeach;
$this->_mapping[] = array('column'=>$numericColumns, 'field'=>$field, 'delimiter'=>$delimiter);
}
//a fake column that is just a literal value
public function mapLiteralField($field, $value){
$this->_mapping[] = array('field'=>$field, 'value'=>$value, 'column'=>null, 'delimiter'=>null);
}
private function _alphaToNum($alphabetChar)
{
$alphabet = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
if(!$alphabetChar){
throw new Exception('No Column Letter Given');
}
if(strlen($alphabetChar) == 1){
return array_search($alphabetChar, $alphabet);
} else {
// e.g. AB AC AD
throw new Exception('> Z Not Implemented. Get a Dev to add this functionality.');
}
}
public function filterField($field, Closure $callback)
{
foreach($this->_mapping as $key=>$mapping):
if($mapping['field'] == $field){
$this->_mapping[$key]['filter'] = $callback;
return $this;
}
endforeach;
return $this;
}
private function getMappedRow($row)
{
$mappedRow = array();
foreach($this->_mapping as $mapping):
if(isset($mapping['value']))
{
//literal set for this field - just set it and move on
$fieldValString = trim($mapping['value']);
}
else {
$fieldVal = array();
if(is_array($mapping['column']))
{
//aggregate field
foreach($mapping['column'] as $column):
if(!empty($row[$column]))
{
$fieldVal[] = trim($row[$column]);
}
endforeach;
} else {
if(!empty($row[$mapping['column']]))
{
$fieldVal[] = trim($row[$mapping['column']]);
}
}
//implode to string
$fieldValString = implode($mapping['delimiter'], $fieldVal);
}
//apply filter where applicable
$mappedRow[$mapping['field']] = isset($mapping['filter']) ? $mapping['filter']($fieldValString) : $fieldValString;
endforeach;
return $mappedRow;
}
public function rewind()
{
return $this->_file->rewind();
}
public function current()
{
return $this->getMappedRow($this->_file->current());
}
public function key()
{
return $this->_file->key();
}
public function next()
{
return $this->_file->next();
}
public function valid()
{
return $this->_file->valid();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment