summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgnace Nyamagana Butera <nyamsprod@gmail.com>2014-04-22 14:43:11 +0200
committerIgnace Nyamagana Butera <nyamsprod@gmail.com>2014-04-22 16:18:23 +0200
commitbea1bd36976aaa6db5d94967424748758f3ea484 (patch)
treec68cedc875a7817b9ec1996d23c61deecbb27585
parent955236dbb6538f0b91eaf4cb0b62a1e52120434d (diff)
downloadcsv-bea1bd36976aaa6db5d94967424748758f3ea484.zip
csv-bea1bd36976aaa6db5d94967424748758f3ea484.tar.gz
csv-bea1bd36976aaa6db5d94967424748758f3ea484.tar.bz2
adding stream filter trait
-rw-r--r--examples/lib/FilterTranscode.php56
-rw-r--r--examples/stream.php17
-rw-r--r--src/AbstractCsv.php21
-rw-r--r--src/Reader.php2
-rw-r--r--src/Stream/StreamFilter.php175
-rw-r--r--src/Writer.php2
-rw-r--r--test/CsvTest.php53
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']);
+ }
}