Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
52.63% |
10 / 19 |
CRAP | |
61.54% |
48 / 78 |
CsvImportService | |
0.00% |
0 / 1 |
|
52.63% |
10 / 19 |
109.74 | |
61.54% |
48 / 78 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
9 / 9 |
|||
current | |
0.00% |
0 / 1 |
5.93 | |
66.67% |
8 / 12 |
|||
getColumnHeaders | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setColumnHeaders | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
setHeaderRowNumber | |
0.00% |
0 / 1 |
2.01 | |
85.71% |
6 / 7 |
|||
rewind | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
count | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
next | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
valid | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
key | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
seek | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getFields | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getRow | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getErrors | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 3 |
|||
hasErrors | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
readHeaderRow | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
incrementHeaders | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
mergeDuplicates | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
convertEncodingRows | |
0.00% |
0 / 1 |
6.00 | |
50.00% |
2 / 4 |
<?php | |
/* | |
* This file is part of EC-CUBE | |
* | |
* Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved. | |
* | |
* http://www.lockon.co.jp/ | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation; either version 2 | |
* of the License, or (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
*/ | |
namespace Eccube\Service; | |
use Eccube\Application; | |
/** | |
* Copyright (C) 2012-2014 David de Boer <david@ddeboer.nl> | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of | |
* this software and associated documentation files (the "Software"), to deal in | |
* the Software without restriction, including without limitation the rights to | |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
* the Software, and to permit persons to whom the Software is furnished to do so, | |
* subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
class CsvImportService implements \Iterator, \SeekableIterator, \Countable | |
{ | |
const DUPLICATE_HEADERS_INCREMENT = 1; | |
const DUPLICATE_HEADERS_MERGE = 2; | |
/** | |
* Number of the row that contains the column names | |
* | |
* @var integer | |
*/ | |
protected $headerRowNumber; | |
/** | |
* CSV file | |
* | |
* @var \SplFileObject | |
*/ | |
protected $file; | |
/** | |
* Column headers as read from the CSV file | |
* | |
* @var array | |
*/ | |
protected $columnHeaders = array(); | |
/** | |
* Number of column headers, stored and re-used for performance | |
* | |
* In case of duplicate headers, this is always the number of unmerged headers. | |
* | |
* @var integer | |
*/ | |
protected $headersCount; | |
/** | |
* Total number of rows in the CSV file | |
* | |
* @var integer | |
*/ | |
protected $count; | |
/** | |
* Faulty CSV rows | |
* | |
* @var array | |
*/ | |
protected $errors = array(); | |
/** | |
* How to handle duplicate headers | |
* | |
* @var integer | |
*/ | |
protected $duplicateHeadersFlag; | |
/** | |
* @param \SplFileObject $file | |
* @param string $delimiter | |
* @param string $enclosure | |
* @param string $escape | |
*/ | |
public function __construct(\SplFileObject $file, $delimiter = ',', $enclosure = '"', $escape = '\\') | |
{ | |
ini_set('auto_detect_line_endings', true); | |
$this->file = $file; | |
$this->file->setFlags( | |
\SplFileObject::READ_CSV | | |
\SplFileObject::SKIP_EMPTY | | |
\SplFileObject::READ_AHEAD | | |
\SplFileObject::DROP_NEW_LINE | |
); | |
$this->file->setCsvControl( | |
$delimiter, | |
$enclosure, | |
$escape | |
); | |
} | |
/** | |
* Return the current row as an array | |
* | |
* If a header row has been set, an associative array will be returned | |
* | |
* @return array | |
*/ | |
public function current() | |
{ | |
// If the CSV has no column headers just return the line | |
if (empty($this->columnHeaders)) { | |
return $this->file->current(); | |
} | |
// Since the CSV has column headers use them to construct an associative array for the columns in this line | |
if ($this->valid()) { | |
$current = $this->file->current(); | |
$current = $this->convertEncodingRows($current); | |
$line = $current; | |
// See if values for duplicate headers should be merged | |
if (self::DUPLICATE_HEADERS_MERGE === $this->duplicateHeadersFlag) { | |
$line = $this->mergeDuplicates($line); | |
} | |
// Count the number of elements in both: they must be equal. | |
if (count($this->columnHeaders) === count($line)) { | |
return array_combine(array_keys($this->columnHeaders), $line); | |
} else { | |
return $line; | |
} | |
}; | |
return null; | |
} | |
/** | |
* Get column headers | |
* | |
* @return array | |
*/ | |
public function getColumnHeaders() | |
{ | |
return array_keys($this->columnHeaders); | |
} | |
/** | |
* Set column headers | |
* | |
* @param array $columnHeaders | |
*/ | |
public function setColumnHeaders(array $columnHeaders) | |
{ | |
$columnHeaders = $this->convertEncodingRows($columnHeaders); | |
$this->columnHeaders = array_count_values($columnHeaders); | |
$this->headersCount = count($columnHeaders); | |
} | |
/** | |
* Set header row number | |
* | |
* @param integer $rowNumber Number of the row that contains column header names | |
* @param integer $duplicates How to handle duplicates (optional). One of: | |
* - CsvReader::DUPLICATE_HEADERS_INCREMENT; | |
* increments duplicates (dup, dup1, dup2 etc.) | |
* - CsvReader::DUPLICATE_HEADERS_MERGE; merges | |
* values for duplicate headers into an array | |
* (dup => [value1, value2, value3]) | |
* @return boolean | |
*/ | |
public function setHeaderRowNumber($rowNumber, $duplicates = null) | |
{ | |
$this->duplicateHeadersFlag = $duplicates; | |
$this->headerRowNumber = $rowNumber; | |
$headers = $this->readHeaderRow($rowNumber); | |
if ($headers === false) { | |
return false; | |
} | |
$this->setColumnHeaders($headers); | |
return true; | |
} | |
/** | |
* Rewind the file pointer | |
* | |
* If a header row has been set, the pointer is set just below the header | |
* row. That way, when you iterate over the rows, that header row is | |
* skipped. | |
*/ | |
public function rewind() | |
{ | |
$this->file->rewind(); | |
if (null !== $this->headerRowNumber) { | |
$this->file->seek($this->headerRowNumber + 1); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function count() | |
{ | |
if (null === $this->count) { | |
$position = $this->key(); | |
$this->count = iterator_count($this); | |
$this->seek($position); | |
} | |
return $this->count; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function next() | |
{ | |
$this->file->next(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function valid() | |
{ | |
return $this->file->valid(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function key() | |
{ | |
return $this->file->key(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function seek($pointer) | |
{ | |
$this->file->seek($pointer); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFields() | |
{ | |
return $this->getColumnHeaders(); | |
} | |
/** | |
* Get a row | |
* | |
* @param integer $number Row number | |
* | |
* @return array | |
*/ | |
public function getRow($number) | |
{ | |
$this->seek($number); | |
return $this->current(); | |
} | |
/** | |
* Get rows that have an invalid number of columns | |
* | |
* @return array | |
*/ | |
public function getErrors() | |
{ | |
if (0 === $this->key()) { | |
// Iterator has not yet been processed, so do that now | |
foreach ($this as $row) { /* noop */ | |
} | |
} | |
return $this->errors; | |
} | |
/** | |
* Does the reader contain any invalid rows? | |
* | |
* @return boolean | |
*/ | |
public function hasErrors() | |
{ | |
return count($this->getErrors()) > 0; | |
} | |
/** | |
* Read header row from CSV file | |
* | |
* @param integer $rowNumber Row number | |
* | |
* @return array | |
* | |
*/ | |
protected function readHeaderRow($rowNumber) | |
{ | |
$this->file->seek($rowNumber); | |
$headers = $this->file->current(); | |
return $headers; | |
} | |
/** | |
* Add an increment to duplicate headers | |
* | |
* So the following line: | |
* |duplicate|duplicate|duplicate| | |
* |first |second |third | | |
* | |
* Yields value: | |
* $duplicate => 'first', $duplicate1 => 'second', $duplicate2 => 'third' | |
* | |
* @param array $headers | |
* | |
* @return array | |
*/ | |
protected function incrementHeaders(array $headers) | |
{ | |
$incrementedHeaders = array(); | |
foreach (array_count_values($headers) as $header => $count) { | |
if ($count > 1) { | |
$incrementedHeaders[] = $header; | |
for ($i = 1; $i < $count; $i++) { | |
$incrementedHeaders[] = $header . $i; | |
} | |
} else { | |
$incrementedHeaders[] = $header; | |
} | |
} | |
return $incrementedHeaders; | |
} | |
/** | |
* Merges values for duplicate headers into an array | |
* | |
* So the following line: | |
* |duplicate|duplicate|duplicate| | |
* |first |second |third | | |
* | |
* Yields value: | |
* $duplicate => ['first', 'second', 'third'] | |
* | |
* @param array $line | |
* | |
* @return array | |
*/ | |
protected function mergeDuplicates(array $line) | |
{ | |
$values = array(); | |
$i = 0; | |
foreach ($this->columnHeaders as $count) { | |
if (1 === $count) { | |
$values[] = $line[$i]; | |
} else { | |
$values[] = array_slice($line, $i, $count); | |
} | |
$i += $count; | |
} | |
return $values; | |
} | |
/** | |
* 行の文字エンコーディングを変換する. | |
* | |
* Windows 版 PHP7 環境では、ファイルエンコーディングが CP932 になるため UTF-8 に変換する. | |
* それ以外の環境では何もしない。 | |
*/ | |
protected function convertEncodingRows($row) { | |
if ('\\' === DIRECTORY_SEPARATOR && PHP_VERSION_ID >= 70000) { | |
foreach ($row as &$col) { | |
$col = mb_convert_encoding($col , 'UTF-8', 'SJIS-win'); | |
} | |
} | |
return $row; | |
} | |
} |