Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ppetrov committed Sep 7, 2017
0 parents commit c431ca3
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor
composer.lock
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# carbon-csv
21 changes: 21 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{

"name": "htmlburger/carbon-csv",
"description": "Simple CSV file parser",

"require": {
"php": "^5.4.0 || ^7.0"
},

"autoload": {
"psr-4": {
"Carbon_CSV\\": "src/"
}
},

"require-dev": {
"illuminate/support": "^5.4",
"symfony/var-dumper": "^3.3",
"phpunit/phpunit": "^6.1"
}
}
Empty file added sample-data/empty.csv
Empty file.
4 changes: 4 additions & 0 deletions sample-data/info-no-head-row.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
John,Smith,Nice Company Name,
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"
5 changes: 5 additions & 0 deletions sample-data/info-semicolon-separator.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
First Name;Last Name;Company Name;Address
John;Doe;Funny Company Name;"Some Address 2; 12345; Country A"
Jane;Dove;Nice Company Name;"That Address 3; 456; Country B"
John;Smith;Nice Company Name;
Jane;Smith;Funny Company Name;"This Address 4; City; Country C"
8 changes: 8 additions & 0 deletions sample-data/info-with-empty-rows-before-actual-content.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@



First Name,Last Name,Company Name,Address
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
John,Smith,Nice Company Name,
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"
5 changes: 5 additions & 0 deletions sample-data/info.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
First Name,Last Name,Company Name,Address
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
John,Smith,Nice Company Name,
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"
185 changes: 185 additions & 0 deletions src/CsvFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

namespace Carbon_CSV;
use \SplFileObject as File;

/**
* Enhanced CSV file object
*/
class CsvFile extends File implements \Countable {
private $file_path;
private $encoding = 'utf-8';
private $is_head_row = false;
/**
* Current row.
*/
private $row_counter = 0;
private $column_names;
private $uses_column_names = false;
private $offset_row = 0;
private $start_column = 0;
private $columns_to_skip = array();

function __construct($file_path, $delimiter = ',', $enclosure = '"', $escape = "\\") {
if (!file_exists($file_path)) {
throw new Exception("File $file_path does not exist. ");
}

if (filesize($file_path) === 0) {
throw new Exception("Empty file. ");
}

$this->file_path = $file_path;
parent::__construct($file_path, 'r');
$this->setFlags(File::READ_CSV | File::READ_AHEAD | File::SKIP_EMPTY | File::DROP_NEW_LINE);
$this->setCsvControl($delimiter, $enclosure, $escape);
}

/**
* Read number of lines in CSV
* @return int number of lines
*/
function count() {
return count($this->to_array());
}

public function to_array() {
$rows = [];
foreach ($this as $row) {
$rows[] = $row;
}

return $rows;
}

public function rewind() {
$this->seek($this->offset_row);
}

/**
* Override the key function in order to allow shifting in indecies according
* to the current offset.
*/
public function key() {
return $this->row_counter - 1;
}

public function current() {
$this->row_counter++;
$row = parent::current();

$row_keys = array_keys($row);
if (!in_array($this->start_column, $row_keys)) {
throw new Exception(sprintf('Start column must be between %d and %d.', min($row_keys), max($row_keys)));
}

$formatted_row = $this->format_row($row);

return $formatted_row;
}

private function remove_columns($old_row) {
$new_row = array();

$index = 0;
foreach ($old_row as $column_name => $column_value) {
if (!in_array($index, $this->columns_to_skip)) {
$new_row[$column_name] = $column_value;
}

$index++;
}

return $new_row;
}

private function format_row($row) {
$row = array_combine(
$this->get_column_names($row),
$row
);

// don't remove columns from the head row
// we remove columns after the row is combined with the header columns
if (!$this->is_head_row) {
$row = $this->remove_columns($row);
}

if (!$this->uses_column_names) {
$row = array_values($row);
}

return $row;
}

private function get_column_names($row) {
if (!empty($this->column_names)) {
return $this->column_names;
}

return array_keys($row);
}

public function set_column_names($mapping) {
$this->uses_column_names = true;

if (empty($this->column_names)) {
$this->column_names = $mapping;
} else {
$this->column_names = array_combine(
array_flip($this->column_names),
$mapping
);
}
}

public function use_first_row_as_header() {
if ($this->row_counter !== 0) {
throw new \LogicException("Column mapping can't be changed after CSV processing has been started");
}

$this->uses_column_names = true;

$this->is_head_row = true;
$this->column_names = $this->current();
$this->is_head_row = false;

// Start processing from the second row(since the first one isn't part of the data)
$this->offset_row++;
$this->rewind();
}

public function skip_to_row($row) {
$this->offset_row = $row;
$this->rewind();
}

public function skip_columns($indexes) {
$this->set_columns_to_skip($indexes);
}

public function skip_to_column($column_index) {
if (!is_int($column_index)) {
throw new Exception('Only numbers are allowed for skip to column.');
}

if ($column_index < 0) {
throw new Exception('Please use numbers larger than zero.');
}

$this->start_column = $column_index;

// this is to handle the strange case, when the user wants to start from the first column (which happens by default)
if ($column_index === 0) {
$last_column_index = 0;
} else {
$last_column_index = $column_index - 1;
}

$this->set_columns_to_skip(range(0, $last_column_index));
}

private function set_columns_to_skip($columns) {
$this->columns_to_skip = array_unique(array_merge($columns, $this->columns_to_skip));
}
}
6 changes: 6 additions & 0 deletions src/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace Carbon_CSV;

class Exception extends \Exception {
}
Loading

0 comments on commit c431ca3

Please sign in to comment.