diff options
author | Ignace Nyamagana Butera <nyamsprod@gmail.com> | 2014-04-22 14:43:11 +0200 |
---|---|---|
committer | Ignace Nyamagana Butera <nyamsprod@gmail.com> | 2014-04-22 16:18:23 +0200 |
commit | bea1bd36976aaa6db5d94967424748758f3ea484 (patch) | |
tree | c68cedc875a7817b9ec1996d23c61deecbb27585 | |
parent | 955236dbb6538f0b91eaf4cb0b62a1e52120434d (diff) | |
download | csv-bea1bd36976aaa6db5d94967424748758f3ea484.zip csv-bea1bd36976aaa6db5d94967424748758f3ea484.tar.gz csv-bea1bd36976aaa6db5d94967424748758f3ea484.tar.bz2 |
adding stream filter trait
-rw-r--r-- | examples/lib/FilterTranscode.php | 56 | ||||
-rw-r--r-- | examples/stream.php | 17 | ||||
-rw-r--r-- | src/AbstractCsv.php | 21 | ||||
-rw-r--r-- | src/Reader.php | 2 | ||||
-rw-r--r-- | src/Stream/StreamFilter.php | 175 | ||||
-rw-r--r-- | src/Writer.php | 2 | ||||
-rw-r--r-- | test/CsvTest.php | 53 |
7 files changed, 322 insertions, 4 deletions
diff --git a/examples/lib/FilterTranscode.php b/examples/lib/FilterTranscode.php new file mode 100644 index 0000000..30cf1ca --- /dev/null +++ b/examples/lib/FilterTranscode.php @@ -0,0 +1,56 @@ +<?php + +namespace lib; + +class FilterTranscode extends php_user_filter +{ + private static $name = 'convert.transcode.'; + + private $encoding_from = 'auto'; + + private $encoding_to; + + public function onCreate() + { + if (strpos($this->filtername, self::$name) !== 0) { + return false; + } + + $params = substr($this->filtername, strlen(self::$name)); + if (! preg_match('/^([-\w]+)(:([-\w]+))?$/', $params, $matches)) { + return false; + } + + if (isset($matches[1])) { + $this->encoding_from = $matches[1]; + } + + $this->encoding_to = mb_internal_encoding(); + if (isset($matches[3])) { + $this->encoding_to = $matches[3]; + } + + $this->params['locale'] = setlocale(LC_CTYPE, '0'); + if (stripos($this->params['locale'], 'UTF-8') === false) { + setlocale(LC_CTYPE, 'en_US.UTF-8'); + } + + return true; + } + + public function onClose() + { + setlocale(LC_CTYPE, $this->params['locale']); + } + + public function filter($in, $out, &$consumed, $closing) + { + while ($res = stream_bucket_make_writeable($in)) { + $res->data = @mb_convert_encoding($res->data, $this->encoding_to, $this->encoding_from); + $consumed += $res->datalen; + stream_bucket_append($out, $res); + } + + return PSFS_PASS_ON; + } +} diff --git a/examples/stream.php b/examples/stream.php new file mode 100644 index 0000000..927e22e --- /dev/null +++ b/examples/stream.php @@ -0,0 +1,17 @@ +<?php + +use League\Csv\Reader; +use lib\FilterTranscode; + +require '../vendor/autoload.php'; +require 'lib/FilterTranscode.php'; + +stream_filter_register(FilterTranscode::$name."*", "FilterTranscode"); + +$reader = new Reader('path/to/chinese/file.csv'); +$reader->appendStreamFilter(FilterTranscode::$name."big5:utf8"); +$reader->setOffset(1); +$reader->setLimit(10); +$res = $reader->fetchAll(); + +print_r($res); //the data is transcoded by the Stream Filter from BIG5 to UTF-8 diff --git a/src/AbstractCsv.php b/src/AbstractCsv.php index 56f46f9..c6fcf71 100644 --- a/src/AbstractCsv.php +++ b/src/AbstractCsv.php @@ -46,6 +46,7 @@ use IteratorAggregate; use LimitIterator; use CallbackFilterIterator; use League\Csv\Iterator\MapIterator; +use League\Csv\Stream\StreamFilter; /** * An abstract class to enable basic CSV manipulation @@ -57,6 +58,8 @@ use League\Csv\Iterator\MapIterator; abstract class AbstractCsv implements JsonSerializable, IteratorAggregate { + use StreamFilter; + /** * The CSV object holder * @@ -342,7 +345,7 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate * @throws \InvalidArgumentException If the $file is not set * @throws \RuntimeException If the $file could not be created and/or opened */ - public function setIterator() + protected function setIterator() { if (! is_null($this->csv)) { return $this; @@ -355,14 +358,24 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate } if ($this->path instanceof SplFileInfo) { - $this->csv = $this->path->openFile($this->open_mode); + $path = $this->getStreamFilterPath($this->path); + if (! isset($path)) { + $this->csv = $this->path->openFile($this->open_mode); + + return $this; + } + $this->csv = new SplFileObject($path, $this->open_mode); return $this; - } elseif (is_string($this->path)) { - $this->csv = new SplFileObject($this->path, $this->open_mode); + } + + if (is_string($this->path)) { + + $this->csv = new SplFileObject($this->getStreamFilterPath($this->path), $this->open_mode); return $this; } + throw new InvalidArgumentException( '$path must be a `SplFileInfo` object or a valid file path.' ); diff --git a/src/Reader.php b/src/Reader.php index 15056ac..0ee9038 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -52,6 +52,8 @@ class Reader extends AbstractCsv */ use IteratorQuery; + protected $stream_filter_mode = STREAM_FILTER_READ; + /** * Intelligent Array Combine * diff --git a/src/Stream/StreamFilter.php b/src/Stream/StreamFilter.php new file mode 100644 index 0000000..6309c39 --- /dev/null +++ b/src/Stream/StreamFilter.php @@ -0,0 +1,175 @@ +<?php +/** +* League.csv - A CSV data manipulation library +* +* @author Ignace Nyamagana Butera <nyamsprod@gmail.com> +* @copyright 2014 Ignace Nyamagana Butera +* @link https://github.com/thephpleague/csv/ +* @license http://opensource.org/licenses/MIT +* @version 5.5.0 +* @package League.csv +* +* MIT LICENSE +* +* 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. +*/ +namespace League\Csv\Stream; + +use InvalidArgumentException; +use SplFileInfo; +use League\Csv\AbstractCsv; + +/** + * A Trait to add ease manipulation Stream Filters + * + * @package League.csv + * @since 5.5.0 + * + */ +trait StreamFilter +{ + /** + * collection of stream filters + * + * @var array + */ + protected $stream_filters = []; + + /** + * Stream filtering mode to apply on all filters + * + * @var integer + */ + protected $stream_filter_mode = STREAM_FILTER_ALL; + + /** + * prepend a stream filter + * + * @param mixed $filter_name a string or an object that implements the '__toString' method + * + * @return self + * + * @throws \InvalidArgumentException If what you try to add is invalid + */ + public function prependStreamFilter($filter_name) + { + if (AbstractCsv::isValidString($filter_name)) { + array_unshift($this->stream_filters, (string) $filter_name); + + return $this; + } + + throw new InvalidArgumentException( + 'you must submit a string, or a method that implements the `__toString method`' + ); + } + + /** + * append a stream filter + * + * @param mixed $filter_name a string or an object that implements the '__toString' method + * + * @return self + * + * @throws \InvalidArgumentException If what you try to add is invalid + */ + public function appendStreamFilter($filter_name) + { + if (AbstractCsv::isValidString($filter_name)) { + $this->stream_filters[] = (string) $filter_name; + + return $this; + } + + throw new InvalidArgumentException( + 'you must submit a string, or a method that implements the `__toString method`' + ); + } + + /** + * Detect if the stream filter is already present + * + * @param string $filter_name + * + * @return boolean + */ + public function hasStreamFilter($filter_name) + { + return false !== array_search($filter_name, $this->stream_filters, true); + } + + /** + * Remove a filter from the collection + * + * @param string $filter_name + * + * @return self + */ + public function removeStreamFilter($filter_name) + { + $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 self + */ + public function clearStreamFilter() + { + $this->stream_filters = []; + + return $this; + } + + /** + * Return the filter path + * + * @param mixed $path a SplFileInfo object or a string + * + * @return string + */ + protected function getStreamFilterPath($path) + { + if ($path instanceof SplFileInfo) { + $path = $path->getRealPath(); + } + $path = trim($path); + if (! is_string($path) || empty($path)) { + return null; + } elseif (! $this->stream_filters) { + return $path; + } + + $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='.$path; + } +} diff --git a/src/Writer.php b/src/Writer.php index cf7d2f3..f4c0b2a 100644 --- a/src/Writer.php +++ b/src/Writer.php @@ -82,6 +82,8 @@ class Writer extends AbstractCsv */ protected $detect_columns_count = false; + protected $stream_filter_mode = STREAM_FILTER_WRITE; + /** * Tell the class how to handle null value * diff --git a/test/CsvTest.php b/test/CsvTest.php index c3ea7d3..d4296ab 100644 --- a/test/CsvTest.php +++ b/test/CsvTest.php @@ -40,6 +40,9 @@ class CsvTest extends PHPUnit_Framework_TestCase $csv = new Reader(new SplFileInfo($path)); $this->assertSame($path, $csv->getIterator()->getRealPath()); + + $csv = new Reader(new SplFileInfo('php://input')); + $this->assertFalse($csv->getIterator()->getRealPath()); } public function testConstructorWithFilePath() @@ -254,4 +257,54 @@ EOF; { return [[file_get_contents(__DIR__.'/data/prenoms.csv')]]; } + + /** + * @expectedException InvalidArgumentException + */ + public function testaddStreamFilter() + { + $path = __DIR__.'/foo.csv'; + $csv = new Reader(new SplFileInfo($path)); + $csv->appendStreamFilter('string.toupper'); + foreach ($csv->getIterator() as $row) { + $this->assertSame($row, ['JOHN', 'DOE', 'JOHN.DOE@EXAMPLE.COM']); + } + $csv->appendStreamFilter(new DateTime); + } + + public function testaddMultipleStreamFilter() + { + $path = __DIR__.'/foo.csv'; + $csv = new Reader(new SplFileInfo($path)); + $csv->appendStreamFilter('string.rot13'); + $csv->appendStreamFilter('string.toupper'); + $this->assertTrue($csv->hasStreamFilter('string.rot13')); + $csv->removeStreamFilter('string.rot13'); + $this->assertFalse($csv->hasStreamFilter('string.rot13')); + $csv->prependStreamFilter('string.rot13'); + foreach ($csv->getIterator() as $row) { + $this->assertSame($row, ['WBUA', 'QBR', 'WBUA.QBR@RKNZCYR.PBZ']); + } + $csv->clearStreamFilter(); + $this->assertFalse($csv->hasStreamFilter('string.tolower')); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetFilterPath() + { + $csv = new Reader(new SplTempFileObject); + $csv->prependStreamFilter('string.rot13'); + $csv->appendStreamFilter('string.toupper'); + $this->assertFalse($csv->getIterator()->getRealPath()); + + $path = __DIR__.'/foo.csv'; + $csv = new Writer(new SplFileInfo($path)); + $csv->prependStreamFilter('string.rot13'); + $csv->appendStreamFilter('string.toupper'); + $this->assertFalse($csv->getIterator()->getRealPath()); + + $csv->prependStreamFilter(['string.rot13']); + } } |