【问题标题】:PHP - preg_replace_callback for camelCasingPHP - 驼峰式的 preg_replace_callback
【发布时间】:2020-02-01 07:08:56
【问题描述】:

我有以下内容

"aa_bb" : "foo"
"pp_Qq" : "bar"
"Xx_yY_zz" : "foobar"

我想把左边的内容转换成camelCase

"aaBb" : "foo"
"ppQq" : "bar"
"xxYyZz" : "foobar"

还有代码:

// selects the left part
$newString = preg_replace_callback("/\"(.*?)\"(.*?):/", function($matches) {        
    // selects the characters following underscores
    $matches[1] = preg_replace_callback("/_(.?)/", function($matches) {
        //removes the underscore and uppercases the character
        return strtoupper($matches[1]);
    }, $matches[1]);

    // lowercases the first character before returning
    return "\"".lcfirst($matches[1])."\" : ".$matches[2];
}, $string);

这段代码可以简化吗?

注意:内容将始终为单个字符串。

【问题讨论】:

  • 您可以稍微简化一下您的正则表达式,即^"([^"]+)"\s*:
  • @CodeManiac 我会试试看。
  • @user3783243 空格可能不一致。
  • @user3783243 引号之外的任何地方。引号之间肯定没有空格。
  • @user3783243 抱歉,误读了您的代码。此外,内容将不能作为数组使用。早该澄清的。内容将以字符串形式提供。

标签: php regex pcre preg-replace-callback camelcasing


【解决方案1】:

您可以将preg_replace_callback\G 锚和捕获组结合使用。

(?:"\K([^_\r\n]+)|\G(?!^))(?=[^":\r\n]*")(?=[^:\r\n]*:)_?([a-zA-Z])([^"_\r\n]*)

部分

  • (?:非捕获组
    • "\K([^_\r\n]+) 匹配 ",捕获 group 1 匹配除 _ 或换行符之外的任何字符 1 次以上
    • |或者
    • \G(?!^) 在上一场比赛中断言位置,而不是在开始时
  • )关闭群
  • (?=[^":\r\n]*") 积极前瞻,断言"
  • (?=[^:\r\n]*:) 积极前瞻,断言:
  • _?匹配可选_
  • ([a-zA-Z]) 捕获 第 2 组 匹配 a-zA-Z
  • ([^"_\r\n]*) 捕获 group 3 匹配除 _ 或换行符之外的任何字符 0+ 次

在替换中,使用 3 个捕获组连接 strtolowerstrtoupper 的组合。

Regex demo

例如

$re = '/(?:"\K([^_\r\n]+)|\G(?!^))(?=[^":\r\n]*")(?=[^:\r\n]*:)_?([a-zA-Z])([^"_\r\n]*)/';
$str = '"aa_bb" : "foo"

"pp_Qq" : "bar"

"Xx_yY_zz" : "foobar"
"Xx_yYyyyyyYyY_zz_a" : "foobar"';

$result =  preg_replace_callback($re, function($matches) {
    return strtolower($matches[1]) . strtoupper($matches[2]) . strtolower($matches[3]);
}, $str);

echo $result;

输出

"aaBb" : "foo"

"ppQq" : "bar"

"xxYyZz" : "foobar"
"xxYyyyyyyyyyZzA" : "foobar"

Php demo

【讨论】:

  • 哇,这表达方式很到位。稍后我检查后会回复
  • 感谢您的努力。但为了可维护性,我最终决定使用更简单的正则表达式解决方案。
【解决方案2】:

首先,既然您已经有了想要改进的工作代码,请考虑下次将您的问题发布到 code review 而不是 stackoverflow。

让我们开始改进你原来的方法:

$result = preg_replace_callback('~"[^"]*"\s*:~', function ($m) {
    return preg_replace_callback('~_+(.?)~', function ($n) {
        return strtoupper($n[1]);
    }, strtolower($m[0]));
}, $str);

赞成:模式比较简单,思路容易理解。
缺点:嵌套preg_replace_callback可能会伤眼。

在这个眼睛热身练习之后,我们可以尝试基于\G 的模式方法:

$pattern = '~(?|\G(?!^)_([^_"]*)|("(?=[^"]*"\s*:)[^_"]*))~';
$result = preg_replace_callback($pattern, function ($m) {
    return ucfirst(strtolower($m[1]));
}, $str);

亲:代码更短,不需要使用两个preg_replace_callback
缺点:模式要复杂得多。

注意:当你写一个长模式时,没有什么禁止使用带有 x 修饰符的 free-spacing 模式 并放置 cmets:

$pattern = '~
(?| # branch reset group: in which capture groups have the same number
    \G # contigous to the last successful match
    (?!^) # but not at the start of the string    
    _
    ( [^_"]* ) # capture group 1
  |
    ( # capture group 1
        "
        (?=[^"]*"\s*:) # lookahead to check if it is the "key part"
        [^_"]*
    )
)
~x';

这两个极端之间有没有妥协,什么是好的?两个建议:

$result = preg_replace_callback('~"[^"]+"\s*:~', function ($m) {
    return array_reduce(explode('_', strtolower($m[0])), function ($c, $i) {
        return $c . ucfirst($i);
    });
}, $str);

专业人士: 最少使用正则表达式。
缺点: 需要两个回调函数,但这次第二个回调函数由array_reduce 而非@ 调用987654330@.

$result = preg_replace_callback('~["_][^"_]*(?=[^"]*"\s*:)~', function ($m) {
    return ucfirst(strtolower(ltrim($m[0], '_')));
}, $str);

亲:模式比较简单,回调函数也很简单。这看起来是一个很好的折衷方案。
缺点:该模式不是很严格(但应该足以满足您的用例)

模式描述:模式查找 _ 或 " 并匹配以下不是 _ 或 " 的字符。然后,前瞻断言检查这些字符是否在 key 部分 内,以寻找右引号和冒号。匹配结果总是像_aBc"aBc(回调函数中左边的下划线被修剪,"在应用ucfirst后保持不变)。

图案细节:

["_] # one " or _
[^"_]* # zero or more characters that aren't " or _
(?= # open a lookahead assertion (followed with)
    [^"]* # all that isn't a "
    " # a literal "
    \s* # eventual whitespaces
    : # a literal :
) # close the lookahead assertion

没有好的答案,看起来简单或复杂实际上取决于读者。

【讨论】:

  • 确实如此。对于我的用例,一个更简单的正则表达式应该更易于维护。最后一种方法在两者之间增加了一个不错的平衡。如果可能,您能否在答案中添加对最后一个正则表达式的解释?
猜你喜欢
  • 2015-12-06
  • 2013-09-29
  • 2010-12-09
  • 2016-06-12
  • 2013-05-25
  • 2016-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多