Completed
Push — master ( ecd0f8...3ad52b )
by ignace nyamagana
15:08
created

RFC4180Field::getFiltername()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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