Completed
Push — master ( 32377d...16aae2 )
by ignace nyamagana
04:12
created

CharsetConverter::filterEncoding()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 4
nop 1
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 3
rs 9.4285
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.0
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 ArrayIterator;
18
use Iterator;
19
use League\Csv\Exception\InvalidArgumentException;
20
use php_user_filter;
21
use Traversable;
22
23
/**
24
 *  A class to convert your CSV records collection charset
25
 *
26
 * @package League.csv
27
 * @since   9.0.0
28
 * @author  Ignace Nyamagana Butera <[email protected]>
29
 */
30
class CharsetConverter extends php_user_filter
31
{
32
    use ValidatorTrait;
33
34
    const STREAM_FILTERNAME = 'convert.league.csv';
35
36
    /**
37
     * The records input encoding charset
38
     *
39
     * @var string
40
     */
41
    protected $input_encoding = 'UTF-8';
42
43
    /**
44
     * The records output encoding charset
45
     *
46
     * @var string
47
     */
48
    protected $output_encoding = 'UTF-8';
49
50
    /**
51
     * Static method to register the class as a PHP stream filter
52
     *
53
     * @return bool
54
     */
55 4
    public static function registerStreamFilter(): bool
56
    {
57 4
        static $is_registered;
58
59 4
        if (null === $is_registered) {
60 2
            $is_registered = stream_filter_register(self::STREAM_FILTERNAME.'.*', CharsetConverter::class);
61
        }
62
63 4
        return $is_registered;
64
    }
65
66
    /**
67
     * Static method to format the filtername to be used with stream_filter_append
68
     *
69
     * @param string $input_encoding
70
     * @param string $output_encoding
71
     *
72
     * @return string
73
     */
74 2
    public static function getFiltername(string $input_encoding, string $output_encoding): string
75
    {
76 2
        return sprintf(
77 2
            '%s.%s/%s',
78 2
            self::STREAM_FILTERNAME,
79 2
            self::filterEncoding($input_encoding),
80 2
            self::filterEncoding($output_encoding)
81
        );
82
    }
83
84
    /**
85
     * Filter encoding charset
86
     *
87
     * @param string $encoding
88
     *
89
     * @throws InvalidArgumentException if the charset is malformed or unsupported
90
     *
91
     * @return string
92
     */
93 10
    protected static function filterEncoding(string $encoding): string
94
    {
95 10
        static $encoding_list;
96 10
        if (null === $encoding_list) {
97 2
            $list = mb_list_encodings();
98 2
            $encoding_list = array_combine(array_map('strtolower', $list), $list);
99
        }
100
101 10
        $key = strtolower($encoding);
102 10
        if (isset($encoding_list[$key])) {
103 8
            return $encoding_list[$key];
104
        }
105
106 2
        throw new InvalidArgumentException(sprintf('Unsupported charset %s', $encoding));
107
    }
108
109
    /**
110
     * @inherit
111
     */
112 2
    public function filter($in, $out, &$consumed, $closing)
113
    {
114 2
        while ($res = stream_bucket_make_writeable($in)) {
115 2
            $res->data = @mb_convert_encoding($res->data, $this->output_encoding, $this->input_encoding);
116 2
            $consumed += $res->datalen;
117 2
            stream_bucket_append($out, $res);
118
        }
119
120 2
        return PSFS_PASS_ON;
121
    }
122
123
    /**
124
     * @inherit
125
     */
126 4
    public function onCreate()
127
    {
128 4
        $encodings = substr($this->filtername, strlen(self::STREAM_FILTERNAME) + 1);
129 4
        if (!preg_match(',^(?<input>[-\w]+)\/(?<output>[-\w]+)$,', $encodings, $matches)) {
130 2
            return false;
131
        }
132
133 2
        $this->input_encoding = $this->filterEncoding($matches['input']);
134 2
        $this->output_encoding = $this->filterEncoding($matches['output']);
135 2
        return true;
136
    }
137
138
    /**
139
     * Enable using the class as a formatter for the {@link Writer}
140
     *
141
     * @param array $record CSV record
142
     *
143
     * @return string[]
144
     */
145 2
    public function __invoke(array $record): array
146
    {
147 2
        if ($this->output_encoding !== $this->input_encoding) {
148 2
            array_walk($record, [$this, 'encodeField']);
149
        }
150
151 2
        return $record;
152
    }
153
154
    /**
155
     * Walker method to convert the offset and the value of a CSV record field
156
     *
157
     * @param string|null &$value
158
     * @param string|int  &$offset
159
     */
160 4
    protected function encodeField(&$value, &$offset)
161
    {
162 4
        if (null !== $value) {
163 4
            $value = mb_convert_encoding((string) $value, $this->output_encoding, $this->input_encoding);
164
        }
165
166 4
        if (!is_int($offset)) {
167 4
            $offset = mb_convert_encoding((string) $offset, $this->output_encoding, $this->input_encoding);
168
        }
169 4
    }
170
171
    /**
172
     * Convert Csv file into UTF-8
173
     *
174
     * @param array|Traversable $records the CSV records collection
175
     *
176
     * @return Iterator
177
     */
178 4
    public function convert($records): Iterator
179
    {
180 4
        $records = $this->filterIterable($records);
181 4
        if (is_array($records)) {
182 2
            $records = new ArrayIterator($records);
183
        }
184
185 4
        if ($this->output_encoding === $this->input_encoding) {
186 2
            return $records;
187
        }
188
189 2
        $convert = function (array $record): array {
190 2
            array_walk($record, [$this, 'encodeField']);
191 2
            return $record;
192 1
        };
193
194 2
        return new MapIterator($records, $convert);
195
    }
196
197
    /**
198
     * Sets the records input encoding charset
199
     *
200
     * @param string $encoding
201
     *
202
     * @return self
203
     */
204 6
    public function inputEncoding(string $encoding): self
205
    {
206 6
        $encoding = $this->filterEncoding($encoding);
207 4
        if ($encoding === $this->input_encoding) {
208 2
            return $this;
209
        }
210
211 2
        $clone = clone $this;
212 2
        $clone->input_encoding = $encoding;
213
214 2
        return $clone;
215
    }
216
217
    /**
218
     * Sets the records output encoding charset
219
     *
220
     * @param string $encoding
221
     *
222
     * @return self
223
     */
224 4
    public function outputEncoding(string $encoding): self
225
    {
226 4
        $encoding = $this->filterEncoding($encoding);
227 4
        if ($encoding === $this->output_encoding) {
228 2
            return $this;
229
        }
230
231 4
        $clone = clone $this;
232 4
        $clone->output_encoding = $encoding;
233
234 4
        return $clone;
235
    }
236
}
237