Многобайтовый безопасный фрейд в PHP


У меня есть файл, который слишком велик, чтобы поместиться в память, из которого мне нужно удалить определенные символы (точнее, управляющие символы). Моя текущая функция выглядит так:

$old = fopen($file, 'r');
$new = fopen($tmpFile, 'w');

while (!feof($old)) {
    fwrite($new, preg_replace('/[^\P{Cc}\t\r\n]/u', '', fgets($old)));
}

rename($tmpFile, $file);

В большинстве случаев это прекрасно работает. Возможная проблема, однако, заключается в том, что fgets считывает целую строку. Некоторые файлы, которые я обрабатываю, являются буквально огромными однострочными, что все равно вызовет проблемы с памятью.

Это можно исправить с помощью fread, с размером блока, скажем, 8192. Однако теперь текст, который я передаю preg_replace, может быть обрезанными многобайтовыми символами.

Я думал, как я могу fread сохранить многобайтовые символы, но я еще не нашел хорошего решения. Любая помощь была бы потрясающей.

Возможное решение

Хотя я решил проблему по-другому, мне все еще любопытен мой первоначальный вопрос: как сделать mb-безопасный fread? Я думаю, что такая функция могла бы работать:

  1. Считайте часть байтов с помощью fread
  2. Проверьте последний байт, проверьте, является ли он частью многобайтовой последовательности. Если нет, остановитесь здесь.
  3. Продолжайте считывать байты до тех пор, пока последний байт не перестанет быть частью многобайтовой последовательности или не завершит текущую последовательность.

Шаг 2, вероятно, мог бы использовать некоторую логику , подобную этой , но я не настолько опытен в юникоде, чтобы знать, как это сделать.

Author: Peter Kruithof, 2014-10-16

4 answers

Я пока не могу оставлять комментарии. Но можно было бы прочитать данные по частям, как вы сказали, и использовать unpack ('C*', $chunk), оттуда вы можете повторить массив байтов и найти соответствие для вашего символа в зависимости от последовательности байтов в массиве байтов. Если вы найдете совпадение в этом массиве, замените или удалите эти байты и упакуйте() строку обратно.

P.S.: не забудьте перечитать последние несколько байтов в следующем фрагменте (чтобы у вас не было никаких совпадений с окончательным замененная строка).
Я не знаю, соответствует ли мой пример распаковки вашим предпочтениям, но вы можете прочитать больше здесь: распаковать документ

Вот еще один указатель того, как работает кодировка utf-8 в случае, если вы используете кодировку utf-8: кодировка utf-8

 1
Author: Geo, 2017-05-23 12:14:05

В конце концов, мое решение было довольно простым. Проблема заключалась в использовании preg_replace с возможным отсечением многобайтовых символов, что приводило к неправильным фрагментам.

Поскольку мне нужно было только удалить управляющие символы, которые находятся в диапазоне ASCII и, следовательно, однобайтовые, я могу так же легко сделать str_replace, что оставляет другие байты в покое.

Мое рабочее решение теперь выглядит так:

$old = fopen($file, 'r');
$new = fopen($tmpFile, 'w');

// list control characters, but leave out \t\r\n
$chars = array_map('chr', range(0, 31));
$chars[] = chr(127);
unset($chars[9], $chars[10], $chars[13]);

while (!feof($old)) {
    fwrite($new, str_replace($chars, '', fread($old, 8192)));
}

Хотя это не отвечает на мой первоначальный вопрос (который заключается в том, как сделать mb-безопасный фрейд), это действительно решает мою проблему.

 1
Author: Peter Kruithof, 2014-10-17 08:58:00

Непроверенный. Слишком много, чтобы уместиться в комментарии, но это суть того, к чему я клонил.

$old = fopen($file, 'r');
$new = fopen($tmpFile, 'w');

while (!feof($old)) {
    // Your search subject
    $subject = '';

    // Get $numChars
    for($x = 0, $numChars = 100; $x < $numChars; $x++){
        $subject .= fgetc($old);
    }

    // Replace and write to $new
    fwrite($new, preg_replace('/[^\P{Cc}\t\r\n]/u', '', $subject));

    // Clean out the characters
    $subject = '';
}

rename($tmpFile, $file);
 0
Author: Chad, 2014-10-16 17:33:31

За последние несколько дней я потратил немало часов на поиск многобайтовой безопасной версии PHP fread(), fgetc(), file_get_contents(), и т.д.

К сожалению, я не думаю, что он существует, особенно для очень больших файлов. Итак, я написал свой собственный (к лучшему или худшему):

Jstewmc\Фрагмент\Файл::gEtchunk()

Надеюсь, это не ужасно; это помогает кому-то, кроме меня; и я не выгляжу как самовозвеличивающий придурок, ха-ха.

 0
Author: Jack Clayton, 2015-07-03 22:31:10