у меня есть csv
файлы в utf-16le
кодирование с BOM
, Они могут быть довольно большими, поэтому мне не очень нравится идея чтения целых файлов в памяти. И вопрос, очевидно, как мне их читать?
Читайте это построчно и используйте mb_convert_encoding ():
$decoded_line = mb_convert_encoding ($line, "UTF-8", "UTF-16LE");
Вы можете выбрать любую кодировку назначения, но я предполагаю, что вы хотите работать со строками utf-8, которые являются наиболее распространенными в настоящее время.
Эта функция нуждается в MBstring расширение должно быть включено.
Затем вы можете передать декодированную строку str_getcsv функция, которая возвращает массив, представляющий текущую строку.
Вот что я придумал:
class readutf16le_filter extends php_user_filter {
function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
# printf("filter: %s\n", to_hex($bucket->data));
$bucket->data = iconv('UTF-16LE', 'UTF-8',
strlen($bucket->data) && substr($bucket->data, 0, 2) == "\xff\xfe"? substr($bucket->data, 2)
: $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register('readutf16le', 'readutf16le_filter');
$fh = fopen('1.txt', 'r');
stream_filter_append($fh, 'readutf16le');
$s = fgets($fh);
printf("%s\n", to_hex($s));
$s = fgets($fh);
printf("%s\n", to_hex($s));
$s = fgets($fh);
var_dump($s);
1.txt
:
a
b
Выход:
filter: ff fe 61 00 0d 00 0a 00 62 00 0d 00 0a 00
61 0d 0a
62 0d 0a
bool(false)
Что мне до сих пор не нравится, так это то, что я не вижу способа обнаружить начало файла в фильтре. Однако вряд ли это вызовет проблемы. Википедия говорит:
Использование спецификации является необязательным, и, если оно используется, должно отображаться в начале текстового потока.
Если символ спецификации появляется в середине потока данных, Unicode говорит, что его следует интерпретировать как «неразрывный пробел нулевой ширины» (запрещает разрыв строки между глифами слов). В Unicode 3.2 это использование не рекомендуется в пользу символа «Word Joiner», U + 2060. [1] Это позволяет использовать U + FEFF только в качестве спецификации.
Для зарегистрированных кодировок IANA UTF-16BE и UTF-16LE знак порядка байтов не должен использоваться, поскольку имена этих наборов символов уже определяют порядок байтов. Если встречается где-либо в таком текстовом потоке, U + FEFF следует интерпретировать как «пробел нулевой ширины без перерывов».
Вероятно, это можно сделать с помощью обертки потока. UPD Можно, наверное, сделать fread($fh, 2);
перед добавлением фильтра в поток.
И другая возможная проблема заключается в том, что strlen($bucket->data)
теоретически может быть нечетным числом. Из того, что я могу сказать, php
использует буферизацию, и вряд ли он попадет в буфер с нечетным числом (обычно они имеют степень 2). Но для размещения таких случаев:
...
while ($bucket = stream_bucket_make_writeable($in)) {
$data = strlen($bucket->data) ?
substr($bucket->data, 0, floor(strlen($bucket->data) / 2) * 2) : '';
$bucket->data = iconv('UTF-16LE', 'UTF-8',
strlen($data) && substr($data, 0, 2) == "\xff\xfe"? substr($data, 2)
: $data);
$consumed += strlen($data);
stream_bucket_append($out, $bucket);
...
Я не знаю, как воспроизвести это все же.