regex PHP中的多字节修剪?

u59ebvdq  于 2022-12-30  发布在  PHP
关注(0)|答案(8)|浏览(132)

显然,mb_* family中没有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空格吗?

wgmfuz8q

wgmfuz8q1#

标准的trim函数删除了一些空格和类空格字符,这些字符被定义为ASCII字符,表示从00100 0000的特定 * 字节 *。

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

这意味着在一个properUTF-8序列中,字节0xxx xxxx只能引用单字节字符,因此PHP的trim函数永远不会删除“半个字符”假设你有一个properUTF-8序列(要非常非常小心unproperUTF-8序列)。
ASCII正则表达式中的\s大多数匹配与trim相同的字符。
带有/u修饰符的preg函数只适用于 *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) ."]";
x6yk4ghg

x6yk4ghg2#

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

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

xvw2m8pv3#

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

但不支持范围使用“..”。

a9wyjsp7

a9wyjsp74#

好了,我采用了@edson-medina的解决方案,修正了一个bug,并添加了一些单元测试。下面是我们用来给予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', ' Å'));
}
lc8prwob

lc8prwob5#

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

gmxoilav

gmxoilav6#

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

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

..但是我对正则表达式的了解不够,不知道如何添加“charlist”参数,人们期望它能够提供给trim()--即要修剪的字符列表--所以我只是将正则表达式作为一个参数。
您可能有一个特殊字符数组,然后在构建正则表达式字符串时,针对charlist中的每个字符逐个执行该数组,并相应地对它们进行转义。

wf82jlnq

wf82jlnq7#

(移植自trim上的一个重复的Q,与NBSP有冲突。)以下注解从PHP 7.2+起有效。里程可能与早期版本不同(请在评论中报告)。
PHP trim忽略未中断的空格。它只删除基本ASCII范围内的空格。作为参考,the source code的trim如下所示(即没有未记录的trim特性):

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

其中,除了普通的空格(ASCII 32,``)之外,这些都是ASCII控制字符;低频(10:\n)、全周波(13:\r)、高温(9:\t)、VT(11:\v)、空值(0:\0).(注意,在PHP中,转义字符必须用双引号括起来:"\n", "\t"等。否则,它们将被解析为文字\n等。)
以下是使用preg_replace的三种风格trimltrimrtrimtrim)的简单实现,这些实现使用Unicode字符串:

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

您可以将它们 Package 到自己的mb_*trim函数中。
根据PCRE specificationu Unicode模式打开时的\s“任意空格”转义序列字符将匹配以下所有空格字符:

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_replacetest iteration,其中u Unicode标志处理了列出的所有空格。它们都按照PCRE规范进行了修剪。如果您只针对上面的水平空格,\h将匹配它们,就像\v匹配所有垂直空格一样。
在一些答案中看到的\p{Z}的使用在某些情况下会失败;特别是,大部分的ASCII空间,令人震惊的是,蒙古语元音分隔符也是如此。忽必烈汗会大发雷霆的。下面是\p{Z}的错误列表:U+0009 * 水平制表符(HT)、U+000 A * 换行符(LF)、U+000 C * 换页符(FF)、U+000 D * 回车符(CR)、U+0085 * 下一行(NEL)* 和U+180 E * 蒙古文元音分隔符。*
至于为什么会发生这种情况,上述PCRE规范还指出:“* \s\p{Z}\h\v匹配的任何字符 *"。也就是说,\s\p{Z}的超集。然后,只需使用\s代替\p{Z}。这样更全面,对于阅读您代码的人来说,导入也更直观。他们可能不记得所有字符类型的短字符。

bfhwhh0e

bfhwhh0e8#

∮ ∮我的两分钱∮
你的问题的实际解决方案是,在修改外来输入字符串之前,你应该先做编码检查。许多人很快就学会了"净化和验证"输入数据,但在早期学习识别字符串的基本性质(字符编码)的步骤时却很慢。
用多少字节来表示每个字符?如果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函数可以删除默认字符或用户指定的字符。2这一点很重要,尤其是当你需要你的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);
}

相关问题