CharsetConverter   A
last analyzed

Coupling/Cohesion

Components 1
Dependencies 2

Complexity

Total Complexity 25

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 236
ccs 72
cts 72
cp 1
rs 10
c 0
b 0
f 0
wmc 25
lcom 1
cbo 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
A addTo() 0 6 1
A register() 0 7 2
A getFiltername() 0 9 1
A filterEncoding() 0 15 3
A onCreate() 0 20 4
A filter() 0 10 2
A convert() 0 16 4
A __invoke() 0 6 1
A encodeField() 0 10 3
A inputEncoding() 0 12 2
A outputEncoding() 0 12 2
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.1.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 OutOfRangeException;
18
use php_user_filter;
19
use Traversable;
20
use TypeError;
21
22
/**
23
 * A class to convert resource stream or tabular data content charset
24
 *
25
 * @package League.csv
26
 * @since   9.0.0
27
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 */
29
class CharsetConverter extends php_user_filter
30
{
31
    const FILTERNAME = 'convert.league.csv';
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 records input encoding charset
50
     *
51
     * @var string
52
     */
53
    protected $input_encoding = 'UTF-8';
54
55
    /**
56
     * The records output encoding charset
57
     *
58
     * @var string
59
     */
60
    protected $output_encoding = 'UTF-8';
61
62
    /**
63
     * Static method to add the stream filter to a {@link AbstractCsv} object
64
     *
65
     * @param AbstractCsv $csv
66
     * @param string      $input_encoding
67
     * @param string      $output_encoding
68
     *
69
     * @return AbstractCsv
70
     */
71 2
    public static function addTo(AbstractCsv $csv, string $input_encoding, string $output_encoding): AbstractCsv
72
    {
73 2
        self::register();
74
75 2
        return $csv->addStreamFilter(self::getFiltername($input_encoding, $output_encoding));
76
    }
77
78
    /**
79
     * Static method to register the class as a stream filter
80
     */
81 2
    public static function register()
82
    {
83 2
        $filtername = self::FILTERNAME.'.*';
84 2
        if (!in_array($filtername, stream_get_filters())) {
85 2
            stream_filter_register($filtername, __CLASS__);
86
        }
87 2
    }
88
89
    /**
90
     * Static method to return the stream filter filtername
91
     *
92
     * @param string $input_encoding
93
     * @param string $output_encoding
94
     *
95
     * @return string
96
     */
97 2
    public static function getFiltername(string $input_encoding, string $output_encoding): string
98
    {
99 2
        return sprintf(
100 2
            '%s.%s/%s',
101 2
            self::FILTERNAME,
102 2
            self::filterEncoding($input_encoding),
103 2
            self::filterEncoding($output_encoding)
104
        );
105
    }
106
107
    /**
108
     * Filter encoding charset
109
     *
110
     * @param string $encoding
111
     *
112
     * @throws OutOfRangeException if the charset is malformed or unsupported
113
     *
114
     * @return string
115
     */
116 4
    protected static function filterEncoding(string $encoding): string
117
    {
118 4
        static $encoding_list;
119 4
        if (null === $encoding_list) {
120 2
            $list = mb_list_encodings();
121 2
            $encoding_list = array_combine(array_map('strtolower', $list), $list);
122
        }
123
124 4
        $key = strtolower($encoding);
125 4
        if (isset($encoding_list[$key])) {
126 2
            return $encoding_list[$key];
127
        }
128
129 2
        throw new OutOfRangeException(sprintf('The submitted charset %s is not supported by the mbstring extension', $encoding));
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 8
    public function onCreate()
136
    {
137 8
        $prefix = self::FILTERNAME.'.';
138 8
        if (0 !== strpos($this->filtername, $prefix)) {
139 2
            return false;
140
        }
141
142 6
        $encodings = substr($this->filtername, strlen($prefix));
143 6
        if (!preg_match(',^(?<input>[-\w]+)\/(?<output>[-\w]+)$,', $encodings, $matches)) {
144 2
            return false;
145
        }
146
147
        try {
148 4
            $this->input_encoding = $this->filterEncoding($matches['input']);
149 2
            $this->output_encoding = $this->filterEncoding($matches['output']);
150 2
            return true;
151 2
        } catch (OutOfRangeException $e) {
152 2
            return false;
153
        }
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 2
    public function filter($in, $out, &$consumed, $closing)
160
    {
161 2
        while ($res = stream_bucket_make_writeable($in)) {
162 2
            $res->data = @mb_convert_encoding($res->data, $this->output_encoding, $this->input_encoding);
163 2
            $consumed += $res->datalen;
164 2
            stream_bucket_append($out, $res);
165
        }
166
167 2
        return PSFS_PASS_ON;
168
    }
169
170
    /**
171
     * Convert Csv file into UTF-8
172
     *
173
     * @param array|Traversable $records the CSV records collection
174
     *
175
     * @return array|Traversable
176
     */
177 8
    public function convert($records)
178
    {
179 8
        if (!is_iterable($records)) {
180 2
            throw new TypeError(sprintf('%s() expects argument passed to be iterable, %s given', __METHOD__, gettype($records)));
181
        }
182
183 6
        if ($this->output_encoding === $this->input_encoding) {
184 2
            return $records;
185
        }
186
187 4
        if (is_array($records)) {
188 2
            return array_map($this, $records);
189
        }
190
191 2
        return new MapIterator($records, $this);
192
    }
193
194
    /**
195
     * Enable using the class as a formatter for the {@link Writer}
196
     *
197
     * @param array $record CSV record
198
     *
199
     * @return array
200
     */
201 2
    public function __invoke(array $record): array
202
    {
203 2
        array_walk($record, [$this, 'encodeField']);
204
205 2
        return $record;
206
    }
207
208
    /**
209
     * Walker method to convert the offset and the value of a CSV record field
210
     *
211
     * @param string|null &$value
212
     * @param string|int  &$offset
213
     */
214 2
    protected function encodeField(&$value, &$offset)
215
    {
216 2
        if (null !== $value) {
217 2
            $value = mb_convert_encoding((string) $value, $this->output_encoding, $this->input_encoding);
218
        }
219
220 2
        if (!is_int($offset)) {
221 2
            $offset = mb_convert_encoding((string) $offset, $this->output_encoding, $this->input_encoding);
222
        }
223 2
    }
224
225
    /**
226
     * Sets the records input encoding charset
227
     *
228
     * @param string $encoding
229
     *
230
     * @return self
231
     */
232 8
    public function inputEncoding(string $encoding): self
233
    {
234 8
        $encoding = $this->filterEncoding($encoding);
235 6
        if ($encoding === $this->input_encoding) {
236 4
            return $this;
237
        }
238
239 4
        $clone = clone $this;
240 4
        $clone->input_encoding = $encoding;
241
242 4
        return $clone;
243
    }
244
245
    /**
246
     * Sets the records output encoding charset
247
     *
248
     * @param string $encoding
249
     *
250
     * @return self
251
     */
252 2
    public function outputEncoding(string $encoding): self
253
    {
254 2
        $encoding = $this->filterEncoding($encoding);
255 2
        if ($encoding === $this->output_encoding) {
256 2
            return $this;
257
        }
258
259 2
        $clone = clone $this;
260 2
        $clone->output_encoding = $encoding;
261
262 2
        return $clone;
263
    }
264
}
265