Skip to content

Commit c431ca3

Browse files
author
ppetrov
committed
Initial commit
0 parents commit c431ca3

File tree

11 files changed

+467
-0
lines changed

11 files changed

+467
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
composer.lock

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# carbon-csv

composer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
3+
"name": "htmlburger/carbon-csv",
4+
"description": "Simple CSV file parser",
5+
6+
"require": {
7+
"php": "^5.4.0 || ^7.0"
8+
},
9+
10+
"autoload": {
11+
"psr-4": {
12+
"Carbon_CSV\\": "src/"
13+
}
14+
},
15+
16+
"require-dev": {
17+
"illuminate/support": "^5.4",
18+
"symfony/var-dumper": "^3.3",
19+
"phpunit/phpunit": "^6.1"
20+
}
21+
}

sample-data/empty.csv

Whitespace-only changes.

sample-data/info-no-head-row.csv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
2+
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
3+
John,Smith,Nice Company Name,
4+
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
First Name;Last Name;Company Name;Address
2+
John;Doe;Funny Company Name;"Some Address 2; 12345; Country A"
3+
Jane;Dove;Nice Company Name;"That Address 3; 456; Country B"
4+
John;Smith;Nice Company Name;
5+
Jane;Smith;Funny Company Name;"This Address 4; City; Country C"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
3+
4+
First Name,Last Name,Company Name,Address
5+
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
6+
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
7+
John,Smith,Nice Company Name,
8+
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"

sample-data/info.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
First Name,Last Name,Company Name,Address
2+
John,Doe,Funny Company Name,"Some Address 2, 12345, Country A"
3+
Jane,Dove,Nice Company Name,"That Address 3, 456, Country B"
4+
John,Smith,Nice Company Name,
5+
Jane,Smith,Funny Company Name,"This Address 4, City, Country C"

src/CsvFile.php

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<?php
2+
3+
namespace Carbon_CSV;
4+
use \SplFileObject as File;
5+
6+
/**
7+
* Enhanced CSV file object
8+
*/
9+
class CsvFile extends File implements \Countable {
10+
private $file_path;
11+
private $encoding = 'utf-8';
12+
private $is_head_row = false;
13+
/**
14+
* Current row.
15+
*/
16+
private $row_counter = 0;
17+
private $column_names;
18+
private $uses_column_names = false;
19+
private $offset_row = 0;
20+
private $start_column = 0;
21+
private $columns_to_skip = array();
22+
23+
function __construct($file_path, $delimiter = ',', $enclosure = '"', $escape = "\\") {
24+
if (!file_exists($file_path)) {
25+
throw new Exception("File $file_path does not exist. ");
26+
}
27+
28+
if (filesize($file_path) === 0) {
29+
throw new Exception("Empty file. ");
30+
}
31+
32+
$this->file_path = $file_path;
33+
parent::__construct($file_path, 'r');
34+
$this->setFlags(File::READ_CSV | File::READ_AHEAD | File::SKIP_EMPTY | File::DROP_NEW_LINE);
35+
$this->setCsvControl($delimiter, $enclosure, $escape);
36+
}
37+
38+
/**
39+
* Read number of lines in CSV
40+
* @return int number of lines
41+
*/
42+
function count() {
43+
return count($this->to_array());
44+
}
45+
46+
public function to_array() {
47+
$rows = [];
48+
foreach ($this as $row) {
49+
$rows[] = $row;
50+
}
51+
52+
return $rows;
53+
}
54+
55+
public function rewind() {
56+
$this->seek($this->offset_row);
57+
}
58+
59+
/**
60+
* Override the key function in order to allow shifting in indecies according
61+
* to the current offset.
62+
*/
63+
public function key() {
64+
return $this->row_counter - 1;
65+
}
66+
67+
public function current() {
68+
$this->row_counter++;
69+
$row = parent::current();
70+
71+
$row_keys = array_keys($row);
72+
if (!in_array($this->start_column, $row_keys)) {
73+
throw new Exception(sprintf('Start column must be between %d and %d.', min($row_keys), max($row_keys)));
74+
}
75+
76+
$formatted_row = $this->format_row($row);
77+
78+
return $formatted_row;
79+
}
80+
81+
private function remove_columns($old_row) {
82+
$new_row = array();
83+
84+
$index = 0;
85+
foreach ($old_row as $column_name => $column_value) {
86+
if (!in_array($index, $this->columns_to_skip)) {
87+
$new_row[$column_name] = $column_value;
88+
}
89+
90+
$index++;
91+
}
92+
93+
return $new_row;
94+
}
95+
96+
private function format_row($row) {
97+
$row = array_combine(
98+
$this->get_column_names($row),
99+
$row
100+
);
101+
102+
// don't remove columns from the head row
103+
// we remove columns after the row is combined with the header columns
104+
if (!$this->is_head_row) {
105+
$row = $this->remove_columns($row);
106+
}
107+
108+
if (!$this->uses_column_names) {
109+
$row = array_values($row);
110+
}
111+
112+
return $row;
113+
}
114+
115+
private function get_column_names($row) {
116+
if (!empty($this->column_names)) {
117+
return $this->column_names;
118+
}
119+
120+
return array_keys($row);
121+
}
122+
123+
public function set_column_names($mapping) {
124+
$this->uses_column_names = true;
125+
126+
if (empty($this->column_names)) {
127+
$this->column_names = $mapping;
128+
} else {
129+
$this->column_names = array_combine(
130+
array_flip($this->column_names),
131+
$mapping
132+
);
133+
}
134+
}
135+
136+
public function use_first_row_as_header() {
137+
if ($this->row_counter !== 0) {
138+
throw new \LogicException("Column mapping can't be changed after CSV processing has been started");
139+
}
140+
141+
$this->uses_column_names = true;
142+
143+
$this->is_head_row = true;
144+
$this->column_names = $this->current();
145+
$this->is_head_row = false;
146+
147+
// Start processing from the second row(since the first one isn't part of the data)
148+
$this->offset_row++;
149+
$this->rewind();
150+
}
151+
152+
public function skip_to_row($row) {
153+
$this->offset_row = $row;
154+
$this->rewind();
155+
}
156+
157+
public function skip_columns($indexes) {
158+
$this->set_columns_to_skip($indexes);
159+
}
160+
161+
public function skip_to_column($column_index) {
162+
if (!is_int($column_index)) {
163+
throw new Exception('Only numbers are allowed for skip to column.');
164+
}
165+
166+
if ($column_index < 0) {
167+
throw new Exception('Please use numbers larger than zero.');
168+
}
169+
170+
$this->start_column = $column_index;
171+
172+
// this is to handle the strange case, when the user wants to start from the first column (which happens by default)
173+
if ($column_index === 0) {
174+
$last_column_index = 0;
175+
} else {
176+
$last_column_index = $column_index - 1;
177+
}
178+
179+
$this->set_columns_to_skip(range(0, $last_column_index));
180+
}
181+
182+
private function set_columns_to_skip($columns) {
183+
$this->columns_to_skip = array_unique(array_merge($columns, $this->columns_to_skip));
184+
}
185+
}

src/Exception.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
namespace Carbon_CSV;
4+
5+
class Exception extends \Exception {
6+
}

0 commit comments

Comments
 (0)