PHP中的多字节修剪?

问题描述 投票:32回答:7

显然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);
    }
}

正则表达式似乎对我来说是正确的,但我是正则表达式的极力菜鸟。这会有效地删除字符串开头/结尾的任何Unicode空间吗?

php regex unicode multibyte mbstring
7个回答
45
投票

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

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

这意味着在正确的UTF-8序列中,字节0xxx xxxx只能引用单字节字符。假设你有一个正确的UTF-8序列,那么PHP的trim函数将永远不会删除“半个字符”。 (非常非常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) ."]";

18
投票

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

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

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);
}

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


4
投票

您还可以使用preg_replace('/^\p{Z}+|\p{Z}+$/u','',$str);修剪UTF-8字符串上的非ascii兼容空格(例如,不间断空格) 即使使用\s修饰符,u也只会匹配“ascii compatible”空格字符。 但\p{Z}将匹配所有已知的unicode空格字符


4
投票

好的,所以我采用了@ 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', ' Å'));
}

2
投票

mb_ereg_replace似乎解决了这个问题:

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

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

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


0
投票

我的两分钱

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

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

在PHP中,一些人主张所有字符串都应该被强制符合正确的UTF-8编码(每个字符1,2,3或4个字节),其中像trim()这样的函数仍然可以工作,因为字符的字节/字符边界对于trim()试图从字符串的开头和结尾消除的扩展ASCII / 1字节值(trim manual page),处理将是一致的。

但是,由于计算机编程是一个多样化的领域,因此不可能采用适用于所有场景的全面方法。话虽如此,请按照正常运行的方式编写应用程序。只是做一个基本的数据库驱动的网站与表单输入?是的,因为我的钱迫使一切都是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);
}
© www.soinside.com 2019 - 2024. All rights reserved.