Completed
Push — master ( 6f82c7...df6d51 )
by ignace nyamagana
05:17 queued 03:20
created

RFC4180Field::addFormatterTo()   A

Complexity

Conditions 4
Paths 2

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 20
Code Lines 10

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 2
nop 2
dl 0
loc 20
ccs 12
cts 12
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
* This file is part of the League.csv library
4
*
5
* @license http://opensource.org/licenses/MIT
6
* @link https://github.com/thephpleague/csv/
7
* @version 9.0.1
8
* @package League.csv
9
*
10
* For the full copyright and license information, please view the LICENSE
11
* file that was distributed with this source code.
12
*/
13
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use InvalidArgumentException;
18
use php_user_filter;
19
20
/**
21
 * A stream filter to conform the CSV field to RFC4180
22
 *
23
 * @see https://tools.ietf.org/html/rfc4180#section-2
24
 *
25
 * @package League.csv
26
 * @since   9.0.0
27
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 */
29
class RFC4180Field extends php_user_filter
30
{
31
    const FILTERNAME = 'convert.league.csv.rfc4180';
32
33
    /**
34
     * the filter name used to instantiate the class with
35
     *
36
     * @var string
37
     */
38
    public $filtername;
39
40
    /**
41
     * Contents of the params parameter passed to stream_filter_append
42
     * or stream_filter_prepend functions
43
     *
44
     * @var mixed
45
     */
46
    public $params;
47
48
    /**
49
     * The value being search for
50
     *
51
     * @var string[]
52
     */
53
    protected $search;
54
55
    /**
56
     * The replacement value that replace found $search values
57
     *
58
     * @var string[]
59
     */
60
    protected $replace;
61
62
    /**
63
     * Characters that triggers enclosure with PHP fputcsv
64
     *
65
     * @var string
66
     */
67
    protected static $force_enclosure = "\n\r\t ";
68
69
    /**
70
     * Static method to add the stream filter to a {@link AbstractCsv} object
71
     *
72
     * @param AbstractCsv $csv
73
     * @param string      $whitespace_replace
74
     *
75
     * @return AbstractCsv
76
     */
77 10
    public static function addTo(AbstractCsv $csv, string $whitespace_replace = ''): AbstractCsv
78
    {
79 10
        self::register();
80
81
        $params = [
82 10
            'enclosure' => $csv->getEnclosure(),
83 10
            'escape' => $csv->getEscape(),
84 10
            'mode' => $csv->getStreamFilterMode(),
85
        ];
86
87 10
        if ($csv instanceof Writer && '' != $whitespace_replace) {
88 4
            self::addFormatterTo($csv, $whitespace_replace);
89 2
            $params['whitespace_replace'] = $whitespace_replace;
90
        }
91
92 8
        return $csv->addStreamFilter(self::FILTERNAME, $params);
93
    }
94
95
    /**
96
     * Add a formatter to the {@link Writer} object to format the record
97
     * field to avoid enclosure around a field with an empty space
98
     *
99
     * @param Writer $csv
100
     * @param string $whitespace_replace
101
     *
102
     * @return Writer
103
     */
104 4
    public static function addFormatterTo(Writer $csv, string $whitespace_replace): Writer
105
    {
106 4
        if ('' == $whitespace_replace || strlen($whitespace_replace) != strcspn($whitespace_replace, self::$force_enclosure)) {
107 2
            throw new InvalidArgumentException('The sequence contains a character that enforces enclosure or is a CSV control character or is the empty string.');
108
        }
109
110 2
        $mapper = function ($value) use ($whitespace_replace) {
111 2
            if (is_string($value)) {
112 2
                return str_replace(' ', $whitespace_replace, $value);
113
            }
114
115 2
            return $value;
116 2
        };
117
118 2
        $formatter = function (array $record) use ($mapper): array {
119 2
            return array_map($mapper, $record);
120 2
        };
121
122 2
        return $csv->addFormatter($formatter);
123
    }
124
125
    /**
126
     * Static method to register the class as a stream filter
127
     */
128 6
    public static function register()
129
    {
130 6
        if (!in_array(self::FILTERNAME, stream_get_filters())) {
131 2
            stream_filter_register(self::FILTERNAME, __CLASS__);
132
        }
133 6
    }
134
135
    /**
136
     * Static method to return the stream filter filtername
137
     *
138
     * @return string
139
     */
140 4
    public static function getFiltername(): string
141
    {
142 4
        return self::FILTERNAME;
143
    }
144
145
    /**
146
     * @inheritdoc
147
     */
148 8
    public function filter($in, $out, &$consumed, $closing)
149
    {
150 8
        while ($bucket = stream_bucket_make_writeable($in)) {
151 8
            $bucket->data = str_replace($this->search, $this->replace, $bucket->data);
152 8
            $consumed += $bucket->datalen;
153 8
            stream_bucket_append($out, $bucket);
154
        }
155
156 8
        return PSFS_PASS_ON;
157
    }
158
159
    /**
160
     * @inheritdoc
161
     */
162 20
    public function onCreate()
163
    {
164 20
        if (!$this->isValidParams($this->params)) {
165 10
            return false;
166
        }
167
168 8
        $this->search = [$this->params['escape'].$this->params['enclosure']];
169 8
        $this->replace = [$this->params['enclosure'].$this->params['enclosure']];
170 8
        if (STREAM_FILTER_WRITE != $this->params['mode']) {
171 2
            return true;
172
        }
173
174 6
        $this->search = [$this->params['escape'].$this->params['enclosure']];
175 6
        $this->replace = [$this->params['escape'].$this->params['enclosure'].$this->params['enclosure']];
176 6
        if ($this->isValidSequence($this->params)) {
177 2
            $this->search[] = $this->params['whitespace_replace'];
178 2
            $this->replace[] = ' ';
179
        }
180
181 6
        return true;
182
    }
183
184
    /**
185
     * Validate params property
186
     *
187
     * @param array $params
188
     *
189
     * @return bool
190
     */
191 16
    protected function isValidParams(array $params): bool
192
    {
193 16
        static $mode_list = [STREAM_FILTER_READ => 1, STREAM_FILTER_WRITE => 1];
194
195 16
        return isset($params['enclosure'], $params['escape'], $params['mode'], $mode_list[$params['mode']])
196 16
            && 1 == strlen($params['enclosure'])
197 16
            && 1 == strlen($params['escape']);
198
    }
199
200
    /**
201
     * Is Valid White space replaced sequence
202
     *
203
     * @param array $params
204
     *
205
     * @return bool
206
     */
207 2
    protected function isValidSequence(array $params)
208
    {
209 2
        return isset($params['whitespace_replace'])
210 2
            && strlen($params['whitespace_replace']) == strcspn($params['whitespace_replace'], self::$force_enclosure);
211
    }
212
}
213