40

显然家庭mb_trim中没有,所以我正在尝试为自己实现一个。mb_*

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

4

8 回答 8

57

标准trim函数修剪少量空格和类似空格的字符。这些被定义为 ASCII 字符,这意味着从到 的某些特定字节00100 0000

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

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


on ASCII 正则表达式将\s 主要匹配与trim.

preg带有/u修饰符的函数仅适用于UTF-8 编码的正则表达式,并且/\s/u也匹配 UTF8 的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) ."]";
于 2012-04-09T00:23:09.207 回答
22

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

function mb_trim($str) {
  return preg_replace("/^\s+|\s+$/u", "", $str); 
}
于 2012-04-08T22:58:57.590 回答
7

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

但不支持范围的“..”。

于 2012-11-08T12:11:23.520 回答
6

好的,所以我采用了@edson-medina 的解决方案并修复了一个错误并添加了一些单元测试。下面是我们用来给 mb 对应的 trim、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', ' Å'));
}
于 2015-05-08T18:55:15.187 回答
5

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

\s,也只会匹配“ascii 兼容”空格字符。 但将匹配所有已知的 unicode 空格字符u
\p{Z}

于 2012-09-14T06:51:11.200 回答
2

mb_ereg_replace 似乎解决了这个问题:

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

..但是我对正则表达式的了解还不够,不知道您将如何添加人们希望能够提供给 trim() 的“charlist”参数 - 即要修剪的字符列表 - 所以只有使正则表达式成为参数。

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

于 2012-05-24T14:11:49.507 回答
2

(从与 NBSP 斗争的重复 Q 中移植trim。)以下注释自 PHP 7.2+ 起有效。里程可能会因早期版本而异(请在评论中报告)。

PHPtrim忽略不间断的空格。它只修剪基本 ASCII 范围内的空格。作为参考, trim的源代码如下所示(即,没有带有 trim 的未记录功能):

(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等。)

以下是使用Unicode 字符串的trim( ltrim, rtrim, trim)三种风格的简单实现:preg_replace

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

随意将它们包装到您自己的mb_*trim函数中。

根据PCRE 规范,开启 Unicode 模式的\s“任何空格”转义序列字符u将匹配以下所有空格字符:

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_replaceu\h\v

在某些答案中使用\p{Z}seen 在某些方面会失败;具体来说,使用了大部分 ASCII 空格,令人震惊的是,还使用了蒙古元音分隔符。忽必烈会大怒。以下是未命中的列表\p{Z}: U+0009水平制表符 (HT)、 U+000A换行 (LF)、 U+000C换页 (FF)、 U+000D回车 (CR)、 U+0085下一行 (NEL) , 和 U+180E蒙古语元音分隔符。

至于为什么会发生这种情况,上面的 PCRE 规范还指出:“\s 任何匹配\p{Z}or \hor\v的字符”。也就是说,\s是 的超集\p{Z}。然后,只需使用\s. \p{Z}它更全面,对于阅读您的代码的人来说,导入更明显,他们可能不记得所有字符类型的矮个子。

于 2020-07-19T16:39:56.787 回答
0

我的两分钱

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

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

在 PHP 中,一些人主张所有字符串都应该强制符合正确的 UTF-8 编码(每个字符 1、2、3 或 4 个字节),其中类似的函数trim()仍然可以工作,因为它处理的字符的字节/字符边界with 将与trim()试图从字符串的开头和结尾消除的扩展 ASCII / 1 字节值一致(修剪手册页)。

然而,由于计算机编程是一个多元化的领域,不可能有一种适用于所有场景的全面方法。话虽如此,以正常运行所需的方式编写您的应用程序。只是用表单输入做一个基本的数据库驱动网站?的,为了我的钱,一切都必须是 UTF-8。

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

我更愿意处理某人无法访问我的网站的问题,然后是不应该发生的访问和响应问题。仔细想想,这符合最小特权(安全性)和通用设计(可访问性)的原则。

概括

如果输入数据不符合正确的 UTF-8 编码,您可能需要抛出异常。您可以尝试使用PHP 多字节函数来确定您的编码,或其他一些多字节库。如果以及何时编写 PHP 以完全支持 unicode(Perl、Java ...),那么 PHP 会更好。PHP unicode 的努力在几年前就死了,因此你不得不使用额外的库来理智地处理 UTF-8 多字节字符串。只是添加/u标志preg_replace()并没有看到大局。

更新:

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

function mb_path_trim($path)
{
    return preg_replace("/^(?:\/)|(?:\/)$/u", "", $path);
}
于 2018-08-14T01:26:37.280 回答