Polyfill
10 months ago
AbstractCsv.php
10 months ago
ByteSequence.php
10 months ago
CannotInsertRecord.php
10 months ago
CharsetConverter.php
10 months ago
ColumnConsistency.php
10 months ago
EncloseField.php
10 months ago
EscapeFormula.php
10 months ago
Exception.php
10 months ago
HTMLConverter.php
10 months ago
Info.php
10 months ago
InvalidArgument.php
10 months ago
MapIterator.php
10 months ago
RFC4180Field.php
10 months ago
Reader.php
10 months ago
ResultSet.php
10 months ago
Statement.php
10 months ago
Stream.php
10 months ago
SyntaxError.php
10 months ago
TabularDataReader.php
10 months ago
UnableToProcessCsv.php
10 months ago
UnavailableFeature.php
10 months ago
UnavailableStream.php
10 months ago
Writer.php
10 months ago
XMLConverter.php
10 months ago
functions.php
10 months ago
functions_include.php
10 months ago
CharsetConverter.php
198 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * League.Csv (https://csv.thephpleague.com) |
| 5 | * |
| 6 | * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | declare (strict_types=1); |
| 12 | namespace IAWPSCOPED\League\Csv; |
| 13 | |
| 14 | use OutOfRangeException; |
| 15 | use php_user_filter; |
| 16 | use function array_combine; |
| 17 | use function array_map; |
| 18 | use function in_array; |
| 19 | use function is_numeric; |
| 20 | use function mb_convert_encoding; |
| 21 | use function mb_list_encodings; |
| 22 | use function preg_match; |
| 23 | use function sprintf; |
| 24 | use function stream_bucket_append; |
| 25 | use function stream_bucket_make_writeable; |
| 26 | use function stream_filter_register; |
| 27 | use function stream_get_filters; |
| 28 | use function strpos; |
| 29 | use function strtolower; |
| 30 | use function substr; |
| 31 | /** |
| 32 | * Converts resource stream or tabular data content charset. |
| 33 | * @internal |
| 34 | */ |
| 35 | class CharsetConverter extends php_user_filter |
| 36 | { |
| 37 | const FILTERNAME = 'convert.league.csv'; |
| 38 | /** |
| 39 | * The records input encoding charset. |
| 40 | * |
| 41 | * @var string |
| 42 | */ |
| 43 | protected $input_encoding = 'UTF-8'; |
| 44 | /** |
| 45 | * The records output encoding charset. |
| 46 | * |
| 47 | * @var string |
| 48 | */ |
| 49 | protected $output_encoding = 'UTF-8'; |
| 50 | /** |
| 51 | * Static method to add the stream filter to a {@link AbstractCsv} object. |
| 52 | */ |
| 53 | public static function addTo(AbstractCsv $csv, string $input_encoding, string $output_encoding) : AbstractCsv |
| 54 | { |
| 55 | self::register(); |
| 56 | return $csv->addStreamFilter(self::getFiltername($input_encoding, $output_encoding)); |
| 57 | } |
| 58 | /** |
| 59 | * Static method to register the class as a stream filter. |
| 60 | */ |
| 61 | public static function register() : void |
| 62 | { |
| 63 | $filtername = self::FILTERNAME . '.*'; |
| 64 | if (!in_array($filtername, stream_get_filters(), \true)) { |
| 65 | stream_filter_register($filtername, self::class); |
| 66 | } |
| 67 | } |
| 68 | /** |
| 69 | * Static method to return the stream filter filtername. |
| 70 | */ |
| 71 | public static function getFiltername(string $input_encoding, string $output_encoding) : string |
| 72 | { |
| 73 | return sprintf('%s.%s/%s', self::FILTERNAME, self::filterEncoding($input_encoding), self::filterEncoding($output_encoding)); |
| 74 | } |
| 75 | /** |
| 76 | * Filter encoding charset. |
| 77 | * |
| 78 | * @throws OutOfRangeException if the charset is malformed or unsupported |
| 79 | */ |
| 80 | protected static function filterEncoding(string $encoding) : string |
| 81 | { |
| 82 | static $encoding_list; |
| 83 | if (null === $encoding_list) { |
| 84 | $list = mb_list_encodings(); |
| 85 | $encoding_list = array_combine(array_map('strtolower', $list), $list); |
| 86 | } |
| 87 | $key = strtolower($encoding); |
| 88 | if (isset($encoding_list[$key])) { |
| 89 | return $encoding_list[$key]; |
| 90 | } |
| 91 | throw new OutOfRangeException('The submitted charset ' . $encoding . ' is not supported by the mbstring extension.'); |
| 92 | } |
| 93 | /** |
| 94 | * {@inheritdoc} |
| 95 | */ |
| 96 | public function onCreate() : bool |
| 97 | { |
| 98 | $prefix = self::FILTERNAME . '.'; |
| 99 | if (0 !== strpos($this->filtername, $prefix)) { |
| 100 | return \false; |
| 101 | } |
| 102 | $encodings = substr($this->filtername, \strlen($prefix)); |
| 103 | if (1 !== preg_match(',^(?<input>[-\\w]+)\\/(?<output>[-\\w]+)$,', $encodings, $matches)) { |
| 104 | return \false; |
| 105 | } |
| 106 | try { |
| 107 | $this->input_encoding = self::filterEncoding($matches['input']); |
| 108 | $this->output_encoding = self::filterEncoding($matches['output']); |
| 109 | } catch (OutOfRangeException $e) { |
| 110 | return \false; |
| 111 | } |
| 112 | return \true; |
| 113 | } |
| 114 | /** |
| 115 | * @param resource $in |
| 116 | * @param resource $out |
| 117 | * @param int $consumed |
| 118 | * @param bool $closing |
| 119 | */ |
| 120 | public function filter($in, $out, &$consumed, $closing) : int |
| 121 | { |
| 122 | while ($res = stream_bucket_make_writeable($in)) { |
| 123 | $res->data = @mb_convert_encoding($res->data, $this->output_encoding, $this->input_encoding); |
| 124 | $consumed += $res->datalen; |
| 125 | stream_bucket_append($out, $res); |
| 126 | } |
| 127 | return \PSFS_PASS_ON; |
| 128 | } |
| 129 | /** |
| 130 | * Convert Csv records collection into UTF-8. |
| 131 | */ |
| 132 | public function convert(iterable $records) : iterable |
| 133 | { |
| 134 | if ($this->output_encoding === $this->input_encoding) { |
| 135 | return $records; |
| 136 | } |
| 137 | if (\is_array($records)) { |
| 138 | return array_map($this, $records); |
| 139 | } |
| 140 | /* @var \Traversable $records */ |
| 141 | return new MapIterator($records, $this); |
| 142 | } |
| 143 | /** |
| 144 | * Enable using the class as a formatter for the {@link Writer}. |
| 145 | */ |
| 146 | public function __invoke(array $record) : array |
| 147 | { |
| 148 | $outputRecord = []; |
| 149 | foreach ($record as $offset => $value) { |
| 150 | [$newOffset, $newValue] = $this->encodeField($value, $offset); |
| 151 | $outputRecord[$newOffset] = $newValue; |
| 152 | } |
| 153 | return $outputRecord; |
| 154 | } |
| 155 | /** |
| 156 | * Walker method to convert the offset and the value of a CSV record field. |
| 157 | * |
| 158 | * @param int|float|string|null $value can be a scalar type or null |
| 159 | * @param int|string $offset can be a string or an int |
| 160 | */ |
| 161 | protected function encodeField($value, $offset) : array |
| 162 | { |
| 163 | if (null !== $value && !is_numeric($value)) { |
| 164 | $value = mb_convert_encoding($value, $this->output_encoding, $this->input_encoding); |
| 165 | } |
| 166 | if (!is_numeric($offset)) { |
| 167 | $offset = mb_convert_encoding($offset, $this->output_encoding, $this->input_encoding); |
| 168 | } |
| 169 | return [$offset, $value]; |
| 170 | } |
| 171 | /** |
| 172 | * Sets the records input encoding charset. |
| 173 | */ |
| 174 | public function inputEncoding(string $encoding) : self |
| 175 | { |
| 176 | $encoding = self::filterEncoding($encoding); |
| 177 | if ($encoding === $this->input_encoding) { |
| 178 | return $this; |
| 179 | } |
| 180 | $clone = clone $this; |
| 181 | $clone->input_encoding = $encoding; |
| 182 | return $clone; |
| 183 | } |
| 184 | /** |
| 185 | * Sets the records output encoding charset. |
| 186 | */ |
| 187 | public function outputEncoding(string $encoding) : self |
| 188 | { |
| 189 | $encoding = self::filterEncoding($encoding); |
| 190 | if ($encoding === $this->output_encoding) { |
| 191 | return $this; |
| 192 | } |
| 193 | $clone = clone $this; |
| 194 | $clone->output_encoding = $encoding; |
| 195 | return $clone; |
| 196 | } |
| 197 | } |
| 198 |