Многобайтовая обрезка в PHP?

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

Недавно я нашел это регулярное выражение в комментарии на 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);
    }
}

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


person federico-t    schedule 08.04.2012    source источник
comment
Мне кажется, что это бесконечная рекурсия…   -  person knittl    schedule 09.04.2012
comment
trim() удалит такие символы, как ,\t,\r,\n,\0,\x0B и модификатор \s, например ,\t,\r,\n,\v,\f, так что это не то, что вы хотите, я думаю . Чтобы удалить некоторые специальные символы из строки, всегда можно использовать команду trim($str,$charlist) со вторым параметром. Можете ли вы написать несколько примеров символов, которые вы хотите удалить?   -  person Naki    schedule 09.04.2012
comment
Какие символы вы хотите удалить, которые trim() не удаляет?   -  person Niko    schedule 09.04.2012
comment
я думаю, что ваше регулярное выражение соответствует 1 или более пробелам в начале или конце строки   -  person Robbie    schedule 09.04.2012
comment
@knittl, да, ты прав! Не понял этого. Функция, которую я объявляю, должна иметь другое имя. Я просто проверял, будет ли когда-нибудь в будущем добавлена ​​функция mb_trim к расширению mbstring, и использовать ее вместо моей собственной.   -  person federico-t    schedule 09.04.2012
comment
mb_trim() не существует в расширении mbstring   -  person Edson Medina    schedule 08.11.2012
comment
Проблема здесь в том, что NBSP является символом UTF8, поэтому \s обнаруживает NBSP только с опцией /u. PHP очень запутался в совместимости с UTF8... Есть FastGuide о том, что сегодня безопасно, а что нет? Пример: str_replace и trim (на мой взгляд) совместимы с UTF8, поэтому некоторым функциям не нужна функция mb_*, другим нужна... А другим, например perg_*, нужны параметры для обнаружения utf8, даже неявного (см. это \s неявное обнаружение NBSP).   -  person Peter Krauss    schedule 08.09.2014


Ответы (8)


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

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

Это означает, что в правильной последовательности UTF-8 байты 0xxx xxxx могут относиться только к однобайтовым символам. Поэтому функция PHP trim никогда не будет обрезать "полсимвола" предполагая, что у вас есть правильная последовательность UTF-8. (Будьте очень-очень осторожнее с неправильными последовательностями UTF-8. .)


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

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

Если вы хотите заменить символы пробела в других, несовместимых с 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) ."]";
person deceze♦    schedule 09.04.2012
comment
trim($s,'????') и trim($s,'???? ') работают нормально (!). Во втором примере есть символ ASCII, работающий вместе... Таким образом, мы можем сказать, что trim() функция безопасна для UTF8, но не trim() является ASCII, так что UTF8. Люди путают /\s/ и /\s/u, где только последний обнаруживает NBSP. - person Peter Krauss; 08.09.2014
comment
неправильно! может показаться, что это работает trim($s,'????'), но может разбить строку на недопустимую последовательность UTF-8. не используйте его! - person Wes; 18.11.2014
comment
Действительно, удаление символов ASCII из строки UTF-8 безопасно, а удаление символов UTF-8 из строки — нет. Это связано с тем, что trim понимает ???? не как один символ, а как три байта, и он будет обрезать любой из этих трех байтов отдельно при встрече. @Питер - person deceze♦; 18.11.2014
comment
Извините - неверно говорить, что работает нормально без полного теста, вы правы, говоря, что trim($s,$utf8) неправильно! – предлагаю сказать это в тексте ответа. Что касается моего другого комментария, я думаю, что текст ответа \s будет в основном соответствовать тем же символам неверно: пожалуйста, проверьте сами preg_replace('/\s/u', '',$s), когда $s = html_entity_decode(" Hello   "); считает UTF8 NBSP. - person Peter Krauss; 19.11.2014
comment
Придерживаться функции trim(), не поддерживающей utf8, является решением только в том случае, если все символы, которые вы хотите удалить, являются однобайтовыми символами. Но если вы хотите, например, также удалить некоторые многобайтовые символы (например, U + 200B, пространство нулевой ширины), вам нужно правильное многобайтовое расширение обрезки, которое запрашивает OP. - person matteo; 16.02.2018
comment
@matteo Однако с итерацией и несколькими этапами и уровнями фильтрации и проверки пространство нулевой ширины не является проблемой, поскольку строка не должна проверяться. - person Anthony Rutledge; 14.08.2018

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

function mb_trim($str) {
  return preg_replace("/^\s+|\s+$/u", "", $str); 
}
person kba    schedule 08.04.2012
comment
Знают ли преги в PHP о различных кодировках? Я не могу вспомнить, но я знаю, что когда-то где-то с ними была проблема, и я думаю, что она была здесь. - person Incognito; 09.04.2012
comment
trim($s,'????') и trim($s,'???? ') работают нормально (!). Зачем нам mb_trim()? - person Peter Krauss; 08.09.2014
comment
Было бы лучше использовать подшаблоны без захвата. us1.php.net/manual/en/regexp.reference.subpatterns. php . Они имеют вид (?: ) - person Anthony Rutledge; 07.09.2018

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

function mb_trim ($string, $charlist = null) 
{   
    if (is_null($charlist)) {
        return trim ($string);
    } 

    $charlist = str_replace ('/', '\/', preg_quote ($charlist));
    return preg_replace ("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
}

Однако не поддерживает «..» для диапазонов.

person Edson Medina    schedule 08.11.2012
comment
Мне нравится твой способ, но не забудь поставить preg_quote в свой $charlist :) - person Alain Tiemblo; 04.09.2013
comment
Хорошо поймал! Спасибо. - person Edson Medina; 04.09.2013
comment
Это не удается для mb_trim('000foo000', '0')... :-3 - person deceze♦; 04.12.2013
comment
Исправил @deceze. Спасибо. - person Edson Medina; 04.12.2013
comment
Это следует немного изменить. Ваша строка $charlist = preg_quote должна быть внутри, иначе проверка is_null($charlist) никогда не сработает. - person Michael Taggart; 08.05.2015
comment
Хорошо поймал! Спасибо. Я изменю это. - person Edson Medina; 09.05.2015
comment
Я не знаю, была ли это старая версия php, но $charlist можно экранировать с помощью этого простого вызова: $charlist=preg_quote($charlist,'/'); или вы можете заставить его восприниматься буквально без вызова функции, обернув его в \Q и \E См. Код 2 на моем связанный пост: codereview.stackexchange.com/a/178931/141885 ps Так как у вас не будет точек в $charlist, вы можете опустить флаг s в конце шаблона. - person mickmackusa; 16.11.2017
comment
Можно рассмотреть возможность использования if($charlist === null) вместо is_null в соответствии с этим вопросом и ответом , чтобы обойти накладные расходы от is_null. - person Zlatan Omerović; 29.12.2017
comment
Это наиболее полное решение. - person Kamafeather; 04.04.2019

Итак, я взял решение @edson-medina, исправил ошибку и добавил несколько модульных тестов. Вот 3 функции, которые мы используем, чтобы дать 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', ' Å'));
}
person Michael Taggart    schedule 08.05.2015

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

\s будет соответствовать только «совместимому с ascii» символу пробела даже с модификатором u .
но \p{Z} будет соответствовать всем известным символам пробела Unicode

person Opty    schedule 14.09.2012
comment
Я отредактировал @deceze, см. о /\s/u, неправильно говорить, что будет соответствовать только ASCII (потому что   не является ASCII), можете ли вы исправить это в своем ответе? Насчет \p{Z}, извините, я не процитировал там в своем редактировании, хорошо бы запомнить (!). - person Peter Krauss; 20.11.2014
comment
Начиная с PHP 7.2+ (возможно, ранее), \s будет соответствовать любому символу пробела Unicode (см. мой недавний ответ) с включенным u. Однако только \p{Z} не будет соответствовать обычным пробелам ASCII. Я не знаю, было ли это по-другому в 2014 году, но по состоянию на 2020 год это не точно. - person Markus AO; 20.07.2020

mb_ereg_replace, похоже, обходит это:

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

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

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

person trapper_hag    schedule 24.05.2012

(Перенесено из дубликата Q на trim борется с NBSP.) Следующие примечания действительны для PHP 7.2+. Пробег может отличаться от более ранних версий (пожалуйста, сообщите в комментариях).

PHP trim игнорирует неразрывные пробелы. Он обрезает только пробелы в основном диапазоне ASCII. Для справки: исходный код для обрезки читается следующим образом (т. е. без недокументированных функций с обрезкой):

(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')

Из вышеперечисленного, за исключением обычного пробела (ASCII 32, ), все это управляющие символы ASCII; LF (10:\n), CR (13:\r), HT (9:\t), VT (11:\v), NUL (0:\0). (Обратите внимание, что в PHP вы должны заключать экранированные символы в двойные кавычки: "\n", "\t" и т. д. В противном случае они анализируются как буквальные \n и т. д.)

Ниже приведены простые реализации трех разновидностей trim (ltrim, rtrim, trim) с использованием preg_replace, которые работают со строками Unicode:

preg_replace('~^\s+~u', '', $string) // == ltrim
preg_replace('~\s+$~u', '', $string) // == rtrim
preg_replace('~^\s+|\s+$~us', '', $string) // == trim

Не стесняйтесь включать их в свои собственные mb_*trim функции.

В соответствии с спецификацией PCRE, \s любой символ управляющей последовательности пробелов с u Включенный режим Unicode будет соответствовать всем следующим символам пробела:

The horizontal space characters are:

U+0009     Horizontal tab (HT)
U+0020     Space
U+00A0     Non-break space
U+1680     Ogham space mark
U+180E     Mongolian vowel separator
U+2000     En quad
U+2001     Em quad
U+2002     En space
U+2003     Em space
U+2004     Three-per-em space
U+2005     Four-per-em space
U+2006     Six-per-em space
U+2007     Figure space
U+2008     Punctuation space
U+2009     Thin space
U+200A     Hair space
U+202F     Narrow no-break space
U+205F     Medium mathematical space
U+3000     Ideographic space

The vertical space characters are:

U+000A     Linefeed (LF)
U+000B     Vertical tab (VT)
U+000C     Form feed (FF)
U+000D     Carriage return (CR)
U+0085     Next line (NEL)
U+2028     Line separator
U+2029     Paragraph separator

Вы можете увидеть тестовую итерацию preg_replace с флагом Unicode u, затрагивающим все перечисленные пробелы. Все они обрезаны, как и ожидалось, в соответствии со спецификацией PCRE. Если вы настроили таргетинг только на указанные выше горизонтальные пробелы, \h будет соответствовать им, как \v — всем вертикальным пробелам.

Использование \p{Z} в некоторых ответах потерпит неудачу по некоторым причинам; в частности, с большинством пробелов ASCII и, что шокирует, также с монгольским разделителем гласных. Хубилай-хан был бы в ярости. Вот список ошибок с \p{Z}: U+0009 Горизонтальная вкладка (HT), U+000A Перевод строки (LF), U+000C Перевод страницы (FF) , U+000D возврат каретки (CR), U+0085 следующая строка (NEL) и U+180E разделитель монгольских гласных. эм>

Что касается того, почему это происходит, в приведенной выше спецификации PCRE также отмечается: \s любой символ, который соответствует \p{Z}, \h или \v. То есть \s является надмножеством \p{Z}. Затем просто используйте \s вместо \p{Z}. Он более полный, и импорт более очевиден для тех, кто читает ваш код, кто может не помнить сокращения для всех типов символов.

person Markus AO    schedule 19.07.2020

Мои два цента

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

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

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

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

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

Я бы предпочел иметь дело с проблемой того, что кто-то не может получить доступ к моему сайту, чем с проблемой доступа и ответов, которых не должно быть. Если подумать, это соответствует принципам наименьших привилегий (безопасность) и универсального дизайна (доступность).

Резюме

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

Обновлять:

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

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