add League csv plugin
This commit is contained in:
parent
460ec78cb7
commit
d323ccd1ff
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv;
|
||||||
|
|
||||||
|
use IteratorAggregate;
|
||||||
|
use JsonSerializable;
|
||||||
|
use League\Csv\Config\Controls;
|
||||||
|
use League\Csv\Config\Factory;
|
||||||
|
use League\Csv\Config\Output;
|
||||||
|
use League\Csv\Config\StreamFilter;
|
||||||
|
use SplFileInfo;
|
||||||
|
use SplFileObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to enable basic CSV manipulation
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 4.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* UTF-8 BOM sequence
|
||||||
|
*/
|
||||||
|
const BOM_UTF8 = "\xEF\xBB\xBF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-16 BE BOM sequence
|
||||||
|
*/
|
||||||
|
const BOM_UTF16_BE = "\xFE\xFF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-16 LE BOM sequence
|
||||||
|
*/
|
||||||
|
const BOM_UTF16_LE = "\xFF\xFE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-16 BE BOM sequence
|
||||||
|
*/
|
||||||
|
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-16 LE BOM sequence
|
||||||
|
*/
|
||||||
|
const BOM_UTF32_LE = "\x00\x00\xFF\xFE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor path
|
||||||
|
*
|
||||||
|
* can be a SplFileInfo object or the string path to a file
|
||||||
|
*
|
||||||
|
* @var \SplFileObject|string
|
||||||
|
*/
|
||||||
|
protected $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file open mode flag
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $open_mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Csv Controls Trait
|
||||||
|
*/
|
||||||
|
use Controls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Csv Factory Trait
|
||||||
|
*/
|
||||||
|
use Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Csv Ouputting Trait
|
||||||
|
*/
|
||||||
|
use Output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream Filter API Trait
|
||||||
|
*/
|
||||||
|
use StreamFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance
|
||||||
|
*
|
||||||
|
* The path must be an SplFileInfo object
|
||||||
|
* an object that implements the `__toString` method
|
||||||
|
* a path to a file
|
||||||
|
*
|
||||||
|
* @param object|string $path The file path
|
||||||
|
* @param string $open_mode the file open mode flag
|
||||||
|
*/
|
||||||
|
public function __construct($path, $open_mode = 'r+')
|
||||||
|
{
|
||||||
|
ini_set('auto_detect_line_endings', '1');
|
||||||
|
|
||||||
|
$this->path = $this->normalizePath($path);
|
||||||
|
$this->open_mode = strtolower($open_mode);
|
||||||
|
$this->initStreamFilter($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a normalize path which could be a SplFileObject
|
||||||
|
* or a string path
|
||||||
|
*
|
||||||
|
* @param object|string $path the filepath
|
||||||
|
*
|
||||||
|
* @return \SplFileObject|string
|
||||||
|
*/
|
||||||
|
protected function normalizePath($path)
|
||||||
|
{
|
||||||
|
if ($path instanceof SplFileObject) {
|
||||||
|
return $path;
|
||||||
|
} elseif ($path instanceof SplFileInfo) {
|
||||||
|
return $path->getPath().'/'.$path->getBasename();
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
//in case $this->path is a SplFileObject we need to remove its reference
|
||||||
|
$this->path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the CSV Iterator
|
||||||
|
*
|
||||||
|
* @return \SplFileObject
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
$obj = $this->path;
|
||||||
|
if (! $obj instanceof SplFileObject) {
|
||||||
|
$obj = new SplFileObject($this->getStreamFilterPath(), $this->open_mode);
|
||||||
|
}
|
||||||
|
$obj->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
|
||||||
|
$obj->setFlags($this->flags);
|
||||||
|
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link AbstractCsv} instance from another {@link AbstractCsv} object
|
||||||
|
*
|
||||||
|
* @param string $class_name the class to be instantiated
|
||||||
|
* @param string $open_mode the file open mode flag
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
protected function newInstance($class_name, $open_mode)
|
||||||
|
{
|
||||||
|
$csv = new $class_name($this->path, $open_mode);
|
||||||
|
$csv->delimiter = $this->delimiter;
|
||||||
|
$csv->enclosure = $this->enclosure;
|
||||||
|
$csv->escape = $this->escape;
|
||||||
|
$csv->encodingFrom = $this->encodingFrom;
|
||||||
|
$csv->bom = $this->bom;
|
||||||
|
|
||||||
|
return $csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Writer} instance from a {@link AbstractCsv} object
|
||||||
|
*
|
||||||
|
* @param string $open_mode the file open mode flag
|
||||||
|
*
|
||||||
|
* @return \League\Csv\Writer
|
||||||
|
*/
|
||||||
|
public function newWriter($open_mode = 'r+')
|
||||||
|
{
|
||||||
|
return $this->newInstance('\League\Csv\Writer', $open_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Reader} instance from a {@link AbstractCsv} object
|
||||||
|
*
|
||||||
|
* @param string $open_mode the file open mode flag
|
||||||
|
*
|
||||||
|
* @return \League\Csv\Reader
|
||||||
|
*/
|
||||||
|
public function newReader($open_mode = 'r+')
|
||||||
|
{
|
||||||
|
return $this->newInstance('\League\Csv\Reader', $open_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a variable to be stringable
|
||||||
|
*
|
||||||
|
* @param object|string $str
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isValidString($str)
|
||||||
|
{
|
||||||
|
return is_scalar($str) || (is_object($str) && method_exists($str, '__toString'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Config;
|
||||||
|
|
||||||
|
use CallbackFilterIterator;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LimitIterator;
|
||||||
|
use SplFileObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait to configure and check CSV file and content
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 6.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait Controls
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* the field delimiter (one character only)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $delimiter = ',';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the field enclosure character (one character only)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $enclosure = '"';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the field escape character (one character only)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $escape = '\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the \SplFileObject flags holder
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $flags = SplFileObject::READ_CSV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return a SplFileOjbect
|
||||||
|
*
|
||||||
|
* @return \SplFileOjbect
|
||||||
|
*/
|
||||||
|
abstract public function getIterator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the field delimeter
|
||||||
|
*
|
||||||
|
* @param string $delimiter
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $delimeter is not a single character
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setDelimiter($delimiter = ',')
|
||||||
|
{
|
||||||
|
if (1 != mb_strlen($delimiter)) {
|
||||||
|
throw new InvalidArgumentException('The delimiter must be a single character');
|
||||||
|
}
|
||||||
|
$this->delimiter = $delimiter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the current field delimiter
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDelimiter()
|
||||||
|
{
|
||||||
|
return $this->delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* detect the actual number of row according to a delimiter
|
||||||
|
*
|
||||||
|
* @param string $delimiter a CSV delimiter
|
||||||
|
* @param int $nb_rows the number of row to consider
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function fetchRowsCountByDelimiter($delimiter, $nb_rows = 1)
|
||||||
|
{
|
||||||
|
$iterator = $this->getIterator();
|
||||||
|
$iterator->setCsvControl($delimiter, $this->enclosure, $this->escape);
|
||||||
|
//"reduce" the csv length to a maximum of $nb_rows
|
||||||
|
$iterator = new LimitIterator($iterator, 0, $nb_rows);
|
||||||
|
//return the parse rows
|
||||||
|
$iterator = new CallbackFilterIterator($iterator, function ($row) {
|
||||||
|
return is_array($row) && count($row) > 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return count(iterator_to_array($iterator, false), COUNT_RECURSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the CSV file delimiter
|
||||||
|
*
|
||||||
|
* @param int $nb_rows
|
||||||
|
* @param string[] $delimiters additional delimiters
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $nb_rows value is invalid
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function detectDelimiterList($nb_rows = 1, array $delimiters = [])
|
||||||
|
{
|
||||||
|
$nb_rows = filter_var($nb_rows, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||||
|
if (! $nb_rows) {
|
||||||
|
throw new InvalidArgumentException('`$nb_rows` must be a valid positive integer');
|
||||||
|
}
|
||||||
|
|
||||||
|
$delimiters = array_filter($delimiters, function ($str) {
|
||||||
|
return 1 == mb_strlen($str);
|
||||||
|
});
|
||||||
|
$delimiters = array_merge([$this->delimiter, ',', ';', "\t"], $delimiters);
|
||||||
|
$delimiters = array_unique($delimiters);
|
||||||
|
$res = array_fill_keys($delimiters, 0);
|
||||||
|
array_walk($res, function (&$value, $delim) use ($nb_rows) {
|
||||||
|
$value = $this->fetchRowsCountByDelimiter($delim, $nb_rows);
|
||||||
|
});
|
||||||
|
|
||||||
|
arsort($res, SORT_NUMERIC);
|
||||||
|
|
||||||
|
return array_keys(array_filter($res));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the field enclosure
|
||||||
|
*
|
||||||
|
* @param string $enclosure
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $enclosure is not a single character
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setEnclosure($enclosure = '"')
|
||||||
|
{
|
||||||
|
if (1 != mb_strlen($enclosure)) {
|
||||||
|
throw new InvalidArgumentException('The enclosure must be a single character');
|
||||||
|
}
|
||||||
|
$this->enclosure = $enclosure;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the current field enclosure
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEnclosure()
|
||||||
|
{
|
||||||
|
return $this->enclosure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the field escape character
|
||||||
|
*
|
||||||
|
* @param string $escape
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $escape is not a single character
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setEscape($escape = "\\")
|
||||||
|
{
|
||||||
|
if (1 != mb_strlen($escape)) {
|
||||||
|
throw new InvalidArgumentException('The escape character must be a single character');
|
||||||
|
}
|
||||||
|
$this->escape = $escape;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the current field escape character
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEscape()
|
||||||
|
{
|
||||||
|
return $this->escape;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Flags associated to the CSV SplFileObject
|
||||||
|
*
|
||||||
|
* @param int $flags
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the argument is not a valid integer
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFlags($flags)
|
||||||
|
{
|
||||||
|
if (false === filter_var($flags, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
||||||
|
throw new InvalidArgumentException('you should use a `SplFileObject` Constant');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->flags = $flags|SplFileObject::READ_CSV|SplFileObject::DROP_NEW_LINE;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file Flags
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getFlags()
|
||||||
|
{
|
||||||
|
return $this->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the BOM sequence of the given CSV
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputBOM()
|
||||||
|
{
|
||||||
|
$bom = [
|
||||||
|
self::BOM_UTF8,
|
||||||
|
self::BOM_UTF16_BE,
|
||||||
|
self::BOM_UTF16_LE,
|
||||||
|
self::BOM_UTF32_BE,
|
||||||
|
self::BOM_UTF32_LE
|
||||||
|
];
|
||||||
|
$csv = $this->getIterator();
|
||||||
|
$csv->rewind();
|
||||||
|
$line = $csv->fgets();
|
||||||
|
|
||||||
|
$res = array_filter($bom, function ($sequence) use ($line) {
|
||||||
|
return strpos($line, $sequence) === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return array_shift($res);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.X.X
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Config;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use SplFileInfo;
|
||||||
|
use SplFileObject;
|
||||||
|
use SplTempFileObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait to facilate class instantiation
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 6.X.X
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a {@link AbstractCsv} from a string
|
||||||
|
*
|
||||||
|
* The path can be:
|
||||||
|
* - an SplFileInfo,
|
||||||
|
* - a SplFileObject,
|
||||||
|
* - an object that implements the `__toString` method,
|
||||||
|
* - a string
|
||||||
|
*
|
||||||
|
* BUT NOT a SplTempFileObject
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
*<?php
|
||||||
|
* $csv = new Reader::createFromPath('/path/to/file.csv', 'a+');
|
||||||
|
* $csv = new Reader::createFromPath(new SplFileInfo('/path/to/file.csv'));
|
||||||
|
* $csv = new Reader::createFromPath(new SplFileObject('/path/to/file.csv'), 'rb');
|
||||||
|
*
|
||||||
|
* ?>
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param object|string $path file path
|
||||||
|
* @param string $open_mode the file open mode flag
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $path is a \SplTempFileObject object
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function createFromPath($path, $open_mode = 'r+')
|
||||||
|
{
|
||||||
|
if ($path instanceof SplTempFileObject) {
|
||||||
|
throw new InvalidArgumentException('an `SplTempFileObject` object does not contain a valid path');
|
||||||
|
} elseif ($path instanceof SplFileInfo) {
|
||||||
|
$path = $path->getPath().'/'.$path->getBasename();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static(trim($path), $open_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link AbstractCsv} from a SplFileObject
|
||||||
|
*
|
||||||
|
* The path can be:
|
||||||
|
* - a SplFileObject,
|
||||||
|
* - a SplTempFileObject
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
*<?php
|
||||||
|
* $csv = new Writer::createFromFileObject(new SplFileInfo('/path/to/file.csv'));
|
||||||
|
* $csv = new Writer::createFromFileObject(new SplTempFileObject);
|
||||||
|
*
|
||||||
|
* ?>
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param SplFileObject $obj
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function createFromFileObject(SplFileObject $obj)
|
||||||
|
{
|
||||||
|
return new static($obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link AbstractCsv} from a string
|
||||||
|
*
|
||||||
|
* The string must be an object that implements the `__toString` method,
|
||||||
|
* or a string
|
||||||
|
*
|
||||||
|
* @param string|object $str the string
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the data provided is invalid
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function createFromString($str)
|
||||||
|
{
|
||||||
|
if (! self::isValidString($str)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'the submitted data must be a string or an object implementing the `__toString` method'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$obj = new SplTempFileObject();
|
||||||
|
$obj->fwrite(rtrim($str)."\n");
|
||||||
|
|
||||||
|
return static::createFromFileObject($obj);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Config;
|
||||||
|
|
||||||
|
use DomDocument;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use League\Csv\Iterator\MapIterator;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait to output CSV
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 6.3.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait Output
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Charset Encoding for the CSV
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $encodingFrom = 'UTF-8';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BOM sequence for Outputting the CSV
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $bom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the CSV Iterator
|
||||||
|
*
|
||||||
|
* @return \SplFileObject
|
||||||
|
*/
|
||||||
|
abstract public function getIterator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonSerializable Interface
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return iterator_to_array($this->convertToUtf8($this->getIterator()), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the CSV encoding charset
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setEncodingFrom($str)
|
||||||
|
{
|
||||||
|
$str = str_replace('_', '-', $str);
|
||||||
|
$str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW|FILTER_FLAG_STRIP_HIGH]);
|
||||||
|
if (empty($str)) {
|
||||||
|
throw new InvalidArgumentException('you should use a valid charset');
|
||||||
|
}
|
||||||
|
$this->encodingFrom = strtoupper($str);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the source CSV encoding charset
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEncodingFrom()
|
||||||
|
{
|
||||||
|
return $this->encodingFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the BOM sequence to prepend the CSV on output
|
||||||
|
*
|
||||||
|
* @param string $str The BOM sequence
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setOutputBOM($str = null)
|
||||||
|
{
|
||||||
|
if (empty($str)) {
|
||||||
|
$this->bom = null;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$str = (string) $str;
|
||||||
|
$str = trim($str);
|
||||||
|
$this->bom = $str;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the BOM sequence in use on Output methods
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOutputBOM()
|
||||||
|
{
|
||||||
|
return $this->bom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output all data on the CSV file
|
||||||
|
*
|
||||||
|
* @param string $filename CSV downloaded name if present adds extra headers
|
||||||
|
*/
|
||||||
|
public function output($filename = null)
|
||||||
|
{
|
||||||
|
$iterator = $this->getIterator();
|
||||||
|
$iterator->rewind();
|
||||||
|
//@codeCoverageIgnoreStart
|
||||||
|
if (! is_null($filename) && self::isValidString($filename)) {
|
||||||
|
$filename = trim($filename);
|
||||||
|
$filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
|
||||||
|
header("Content-Type: application/octet-stream");
|
||||||
|
header("Content-Transfer-Encoding: binary");
|
||||||
|
header('Content-Disposition: attachment; filename="'.$filename);
|
||||||
|
}
|
||||||
|
//@codeCoverageIgnoreEnd
|
||||||
|
echo $this->bom;
|
||||||
|
$iterator->fpassthru();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the CSV content
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
$this->output();
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a HTML table representation of the CSV Table
|
||||||
|
*
|
||||||
|
* @param string $class_name optional classname
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function toHTML($class_name = 'table-csv-data')
|
||||||
|
{
|
||||||
|
$doc = $this->toXML('table', 'tr', 'td');
|
||||||
|
$doc->documentElement->setAttribute('class', $class_name);
|
||||||
|
|
||||||
|
return $doc->saveHTML($doc->documentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transform a CSV into a XML
|
||||||
|
*
|
||||||
|
* @param string $root_name XML root node name
|
||||||
|
* @param string $row_name XML row node name
|
||||||
|
* @param string $cell_name XML cell node name
|
||||||
|
*
|
||||||
|
* @return \DomDocument
|
||||||
|
*/
|
||||||
|
public function toXML($root_name = 'csv', $row_name = 'row', $cell_name = 'cell')
|
||||||
|
{
|
||||||
|
$doc = new DomDocument('1.0', 'UTF-8');
|
||||||
|
$root = $doc->createElement($root_name);
|
||||||
|
$iterator = $this->convertToUtf8($this->getIterator());
|
||||||
|
foreach ($iterator as $row) {
|
||||||
|
$item = $doc->createElement($row_name);
|
||||||
|
array_walk($row, function ($value) use (&$item, $doc, $cell_name) {
|
||||||
|
$content = $doc->createTextNode($value);
|
||||||
|
$cell = $doc->createElement($cell_name);
|
||||||
|
$cell->appendChild($content);
|
||||||
|
$item->appendChild($cell);
|
||||||
|
});
|
||||||
|
$root->appendChild($item);
|
||||||
|
}
|
||||||
|
$doc->appendChild($root);
|
||||||
|
|
||||||
|
return $doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Csv file into UTF-8
|
||||||
|
*
|
||||||
|
* @return \Traversable
|
||||||
|
*/
|
||||||
|
protected function convertToUtf8(Traversable $iterator)
|
||||||
|
{
|
||||||
|
if (strpos($this->encodingFrom, 'UTF-8') !== false) {
|
||||||
|
return $iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MapIterator($iterator, function ($row) {
|
||||||
|
foreach ($row as &$value) {
|
||||||
|
$value = mb_convert_encoding($value, 'UTF-8', $this->encodingFrom);
|
||||||
|
}
|
||||||
|
unset($value);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Config;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
use OutOfBoundsException;
|
||||||
|
use SplFileInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Trait to ease PHP Stream Filters manipulation
|
||||||
|
* with a SplFileObject
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 6.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait StreamFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* collection of stream filters
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $stream_filters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream filtering mode to apply on all filters
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $stream_filter_mode = STREAM_FILTER_ALL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*the real path
|
||||||
|
*
|
||||||
|
* @var string the real path to the file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $stream_uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal path setter
|
||||||
|
*
|
||||||
|
* The path must be an SplFileInfo object
|
||||||
|
* an object that implements the `__toString` method
|
||||||
|
* a path to a file
|
||||||
|
*
|
||||||
|
* @param \SplFileObject|string $path The file path
|
||||||
|
*/
|
||||||
|
protected function initStreamFilter($path)
|
||||||
|
{
|
||||||
|
if (! is_string($path)) {
|
||||||
|
$this->stream_uri = null;
|
||||||
|
$this->stream_filters = [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->extractStreamSettings($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Available stream settings from $path
|
||||||
|
*
|
||||||
|
* @param string $path the file path
|
||||||
|
*/
|
||||||
|
protected function extractStreamSettings($path)
|
||||||
|
{
|
||||||
|
if (! preg_match(
|
||||||
|
',^php://filter/(?P<mode>:?read=|write=)?(?P<filters>.*?)/resource=(?P<resource>.*)$,i',
|
||||||
|
$path,
|
||||||
|
$matches
|
||||||
|
)) {
|
||||||
|
$this->stream_uri = $path;
|
||||||
|
$this->stream_filters = [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$matches['mode'] = strtolower($matches['mode']);
|
||||||
|
$mode = STREAM_FILTER_ALL;
|
||||||
|
if ('write=' == $matches['mode']) {
|
||||||
|
$mode = STREAM_FILTER_WRITE;
|
||||||
|
} elseif ('read=' == $matches['mode']) {
|
||||||
|
$mode = STREAM_FILTER_READ;
|
||||||
|
}
|
||||||
|
$this->stream_filter_mode = $mode;
|
||||||
|
$this->stream_uri = $matches['resource'];
|
||||||
|
$this->stream_filters = explode('|', $matches['filters']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the trait methods can be used
|
||||||
|
*
|
||||||
|
* @throws \LogicException If the API can not be use
|
||||||
|
*/
|
||||||
|
protected function assertStreamable()
|
||||||
|
{
|
||||||
|
if (! is_string($this->stream_uri)) {
|
||||||
|
throw new LogicException('The stream filter API can not be used');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether the stream filter capabilities can be used
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActiveStreamFilter()
|
||||||
|
{
|
||||||
|
return is_string($this->stream_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stream filter mode Setter
|
||||||
|
*
|
||||||
|
* Set the new Stream Filter mode and remove all
|
||||||
|
* previously attached stream filters
|
||||||
|
*
|
||||||
|
* @param int $mode
|
||||||
|
*
|
||||||
|
* @throws \OutOfBoundsException If the mode is invalid
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setStreamFilterMode($mode)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
if (! in_array($mode, [STREAM_FILTER_ALL, STREAM_FILTER_READ, STREAM_FILTER_WRITE])) {
|
||||||
|
throw new OutOfBoundsException('the $mode should be a valid `STREAM_FILTER_*` constant');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stream_filter_mode = $mode;
|
||||||
|
$this->stream_filters = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stream filter mode getter
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getStreamFilterMode()
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
|
||||||
|
return $this->stream_filter_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* append a stream filter
|
||||||
|
*
|
||||||
|
* @param string $filter_name a string or an object that implements the '__toString' method
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function appendStreamFilter($filter_name)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
$this->stream_filters[] = $this->sanitizeStreamFilter($filter_name);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepend a stream filter
|
||||||
|
*
|
||||||
|
* @param string $filter_name a string or an object that implements the '__toString' method
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function prependStreamFilter($filter_name)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
array_unshift($this->stream_filters, $this->sanitizeStreamFilter($filter_name));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize the stream filter name
|
||||||
|
*
|
||||||
|
* @param string $filter_name the stream filter name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function sanitizeStreamFilter($filter_name)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
$filter_name = (string) $filter_name;
|
||||||
|
|
||||||
|
return trim($filter_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the stream filter is already present
|
||||||
|
*
|
||||||
|
* @param string $filter_name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasStreamFilter($filter_name)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
|
||||||
|
return false !== array_search($filter_name, $this->stream_filters, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a filter from the collection
|
||||||
|
*
|
||||||
|
* @param string $filter_name
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function removeStreamFilter($filter_name)
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
$res = array_search($filter_name, $this->stream_filters, true);
|
||||||
|
if (false !== $res) {
|
||||||
|
unset($this->stream_filters[$res]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all registered stream filter
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function clearStreamFilter()
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
$this->stream_filters = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the filter path
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getStreamFilterPath()
|
||||||
|
{
|
||||||
|
$this->assertStreamable();
|
||||||
|
if (! $this->stream_filters) {
|
||||||
|
return $this->stream_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = '';
|
||||||
|
if (STREAM_FILTER_READ == $this->stream_filter_mode) {
|
||||||
|
$prefix = 'read=';
|
||||||
|
} elseif (STREAM_FILTER_WRITE == $this->stream_filter_mode) {
|
||||||
|
$prefix = 'write=';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'php://filter/'.$prefix.implode('|', $this->stream_filters).'/resource='.$this->stream_uri;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Iterator;
|
||||||
|
|
||||||
|
use IteratorIterator;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple MapIterator
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 3.3.0
|
||||||
|
* @internal used internally to modify CSV content
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MapIterator extends IteratorIterator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The function to be apply on all InnerIterator element
|
||||||
|
*
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
private $callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Constructor
|
||||||
|
*
|
||||||
|
* @param Traversable $iterator
|
||||||
|
* @param callable $callable
|
||||||
|
*/
|
||||||
|
public function __construct(Traversable $iterator, callable $callable)
|
||||||
|
{
|
||||||
|
parent::__construct($iterator);
|
||||||
|
$this->callable = $callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the current element
|
||||||
|
*/
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
$iterator = $this->getInnerIterator();
|
||||||
|
$callable = $this->callable;
|
||||||
|
|
||||||
|
return $callable($iterator->current(), $iterator->key(), $iterator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv\Iterator;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use CallbackFilterIterator;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Iterator;
|
||||||
|
use LimitIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Trait to Query rows against a SplFileObject
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 4.2.1
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
trait Query
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Callable function to filter the iterator
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $iterator_filters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callable function to sort the ArrayObject
|
||||||
|
*
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
protected $iterator_sort_by = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iterator Offset
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $iterator_offset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iterator maximum length
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $iterator_limit = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set LimitIterator Offset
|
||||||
|
*
|
||||||
|
* @param $offset
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setOffset($offset = 0)
|
||||||
|
{
|
||||||
|
if (false === filter_var($offset, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
||||||
|
throw new InvalidArgumentException('the offset must be a positive integer or 0');
|
||||||
|
}
|
||||||
|
$this->iterator_offset = $offset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set LimitInterator Count
|
||||||
|
*
|
||||||
|
* @param int $limit
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLimit($limit = -1)
|
||||||
|
{
|
||||||
|
if (false === filter_var($limit, FILTER_VALIDATE_INT, ['options' => ['min_range' => -1]])) {
|
||||||
|
throw new InvalidArgumentException('the limit must an integer greater or equals to -1');
|
||||||
|
}
|
||||||
|
$this->iterator_limit = $limit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the Iterator
|
||||||
|
*
|
||||||
|
* @param \Iterator $iterator
|
||||||
|
*
|
||||||
|
* @return \LimitIterator
|
||||||
|
*/
|
||||||
|
protected function applyIteratorInterval(Iterator $iterator)
|
||||||
|
{
|
||||||
|
if (0 == $this->iterator_offset && -1 == $this->iterator_limit) {
|
||||||
|
return $iterator;
|
||||||
|
}
|
||||||
|
$offset = $this->iterator_offset;
|
||||||
|
$limit = $this->iterator_limit;
|
||||||
|
|
||||||
|
$this->iterator_limit = -1;
|
||||||
|
$this->iterator_offset = 0;
|
||||||
|
|
||||||
|
return new LimitIterator($iterator, $offset, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an Iterator sorting callable function
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addSortBy(callable $callable)
|
||||||
|
{
|
||||||
|
$this->iterator_sort_by[] = $callable;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a callable from the collection
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function removeSortBy(callable $callable)
|
||||||
|
{
|
||||||
|
$res = array_search($callable, $this->iterator_sort_by, true);
|
||||||
|
if (false !== $res) {
|
||||||
|
unset($this->iterator_sort_by[$res]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the callable is already registered
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasSortBy(callable $callable)
|
||||||
|
{
|
||||||
|
return false !== array_search($callable, $this->iterator_sort_by, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all registered callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function clearSortBy()
|
||||||
|
{
|
||||||
|
$this->iterator_sort_by = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the Iterator
|
||||||
|
*
|
||||||
|
* @param \Iterator $iterator
|
||||||
|
*
|
||||||
|
* @return \ArrayIterator
|
||||||
|
*/
|
||||||
|
protected function applyIteratorSortBy(Iterator $iterator)
|
||||||
|
{
|
||||||
|
if (! $this->iterator_sort_by) {
|
||||||
|
return $iterator;
|
||||||
|
}
|
||||||
|
$nb_callbacks = count($this->iterator_sort_by);
|
||||||
|
$this->iterator_sort_by = array_values($this->iterator_sort_by);
|
||||||
|
$res = iterator_to_array($iterator, false);
|
||||||
|
uasort($res, function ($rowA, $rowB) use ($nb_callbacks) {
|
||||||
|
$res = 0;
|
||||||
|
$index = 0;
|
||||||
|
while ($index < $nb_callbacks && 0 === $res) {
|
||||||
|
$res = $this->iterator_sort_by[$index]($rowA, $rowB);
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
$this->clearSortBy();
|
||||||
|
|
||||||
|
return new ArrayIterator($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Iterator filter method
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addFilter(callable $callable)
|
||||||
|
{
|
||||||
|
$this->iterator_filters[] = $callable;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a filter from the callable collection
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function removeFilter(callable $callable)
|
||||||
|
{
|
||||||
|
$res = array_search($callable, $this->iterator_filters, true);
|
||||||
|
if (false !== $res) {
|
||||||
|
unset($this->iterator_filters[$res]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the callable filter is already registered
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasFilter(callable $callable)
|
||||||
|
{
|
||||||
|
return false !== array_search($callable, $this->iterator_filters, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all registered callable filter
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function clearFilter()
|
||||||
|
{
|
||||||
|
$this->iterator_filters = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the Iterator
|
||||||
|
*
|
||||||
|
* @param \Iterator $iterator
|
||||||
|
*
|
||||||
|
* @return \Iterator
|
||||||
|
*/
|
||||||
|
protected function applyIteratorFilter(Iterator $iterator)
|
||||||
|
{
|
||||||
|
foreach ($this->iterator_filters as $callable) {
|
||||||
|
$iterator = new CallbackFilterIterator($iterator, $callable);
|
||||||
|
}
|
||||||
|
$this->clearFilter();
|
||||||
|
|
||||||
|
return $iterator;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv;
|
||||||
|
|
||||||
|
use CallbackFilterIterator;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Iterator;
|
||||||
|
use League\Csv\Iterator\MapIterator;
|
||||||
|
use League\Csv\Iterator\Query;
|
||||||
|
use LimitIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to manage extracting and filtering a CSV
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 3.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Reader extends AbstractCsv
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Iterator Query Trait
|
||||||
|
*/
|
||||||
|
use Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@ihneritdoc}
|
||||||
|
*/
|
||||||
|
protected $stream_filter_mode = STREAM_FILTER_READ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Filtered Iterator
|
||||||
|
*
|
||||||
|
* @param callable $callable a callable function to be applied to each Iterator item
|
||||||
|
*
|
||||||
|
* @return \Traversable
|
||||||
|
*/
|
||||||
|
public function query(callable $callable = null)
|
||||||
|
{
|
||||||
|
$iterator = new CallbackFilterIterator($this->getIterator(), function ($row) {
|
||||||
|
return is_array($row);
|
||||||
|
});
|
||||||
|
|
||||||
|
$iterator = $this->applyIteratorFilter($iterator);
|
||||||
|
$iterator = $this->applyIteratorSortBy($iterator);
|
||||||
|
$iterator = $this->applyIteratorInterval($iterator);
|
||||||
|
if (! is_null($callable)) {
|
||||||
|
$iterator = new MapIterator($iterator, $callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a callback function on the CSV
|
||||||
|
*
|
||||||
|
* The callback function must return TRUE in order to continue
|
||||||
|
* iterating over the iterator.
|
||||||
|
*
|
||||||
|
* @param callable $callable The callback function
|
||||||
|
*
|
||||||
|
* @return int the iteration count
|
||||||
|
*/
|
||||||
|
public function each(callable $callable)
|
||||||
|
{
|
||||||
|
$index = 0;
|
||||||
|
$iterator = $this->query();
|
||||||
|
$iterator->rewind();
|
||||||
|
while ($iterator->valid() && true === $callable($iterator->current(), $iterator->key(), $iterator)) {
|
||||||
|
++$index;
|
||||||
|
$iterator->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a single column from the CSV data
|
||||||
|
*
|
||||||
|
* The callable function will be applied to each value to be return
|
||||||
|
*
|
||||||
|
* @param int $column_index field Index
|
||||||
|
* @param callable $callable a callable function
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the column index is not a positive integer or 0
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function fetchColumn($column_index = 0, callable $callable = null)
|
||||||
|
{
|
||||||
|
if (false === filter_var($column_index, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'the column index must be a positive integer or 0'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$iterator = $this->query($callable);
|
||||||
|
$iterator = new MapIterator($iterator, function ($row) use ($column_index) {
|
||||||
|
if (! array_key_exists($column_index, $row)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row[$column_index];
|
||||||
|
});
|
||||||
|
|
||||||
|
return iterator_to_array($iterator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a single row from the CSV
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the $offset is not a valid Integer
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function fetchOne($offset = 0)
|
||||||
|
{
|
||||||
|
$this->setOffset($offset);
|
||||||
|
$this->setLimit(1);
|
||||||
|
$iterator = $this->query();
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
return (array) $iterator->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a sequential array of all CSV lines
|
||||||
|
*
|
||||||
|
* The callable function will be applied to each Iterator item
|
||||||
|
*
|
||||||
|
* @param callable $callable a callable function
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function fetchAll(callable $callable = null)
|
||||||
|
{
|
||||||
|
return iterator_to_array($this->query($callable), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a sequential array of all CSV lines;
|
||||||
|
*
|
||||||
|
* The rows are presented as associated arrays
|
||||||
|
* The callable function will be applied to each Iterator item
|
||||||
|
*
|
||||||
|
* @param array|int $offset_or_keys the name for each key member OR the row Index to be
|
||||||
|
* used as the associated named keys
|
||||||
|
* @param callable $callable a callable function
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the submitted keys are invalid
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function fetchAssoc($offset_or_keys = 0, callable $callable = null)
|
||||||
|
{
|
||||||
|
$keys = $this->getAssocKeys($offset_or_keys);
|
||||||
|
|
||||||
|
$iterator = $this->query($callable);
|
||||||
|
$iterator = new MapIterator($iterator, function ($row) use ($keys) {
|
||||||
|
return static::combineArray($keys, $row);
|
||||||
|
});
|
||||||
|
|
||||||
|
return iterator_to_array($iterator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the array to be used as key for the fetchAssoc method
|
||||||
|
* @param array|int $offset_or_keys the assoc key OR the row Index to be used
|
||||||
|
* as the key index
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the row index and/or the resulting array is invalid
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getAssocKeys($offset_or_keys)
|
||||||
|
{
|
||||||
|
$res = $offset_or_keys;
|
||||||
|
if (! is_array($res)) {
|
||||||
|
$res = $this->getRow($offset_or_keys);
|
||||||
|
$this->addFilter(function ($row, $rowIndex) use ($offset_or_keys) {
|
||||||
|
return is_array($row) && $rowIndex != $offset_or_keys;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (! $this->isValidAssocKeys($res)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Use a flat non empty array with unique string values'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a single row from the CSV without filtering
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the $offset is not valid or the row does not exist
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getRow($offset)
|
||||||
|
{
|
||||||
|
if (false === filter_var($offset, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
||||||
|
throw new InvalidArgumentException('the row index must be a positive integer or 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
$iterator = new LimitIterator($this->getIterator(), $offset, 1);
|
||||||
|
$iterator->rewind();
|
||||||
|
$res = $iterator->current();
|
||||||
|
if (is_null($res)) {
|
||||||
|
throw new InvalidArgumentException('the specified row does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the array to be used by the fetchAssoc method
|
||||||
|
*
|
||||||
|
* @param array $keys
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
protected function isValidAssocKeys(array $keys)
|
||||||
|
{
|
||||||
|
$validKeys = array_unique(array_filter($keys, function ($value) {
|
||||||
|
return self::isValidString($value);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return count($keys) && $keys === $validKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intelligent Array Combine
|
||||||
|
*
|
||||||
|
* add or remove values from the $value array to
|
||||||
|
* match array $keys length before using PHP array_combine function
|
||||||
|
*
|
||||||
|
* @param array $keys
|
||||||
|
* @param array $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function combineArray(array $keys, array $value)
|
||||||
|
{
|
||||||
|
$nbKeys = count($keys);
|
||||||
|
$diff = $nbKeys - count($value);
|
||||||
|
if ($diff > 0) {
|
||||||
|
$value = array_merge($value, array_fill(0, $diff, null));
|
||||||
|
} elseif ($diff < 0) {
|
||||||
|
$value = array_slice($value, 0, $nbKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_combine($keys, $value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,355 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of the League.csv library
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/MIT
|
||||||
|
* @link https://github.com/thephpleague/csv/
|
||||||
|
* @version 6.3.0
|
||||||
|
* @package League.csv
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace League\Csv;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use OutOfBoundsException;
|
||||||
|
use RuntimeException;
|
||||||
|
use SplFileObject;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to manage data insertion into a CSV
|
||||||
|
*
|
||||||
|
* @package League.csv
|
||||||
|
* @since 4.0.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Writer extends AbstractCsv
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* set null handling mode to throw exception
|
||||||
|
*/
|
||||||
|
const NULL_AS_EXCEPTION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set null handling mode to remove cell
|
||||||
|
*/
|
||||||
|
const NULL_AS_SKIP_CELL = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set null handling mode to convert null into empty string
|
||||||
|
*/
|
||||||
|
const NULL_AS_EMPTY = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the object current null handling mode
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $null_handling_mode = self::NULL_AS_EXCEPTION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of column per row
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $columns_count = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* should the class detect the column count based the inserted row
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $detect_columns_count = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@ihneritdoc}
|
||||||
|
*/
|
||||||
|
protected $stream_filter_mode = STREAM_FILTER_WRITE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CSV object holder
|
||||||
|
*
|
||||||
|
* @var \SplFileObject
|
||||||
|
*/
|
||||||
|
protected $csv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* newline character
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $newline = "\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the class how to handle null value
|
||||||
|
*
|
||||||
|
* @param int $value a Writer null behavior constant
|
||||||
|
*
|
||||||
|
* @throws \OutOfBoundsException If the Integer is not valid
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setNullHandlingMode($value)
|
||||||
|
{
|
||||||
|
if (!in_array($value, [self::NULL_AS_SKIP_CELL, self::NULL_AS_EXCEPTION, self::NULL_AS_EMPTY])) {
|
||||||
|
throw new OutOfBoundsException('invalid value for null handling');
|
||||||
|
}
|
||||||
|
$this->null_handling_mode = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* null handling getter
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getNullHandlingMode()
|
||||||
|
{
|
||||||
|
return $this->null_handling_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the newline sequence characters
|
||||||
|
*
|
||||||
|
* @param string $newline
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setNewline($newline)
|
||||||
|
{
|
||||||
|
$this->newline = (string) $newline;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the current newline sequence characters
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNewline()
|
||||||
|
{
|
||||||
|
return $this->newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Inserted row column count
|
||||||
|
*
|
||||||
|
* @param int $value
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If $value is lesser than -1
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setColumnsCount($value)
|
||||||
|
{
|
||||||
|
if (false === filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -1]])) {
|
||||||
|
throw new InvalidArgumentException('the column count must an integer greater or equals to -1');
|
||||||
|
}
|
||||||
|
$this->detect_columns_count = false;
|
||||||
|
$this->columns_count = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column count getter
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getColumnsCount()
|
||||||
|
{
|
||||||
|
return $this->columns_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method will set the $columns_count property according to the next inserted row
|
||||||
|
* and therefore will also validate the next line whatever length it has no matter
|
||||||
|
* the current $columns_count property value.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function autodetectColumnsCount()
|
||||||
|
{
|
||||||
|
$this->detect_columns_count = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add multiple lines to the CSV your are generating
|
||||||
|
*
|
||||||
|
* a simple helper/Wrapper method around insertOne
|
||||||
|
*
|
||||||
|
* @param \Traversable|array $rows a multidimentional array or a Traversable object
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the given rows format is invalid
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function insertAll($rows)
|
||||||
|
{
|
||||||
|
if (! is_array($rows) && ! $rows instanceof Traversable) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'the provided data must be an array OR a \Traversable object'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$this->insertOne($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new CSV row to the generated CSV
|
||||||
|
*
|
||||||
|
* @param string[]|string $data a string, an array or an object implementing to '__toString' method
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the given row is invalid
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function insertOne($data)
|
||||||
|
{
|
||||||
|
$data = $this->validateRow($data);
|
||||||
|
$data = $this->sanitizeColumnsContent($data);
|
||||||
|
if (! $this->isColumnsCountConsistent($data)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'You are trying to add '.count($data).' columns to a CSV
|
||||||
|
that requires '.$this->columns_count.' columns per row.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$csv = $this->getCsv();
|
||||||
|
$csv->fputcsv($data, $this->delimiter, $this->enclosure);
|
||||||
|
|
||||||
|
if ("\n" !== $this->newline) {
|
||||||
|
$csv->fseek(-1, SEEK_CUR);
|
||||||
|
$csv->fwrite($this->newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the submitted row valid
|
||||||
|
*
|
||||||
|
* @param string[]|string $row
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the given $row is not valid
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function validateRow($row)
|
||||||
|
{
|
||||||
|
if (! is_array($row)) {
|
||||||
|
$row = str_getcsv((string) $row, $this->delimiter, $this->enclosure, $this->escape);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_walk($row, function ($value) {
|
||||||
|
if (! $this->isConvertibleContent($value)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'the values are not convertible into strings'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given value can be added into a CSV cell
|
||||||
|
*
|
||||||
|
* The value MUST respect the null handling mode
|
||||||
|
* The valie MUST be convertible into a string
|
||||||
|
*
|
||||||
|
* @param string|null $value the value to be added
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isConvertibleContent($value)
|
||||||
|
{
|
||||||
|
return (is_null($value) && self::NULL_AS_EXCEPTION != $this->null_handling_mode)
|
||||||
|
|| self::isValidString($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the row according to the null handling behavior
|
||||||
|
*
|
||||||
|
* @param array $row
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function sanitizeColumnsContent(array $row)
|
||||||
|
{
|
||||||
|
if (self::NULL_AS_EXCEPTION == $this->null_handling_mode) {
|
||||||
|
return $row;
|
||||||
|
} elseif (self::NULL_AS_EMPTY == $this->null_handling_mode) {
|
||||||
|
return str_replace(null, '', $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_filter($row, function ($value) {
|
||||||
|
return !is_null($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check column count consistency
|
||||||
|
*
|
||||||
|
* @param array $row the row to be added to the CSV
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isColumnsCountConsistent(array $row)
|
||||||
|
{
|
||||||
|
if ($this->detect_columns_count) {
|
||||||
|
$this->columns_count = count($row);
|
||||||
|
$this->detect_columns_count = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} elseif (-1 == $this->columns_count) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($row) == $this->columns_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the csv container as a SplFileObject instance
|
||||||
|
* insure we use the same object for insertion to
|
||||||
|
* avoid loosing the cursor position
|
||||||
|
*
|
||||||
|
* @return \SplFileObject
|
||||||
|
*/
|
||||||
|
protected function getCsv()
|
||||||
|
{
|
||||||
|
if (is_null($this->csv)) {
|
||||||
|
$this->csv = $this->getIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether the stream filter capabilities can be used
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActiveStreamFilter()
|
||||||
|
{
|
||||||
|
return parent::isActiveStreamFilter() && is_null($this->csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->csv = null;
|
||||||
|
parent::__destruct();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue