Многобайтовая обрезка в PHP?
Видимо нет mb_trim
в mb_*
семья, поэтому я пытаюсь реализовать один для себя.
Я недавно нашел это регулярное выражение в комментарии в http://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);
}
}
Регулярное выражение кажется мне верным, но я очень недоволен регулярными выражениями. Будет ли это эффективно удалять любое пространство Юникода в начале / конце строки?
7 ответов
Стандарт trim
Функция обрезает несколько пробелов и пробелов. Они определены как символы ASCII, что означает определенные конкретные байты из 0
в 0100 0000
,
Правильный ввод UTF-8 никогда не будет содержать многобайтовые символы, состоящие из байтов 0xxx xxxx
, Все байты в правильных многобайтовых символах UTF-8 начинаются с 1xxx xxxx
,
Это означает, что в правильной последовательности UTF-8 байты 0xxx xxxx
может ссылаться только на однобайтовые символы. РНР trim
Поэтому функция никогда не обрезает "половину символа", если у вас есть правильная последовательность UTF-8. (Будьте очень осторожны с неправильными последовательностями UTF-8.)
\s
в ASCII регулярные выражения будут в основном соответствовать тем же символам, что и trim
,
preg
функции с /u
модификатор работает только с регулярными выражениями в кодировке UTF-8, и /\s/u
соответствуют также UTF8. Такое поведение с неразрывными пробелами является единственным преимуществом его использования.
Если вы хотите заменить пробелы в других, не 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) ."]";
Я не знаю, что вы пытаетесь сделать с этой бесконечной рекурсивной функцией, которую вы определяете, но если вы просто хотите использовать многобайтовую безопасную обрезку, это сработает.
function mb_trim($str) {
return preg_replace("/(^\s+)|(\s+$)/us", "", $str);
}
Эта версия поддерживает второй необязательный параметр $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);
}
}
Хотя не поддерживает ".." для диапазонов.
Итак, я взял решение @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', ' Å'));
}
Вы также можете обрезать не совместимые с ASCII пробелы (например, неразрывный пробел) в строках UTF-8 с помощью preg_replace('/^\p{Z}+|\p{Z}+$/u','',$str);
\s
будет совпадать только с "ascii-совместимым" пробелом даже с u
модификатор
но \p{Z}
будет соответствовать всем известным символам пространства Юникод
(Портировано с дубликата Q на trim
борется с NBSP.) Следующие примечания действительны для PHP 7.2+. Пробег может отличаться от более ранних версий (просьба сообщать в комментариях).
PHP trim
игнорирует неразрывные пробелы. Он только обрезает пробелы в основном диапазоне ASCII. Для справки, исходный код обрезки выглядит следующим образом (т.е. без недокументированных функций с обрезкой):
(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')
Из вышеперечисленного, помимо обычного пробела (32 ASCII,
), это все управляющие символы 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
символ escape-последовательности "любой пробел" с 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
с u
Флаг Unicode, устраняющий все перечисленные пробелы. Все они обрезаны, как и ожидалось, в соответствии со спецификацией 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}
. Он более всеобъемлющий, и импорт более очевиден для тех, кто читает ваш код, кто может не помнить краткости для всех типов символов.
mb_ereg_replace, кажется, обходит это:
function mb_trim($str,$regex = "(^\s+)|(\s+$)/us") {
return mb_ereg_replace($regex, "", $str);
}
... но я не знаю достаточно о регулярных выражениях, чтобы знать, как бы вы добавили параметр "charlist", который люди ожидают, что смогут передавать в trim() - то есть список символов для усечения - так что просто сделал регулярное выражение параметром.
Возможно, вы могли бы иметь массив специальных символов, а затем выполнить его для каждого символа в списке символов и, соответственно, экранировать их при построении строки регулярного выражения.
Мои два цента
Фактическое решение вашего вопроса состоит в том, что вы должны сначала выполнить проверки кодировки, прежде чем работать над изменением внешних входных строк. Многие из них быстро узнают о "санации и проверке" входных данных, но не спешат освоить шаг определения базовой природы (кодировки символов) строк, с которыми они работают на ранних этапах.
Сколько байтов будет использовано для представления каждого символа? При правильно отформатированном 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 написан для полной поддержки Unicode (Perl, Java ...), PHP будет лучше для него. Усилия по созданию Unicode в PHP прекратились несколько лет назад, поэтому вы вынуждены использовать дополнительные библиотеки для правильной работы с многобайтовыми строками UTF-8. Просто добавив /u
флаг для preg_replace()
не смотрит на общую картину.
Обновить:
При этом, я считаю, что следующая многобайтовая обрезка будет полезна для тех, кто пытается извлечь ресурсы REST из компонента пути URL-адреса (естественно, за исключением строки запроса. Примечание: это будет полезно после очистки и проверки строки пути).
function mb_path_trim($path)
{
return preg_replace("/^(?:\/)|(?:\/)$/u", "", $path);
}