32

Вопрос по unicode, regex, php – Многобайтовая обрезка в PHP?

По-видимому, нетmb_trim вmb_* familyпоэтому я пытаюсь реализовать один для себя.

Я недавно нашел это регулярное выражение в комментарии вphp.net:

/(^\s+)|(\s+$)/u

Итак, я реализую это следующим образом:

function multibyte_trim($str)
{
    if (!function_exists("mb_trim") || !extension_loaded("mbstring")) {
        return preg_replace("/(^\s+)|(\s+$)/u", "", $str);
    } else {
        return mb_trim($str);
    }
}

Регулярное выражение кажется мне верным, но я крайне неравнодушен к регулярным выражениям. Будет ли это эффективно удалитьany Unicode пробел в начале / конце строки?

  • Хорошо поймал! Спасибо.

    от
  • Было бы лучше использовать не захватывающие подпаттеры.us1.php.net/manual/en/regexp.reference.subpatterns.php , У них есть форма(?: )

    от
  • @Incognito Модификатор / u информирует preg о юникоде

    от
  • Это должно быть немного изменено. Ваша строка $ charlist = preg_quote должна находиться внутри else, иначе проверка is_null ($ charlist) никогда не работает.

    от
  • Я редактировал @deceze, смотрите о/\s/uневерно утверждать, что "будет соответствовать только ASCII" (поскольку & amp; # 160; не является ASCII), можете ли вы исправить это в своем ответе? Около\p{Z}Извините, что я не процитировал это в моей редакции, это хорошо запомнить (!).

    от
  • trim($s,'') а такжеtrim($s,' ') работает отлично (!). Зачем намmb_trim()?

    от
  • Это не дляmb_trim('000foo000', '0')... :-3

    от
  • Знают ли pregs в PHP о различных кодировках? Я не могу вспомнить, но я знаю, что с ними когда-то была проблема, и я думаю, что это было здесь.

    от
  • Мне нравится ваш способ, но не забывайте предварительно цитировать ваш $ charlist :)

    от
  • К сожалению & # x2013; неверно говорить "работай нормально" без полного теста вы правильно скажете & quot;trim($s,$utf8) неправильно! & quot; & # x2013 ;, я предлагаю сказать это в тексте ответа. Что касается моего другого комментария, я думаю, что текст ответа & quot;\s в основном будут соответствовать одним и тем же символам & quot;is wrong: пожалуйста, проверьте самиpreg_replace('/\s/u', '',$s) когда$s = html_entity_decode(" Hello   "); фонтаны UTF8NBSP.

    от
  • Исправлено это @deceze. Благодарю.

    от
  • Действительно, обрезка символов ASCII от строки UTF-8 безопасна, но обрезка символов UTF-8 от строки - нет. Это потому, чтоtrim "не понимает" & quot; быть одним символом, но тремя байтами, и он урежет любой из этих трех байтовindividually когда встретил @Питер

    от
  • trim($s,'') а такжеtrim($s,' ') работает отлично (!). Во втором примере ASCII-символ работает вместе ... Итак, мы можем сказать,"trim() function is UTF8 safe" но не & quot;trim() это ASCII, так же как и UTF8 ". Люди путают/\s/ а также/\s/u где только последний обнаруживает NBSP.

    от
  • Я думаю, что я буду придерживатьсяtrim тогда спасибо

    от federicot
  • неправильно! может показаться, что это работаетtrim($s,'') но он может разбить строку на недопустимую последовательность UTF-8. не используйте его!

    от
  • я думаю, что ваше регулярное выражение соответствует 1 или более пробелов в начале или конце строки

    от Robbie
  • Какие символы вы хотите удалить, что trim () не удаляет?

    от Niko
  • Выглядит как бесконечная рекурсия для меня & # x2026;

    от knittl
  • trim () удалит такие символы, как & quot; , \ Т, \ г, \ п, \ 0, \ x0B & Quot; и модификатор \ s, такой как & quot; , \ Т, \ г, \ п, \ v, \ F & Quot; так что это не то, что Ты хочешь, я думаю. Чтобы удалить некоторые специальные символы из строки, вы всегда можете использовать trim ($ str, $ charlist) со вторым параметром. Можете ли вы написать несколько примеров символов, которые хотите удалить?

    от Naki
  • @ knittl, да, ты прав! Не понял этого. Функция, которую я объявляю, должна иметь другое имя. Я просто проверял, если в будущемmb_trim функция добавлена кmbstring расширение, и использование этого вместо моего собственного

    от federicot
7 ответов
  • 18

    Я не знаю

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

    function mb_trim($str) {
      return preg_replace("/(^\s+)|(\s+$)/us", "", $str); 
    }
    

  • 2

    mb_ereg_replace, кажется, обходит это:

    function mb_trim($str,$regex = "(^\s+)|(\s+$)/us") {
        return mb_ereg_replace($regex, "", $str);
    }
    

    ... но я не знаю достаточно о регулярных выражениях, чтобы знать, как вы потом добавите их в "charlist" люди с параметрами могут ожидать, что они смогут передавать в trim () - то есть список символов для усечения - так что просто сделали регулярное выражение параметром.

    Возможно, вы могли бы иметь массив специальных символов, а затем выполнить его для каждого символа в списке символов и, соответственно, экранировать их при построении строки регулярного выражения.

  • 0

    Фактическое решение вашего вопроса состоит в том

    My two cents

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

    Сколько байтов будет использовано для представления каждого символа? При правильно отформатированном UTF-8 это может быть 1 (символыtrim имеет дело с), 2, 3 или 4 байта. Проблема возникает, когда в игру вступают устаревшие или искаженные представления UTF-8 - границы байтовых символов могут не совпадать, как ожидалось (говорят дилетанты).

    В PHP некоторые отстаивают необходимость принудительного соответствия всех строк правильной кодировке UTF-8 (1, 2, 3 или 4 байта на символ), где такие функции, какtrim() будет по-прежнему работать, потому что граница байта / символа для символов, с которыми он работает, будет конгруэнтной для значений расширенного ASCII / 1-байта, которыеtrim() стремится исключить начало и конец строки (обрезать страницу руководства).

    Однако, поскольку компьютерное программирование является разнообразной областью, невозможно иметь общий подход, который работает во всех сценариях. С учетом вышесказанного, напишите ваше приложение так, как оно должно быть для правильной работы. Просто делаете базовый веб-сайт, управляемый базой данных, с вводом формы?Yes, за мои деньги заставь все быть UTF-8.

    Note: У вас все еще будут проблемы с интернационализацией, даже если ваша проблема с UTF-8 стабильна. Зачем? Многие неанглийские наборы символов существуют в 2, 3 или 4-байтовом пространстве (кодовые точки и т. Д.). Очевидно, что если вы используете компьютер, который должен работать со скриптами на китайском, японском, русском, арабском или иврите, вы хотите, чтобы все работало с 2, 3 и 4 байтами! Помните, PHPtrim Функция может обрезать символы по умолчанию или заданные пользователем. Это имеет значение, особенно если вам нужна вашаtrim чтобы объяснить некоторые китайские иероглифы.

    Я бы предпочел иметь дело с проблемой невозможности доступа к моему сайту, а затем с проблемой доступа и ответов, которые не должны появляться. Когда вы думаете об этом, это соответствует принципамleast privilege (безопасность) иuniversal design (Доступность).

    Summary

    Если входные данные не будут соответствовать надлежащей кодировке UTF-8, вы можетебросить исключение, Вы можете попытаться использоватьPHP многобайтовые функции определить вашу кодировку или другую многобайтовую библиотеку. Если и когда PHP написан для полной поддержки Unicode (Perl, Java ...), PHP будет лучше для него. Усилия по созданию Unicode в PHP прекратились несколько лет назад, поэтому вы вынуждены использовать дополнительные библиотеки для разумной работы с многобайтовыми строками UTF-8. Просто добавив/u флаг дляpreg_replace() не смотрит на общую картину.

    Обновить:

    При этом, я считаю, что следующая многобайтовая обрезка будет полезна для тех, кто пытается извлечь ресурсы REST из компонента пути URL (естественно, за исключением строки запроса. Примечание: это будет полезно после очистки и проверки строки пути)

    function mb_path_trim($path)
    {
        return preg_replace("/^(?:\/)|(?:\/)$/u", "", $path);
    }
    

  • 44

    Стандарт

    trim Функция обрезает несколько пробелов и пробелов. Они определены как символы ASCII, что означает определенные конкретныеbytes от0 в0100 0000.

    Proper UTF-8 никогда не будет содержать многобайтовые символы, состоящие из байтов0xxx xxxx, Все байты вproper Многобайтовые символы UTF-8 начинаются с1xxx xxxx.

    Это означает, что вproper Последовательность UTF-8, байты0xxx xxxx может ссылаться только на однобайтовые символы. РНР & APOS; strim поэтому функция никогда не обрезает "половину символа"assuming у тебя естьproper Последовательность UTF-8. (Будь очень оченьосторожноimproper UTF-8 sequences.)

    \s  в ASCII регулярные выражения будут в основном соответствовать тем же символам, что иtrim.

    preg функции с/u модификатор работает только наUTF-8 encoded regular expressions, а также/\s/u соответствуют также UTF8 'sNBSP, Такое поведение с неразрывными пробелами является единственным преимуществом его использования.

    Если вы хотите заменить пробелы в других, не ASCII-совместимых кодировках, ни один из методов не будет работать.

    Другими словами, если вы пытаетесь обрезать обычные пробелы ASCII-совместимой строкой, просто используйтеtrim, Когда используешь/\s/u будьте осторожны со значением nbsp для вашего текста.

    Береги себя:

      $s1 = html_entity_decode(" Hello   "); // the NBSP
      $s2 = "  exotic test ホ  ";
    
      echo "\nCORRECT trim: [". trim($s1) ."], [".  trim($s2) ."]";
      echo "\nSAME: [". trim($s1) ."] == [". preg_replace('/^\s+|\s+$/','',$s1) ."]";
      echo "\nBUT: [". trim($s1) ."] != [". preg_replace('/^\s+|\s+$/u','',$s1) ."]";
    
      echo "\n!INCORRECT trim: [". trim($s2,' ') ."]"; // DANGER! not UTF8 safe!
      echo "\nSAFE ONLY WITH preg: [". 
           preg_replace('/^[\s]+|[\s]+$/u', '', $s2) ."]";
    

  • 5

    Эта версия поддерживает второй необязательный параметр $ charlist:

    function mb_trim ($string, $charlist = null) 
    {   
        if (is_null($charlist)) {
            return trim ($string);
        } else {
            $charlist = str_replace ('/', '\/', preg_quote ($charlist));
            return preg_replace ("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
        }
    }
    

    Не поддерживает & quot; .. & quot; хотя для диапазонов.

  • 5

    Итак

    я принял решение @ edson-medina, исправил ошибку и добавил несколько юнит-тестов. Вот три функции, которые мы используем, чтобы дать аналогам mb параметры trim, rtrim и ltrim.

    ////////////////////////////////////////////////////////////////////////////////////
    //Add some multibyte core functions not in PHP
    ////////////////////////////////////////////////////////////////////////////////////
    function mb_trim($string, $charlist = null) {
        if (is_null($charlist)) {
            return trim($string);
        } else {
            $charlist = preg_quote($charlist, '/');
            return preg_replace("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
        }
    }
    function mb_rtrim($string, $charlist = null) {
        if (is_null($charlist)) {
            return rtrim($string);
        } else {
            $charlist = preg_quote($charlist, '/');
            return preg_replace("/([$charlist]+$)/us", '', $string);
        }
    }
    function mb_ltrim($string, $charlist = null) {
        if (is_null($charlist)) {
            return ltrim($string);
        } else {
            $charlist = preg_quote($charlist, '/');
            return preg_replace("/(^[$charlist]+)/us", '', $string);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////
    

    Вот модульные тесты, которые я написал для всех, кто интересуется:

    public function test_trim() {
        $this->assertEquals(trim(' foo '), mb_trim(' foo '));
        $this->assertEquals(trim(' foo ', ' o'), mb_trim(' foo ', ' o'));
        $this->,;assertEquals('foo', mb_trim(' Åfooホ ', ' Åホ'));
    }
    
    public function test_rtrim() {
        $this->assertEquals(rtrim(' foo '), mb_rtrim(' foo '));
        $this->assertEquals(rtrim(' foo ', ' o'), mb_rtrim(' foo ', ' o'));
        $this->assertEquals('foo', mb_rtrim('fooホ ', ' ホ'));
    }
    
    public function test_ltrim() {
        $this->assertEquals(ltrim(' foo '), mb_ltrim(' foo '));
        $this->assertEquals(ltrim(' foo ', ' o'), mb_ltrim(' foo ', ' o'));
        $this->assertEquals('foo', mb_ltrim(' Åfoo', ' Å'));
    }
    

  • 4

    Вы также можете обрезать не совместимые с ASCII пробелы (например

    неразрывный пробел) в строках UTF-8 с помощьюpreg_replace('/^\p{Z}+|\p{Z}+$/u','',$str);

    \s будет соответствовать только & quot; совместимому ascii & quot; космический персонажeven with the u modifier.
    но\p{Z} будет соответствовать всем известным символам пространства Юникод