* $parser = new CSVParser('myfile.csv'); * $parser->mapColumns(array( * '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(); * } * * @deprecated 4.13.0 Use League\Csv\Reader instead */ class CSVParser implements Iterator { use Injectable; /** * @var string $filename */ protected $filename; /** * @var resource $fileHandle */ protected $fileHandle; /** * Map of source columns to output columns. * * Once they get into this variable, all of the source columns are in * lowercase. * * @var array */ protected $columnMap = []; /** * 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. * * @var array */ protected $headerRow = null; /** * A custom header row provided by the caller. * * @var array */ protected $providedHeaderRow = null; /** * The data of the current row. * * @var array */ 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. * * @var int */ protected $rowNum = 0; /** * The character for separating columns. * * @var string */ protected $delimiter = ","; /** * The character for quoting columns. * * @var string */ protected $enclosure = '"'; /** * Open a CSV file for parsing. * * You can use the object returned in a foreach loop to extract the data. * * @param string $filename The name of the file. If relative, it will be relative to the site's base dir * @param string $delimiter The character for separating columns * @param string $enclosure The character for quoting or enclosing columns */ public function __construct($filename, $delimiter = ",", $enclosure = '"') { Deprecation::notice('4.13.0', 'Use ' . Reader::class . ' instead', Deprecation::SCOPE_CLASS); $filename = Director::getAbsFile($filename); $this->filename = $filename; $this->delimiter = $delimiter; $this->enclosure = $enclosure; } /** * Re-map columns in the CSV file. * * This can be useful for identifying synonyms in the file. For example: * * * $csv->mapColumns(array( * 'firstname' => 'FirstName', * 'last name' => 'Surname', * )); * * * @param array $columnMap */ public function mapColumns($columnMap) { if ($columnMap) { $lowerColumnMap = []; 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. * * @param array $headerRow */ public function provideHeaderRow($headerRow) { $this->providedHeaderRow = $headerRow; } /** * Open the CSV file for reading. */ protected function openFile() { $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. * * @param array $header * * @return array */ protected function remapHeader($header) { $mappedHeader = []; 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; * * @return array */ 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 = []; foreach ($srcRow as $i => $value) { // Allow escaping of quotes and commas in the data $value = str_replace( ['\\' . $this->enclosure,'\\' . $this->delimiter], [$this->enclosure, $this->delimiter], $value ?? '' ); // Trim leading tab // [SS-2017-007] Ensure all cells with leading [@=+] have a leading tab $value = ltrim($value ?? '', "\t"); 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 */ public function __destruct() { $this->closeFile(); } //// ITERATOR FUNCTIONS /** * @ignore */ #[\ReturnTypeWillChange] public function rewind() { $this->closeFile(); $this->fetchCSVRow(); } /** * @ignore */ #[\ReturnTypeWillChange] public function current() { return $this->currentRow; } /** * @ignore */ #[\ReturnTypeWillChange] public function key() { return $this->rowNum; } /** * @ignore */ #[\ReturnTypeWillChange] public function next() { $this->fetchCSVRow(); return $this->currentRow; } /** * @ignore */ #[\ReturnTypeWillChange] public function valid() { return $this->currentRow ? true : false; } }