PHP中的多字节修剪?

时间:2012-04-08 21:23:38

标签: php regex unicode multibyte mbstring

显然mb_* family中没有mb_trim,所以我正在尝试为自己实现一个。{/ p>

我最近在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空间吗?

8 个答案:

答案 0 :(得分:45)

标准trim功能修剪了一些空格和空格字符。这些被定义为ASCII字符,这意味着从00100 0000的某些特定字节

正确 UTF-8输入永远不会包含由字节0xxx xxxx组成的多字节字符。 正确 UTF-8多字节字符中的所有字节均以1xxx xxxx开头。

这意味着在正确的 UTF-8序列中,字节0xxx xxxx只能引用单字节字符。因此,PHP的trim函数永远不会删除“半个字符”假设你有一个正确的 UTF-8序列。 (非常非常careful about improper UTF-8 sequences。)


ASCII正则表达式上的\s主要与trim匹配相同的字符。

preg修饰符的/u函数仅适用于 UTF-8编码的正则表达式,而/\s/u也符合UTF8的nbsp 。这种不间断空格的行为是使用它的唯一优势。

如果要替换其他非ASCII兼容编码中的空格字符,则两种方法都不起作用。

换句话说,如果您尝试修剪常用空格和ASCII兼容字符串,只需使用trim即可。使用/\s/u时请注意文字的含义。


小心:

  $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) ."]";

答案 1 :(得分:18)

我不知道你要用你定义的无限递归函数做什么,但如果你只想要一个多字节安全的修剪,这将有效。

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

答案 2 :(得分:6)

此版本支持第二个可选参数$ 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);
}

虽然不支持范围“..”。

答案 3 :(得分:4)

您还可以使用preg_replace('/^\p{Z}+|\p{Z}+$/u','',$str);修剪UTF-8字符串上的非ascii兼容空格(例如,不间断空格)

\s只会匹配“ascii compatible”空格字符,即使使用u修饰符
但是\p{Z}将匹配所有已知的unicode空格字符

答案 4 :(得分:4)

好的,所以我采用了@ edson-medina的解决方案并修复了一个错误,并添加了一些单元测试。这是我们用来为mb对应物修剪,rtrim和ltrim的3个函数。

////////////////////////////////////////////////////////////////////////////////////
//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', ' Å'));
}

答案 5 :(得分:2)

mb_ereg_replace似乎可以解决这个问题:

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

..但我对正则表达式知之甚少,不知道你如何添加人们希望能够提供给trim()的“charlist”参数 - 即要修剪的字符列表 - 所以刚刚使用正则表达式作为参数。

可能你可能有一个特殊字符数组,然后为charlist中的每个字符单步执行它,并在构建正则表达式字符串时相应地转义它们。

答案 6 :(得分:1)

(从trim上的重复Q移植而来,与NBSP格格不入。)以下注释自PHP 7.2+起有效。里程可能因早期版本而异(请在评论中注明)。

PHP trim会忽略不间断空格。它仅修剪基本ASCII范围内的空格。作为参考,the source code的修边内容如下(即,没有未记录的带有修边的功能):

(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:{{ 1}})。 (请注意,在PHP中,必须将转义的字符双引号:\0等。否则,它们将被解析为文字"\n", "\t"等。)

以下是使用\n的{​​{1}}(trimltrimrtrim)的三种风格的简单实现,它们与Unicode字符串一起使用:

trim

随时将它们包装到您自己的preg_replace函数中。

对于每个PCRE specification,启用preg_replace('~^\s+~u', '', $string) // == ltrim preg_replace('~\s+$~u', '', $string) // == rtrim preg_replace('~^\s+|\s+$~us', '', $string) // == trim Unicode模式的mb_*trim“任何空格”转义序列字符将与以下所有空格字符匹配:

\s

您可以看到u中的test iteration带有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 Unicode标志来处理所有列出的空格。它们均按照PCRE规范按预期进行修整。如果您只定位上方的水平空间,则preg_replace会与它们匹配,就像u会匹配所有垂直空间一样。

在某些答案中使用\h可能会因某些原因而失败;特别是,对于大多数ASCII空间,以及令人震惊的蒙古元音分隔符。忽必烈会生气。以下是\v遗漏的列表:U + 0009 “水平”标签(HT),U + 000A 换行符(LF),U + 000C 表格feed(FF),U + 000D 回车(CR),U + 0085 下一行(NEL)和U + 180E 蒙古语元音分隔符。

关于发生这种情况的原因,以上PCRE规范还指出:“ \p{Z}\p{Z}\s\p{Z} 匹配的任何字符”。也就是说,\h\v的超集。然后,只需使用\s代替\p{Z}。对于阅读您的代码的人来说,它可能更全面,并且导入更直接,而他们可能不会记住所有字符类型的缩写。

答案 7 :(得分:0)

我的两分钱

您的问题的实际解决方案是,在更改外部输入字符串之前,您应该首先进行编码检查。许多人很快就了解了“消毒和验证”输入数据的知识,但是却很快学会了识别早期使用的字符串的基础性质(字符编码)的步骤。

将使用多少个字节表示每个字符?使用正确格式的UTF-8,它可以是1(2个字符,trim处理),2个,3个或4个字节。问题是当旧的或格式错误的UTF-8表示形式发挥作用时出现的-字节字符边界可能未按预期排列(外行说话)。

在PHP中,有人主张应强制所有字符串遵循正确的UTF-8编码(每个字符1、2、3或4个字节),其中trim()之类的功能仍将起作用,因为该字节trim()试图从字符串(trim manual page的开头和结尾消除)的扩展ASCII / 1字节值将与它处理的字符的/字符边界完全一致。

但是,由于计算机编程是一个多元化的领域,因此不可能有一种适用于所有情况的通用方法。话虽如此,编写应用程序的方式必须使其正常运行。只是使用表单输入做一个基本的数据库驱动的网站? ,因为我的钱迫使一切都变成UTF-8。

注意:即使您的UTF-8问题稳定,您仍然会遇到国际化问题。为什么? 2、3或4字节空间(代码点等)中存在许多非英语字符集。显然,如果您使用的计算机必须处理中文,日文,俄文,阿拉伯文或希伯来文脚本,那么您还希望所有内容都使用2、3和4个字节!请记住,PHP trim函数可以修剪默认字符或用户指定的字符。这很重要,尤其是在您需要trim来解释一些汉字的情况下。

我宁愿处理无法访问我的网站的问题,也不应该处理访问和响应问题。当您考虑时,这符合最低特权(安全性)和通用设计(可访问性)的原则。

摘要

如果输入数据不符合正确的UTF-8编码,则可能需要throw an exception。您可以尝试使用PHP multi-byte functions来确定您的编码或其他一些多字节库。如果何时以及何时编写PHP以完全支持unicode(Perl,Java ...),则PHP会更好。 PHP unicode的工作在几年前就死了,因此您被迫使用额外的库来理智地处理UTF-8多字节字符串。只是将/u标志添加到preg_replace()并不能看到全局。

更新:

话虽如此,我相信以下多字节修剪对那些尝试从url的路径组件中提取REST资源(自然会减少查询字符串)的人很有用。注意:在清理和验证路径之后,这将很有用字符串。

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