使用PHP preg_match(正则表达式)将camelCase单词拆分为单词

77

我应该如何分割单词:

oneTwoThreeFour

将其转换为数组,以便我可以获取:

one Two Three Four

如何使用preg_match

我尝试了这个方法,但是它只返回整个单词。

$words = preg_match("/[a-zA-Z]*(?:[a-z][a-zA-Z]*[A-Z]|[A-Z][a-zA-Z]*[a-z])[a-zA-Z]*\b/", $string, $matches)`;

也许我的问题可以帮到你, 我昨天问了同样的问题,但是关于Java https://dev59.com/AFLTa4cB1Zd3GeqPd9Um - Gondim
13个回答

90

你可以使用 preg_split

$arr = preg_split('/(?=[A-Z])/',$str);

查看它

我基本上是在大写字母前分割输入字符串。使用的正则表达式(?=[A-Z])匹配大写字母前的点。


84

你也可以使用 preg_match_all,如下所示:

preg_match_all('/((?:^|[A-Z])[a-z]+)/',$str,$matches);

解释:

(        - Start of capturing parenthesis.
 (?:     - Start of non-capturing parenthesis.
  ^      - Start anchor.
  |      - Alternation.
  [A-Z]  - Any one capital letter.
 )       - End of non-capturing parenthesis.
 [a-z]+  - one ore more lowercase letter.
)        - End of capturing parenthesis.

非捕获组不会导致结果变为[one, wo, hree, our]吗? - Aaron J Lang
2
@AaronJLang 不行,因为外层括号捕获了整个组,包括子组。这是一个他不想在 $matches 集合中混杂的子组。 - Eli Gassert
2
这段代码在我使用"TestID"时失败了,使用以下代码:"preg_match_all('/((?:^|[A-Z])[a-z]+)/', $key, $matches); die(implode(' ', $matches[0]));",因为它不喜欢连续的大写问题。我需要用空格分隔大小写变化,@blak3r的解决方案对我有用:https://dev59.com/d2855IYBdhLWcg3wIAka#17122207 - Zack Morris
1
更好的解决方案适用于像HTMLParser这样的字符串:https://dev59.com/11jUa4cB1Zd3GeqPVuQL#6572999。 - Maciej Sz
根据@TarranJones的规定(尽管没有表述得太清楚),您不需要外部括号。匹配字符串'/(?:^|[A-Z])[a-z]+/'就足以产生一个数组(而不是两个)。这是因为preg_match_all()自动捕获所有匹配实例,无需您明确指定。 - cartbeforehorse

57

我知道这是一个旧问题,并且已经有了一个被接受的答案,但在我看来,有一个更好的解决方案:

<?php // test.php Rev:20140412_0800
$ccWord = 'NewNASAModule';
$re = '/(?#! splitCamelCase Rev:20140412)
    # Split camelCase "words". Two global alternatives. Either g1of2:
      (?<=[a-z])      # Position is after a lowercase,
      (?=[A-Z])       # and before an uppercase letter.
    | (?<=[A-Z])      # Or g2of2; Position is after uppercase,
      (?=[A-Z][a-z])  # and before upper-then-lower case.
    /x';
$a = preg_split($re, $ccWord);
$count = count($a);
for ($i = 0; $i < $count; ++$i) {
    printf("Word %d of %d = \"%s\"\n",
        $i + 1, $count, $a[$i]);
}
?>

注意,这个正则表达式(就像codaddict的解决方案'/(?=[A-Z])/'一样-对于格式良好的camelCase单词非常有效),只匹配字符串内的一个位置,并不消耗任何文本。这种解决方案还有额外的好处,它也可以正确地处理那些不太规范的伪camelcase单词,例如:StartsWithCap 和:hasConsecutiveCAPS

输入:

oneTwoThreeFour
StartsWithCap
hasConsecutiveCAPS
NewNASAModule

输出:

第1个单词 = "one"
第2个单词 = "Two"
第3个单词 = "Three"
第4个单词 = "Four"

第1个单词 = "Starts"
第2个单词 = "With"
第3个单词 = "Cap"

第1个单词 = "has"
第2个单词 = "Consecutive"
第3个单词 = "CAPS"

第1个单词 = "New"
第2个单词 = "NASA"
第3个单词 = "Module"

编辑:2014-04-12:修改了正则表达式、脚本和测试数据,以正确拆分"NewNASAModule"的情况(响应rr的评论)。


这是一个更好的解决方案,第一次就能运行(其他人向数组添加了空值,而这个完美无缺!谢谢!+1) - Anil
1
似乎存在一个字符串问题,例如 NewNASAModule(输出:[New, NASAModule];我期望的是 [New, NASA, Module])。 - rr-
1
@rr - 是的,你说得对。请看我的其他更新答案,它正确地拆分了NewNASAModuleRegEx to split camelCase or TitleCase (advanced) - ridgerunner
它不涵盖数字情况。由于某种原因,其他回复者也忽略了这个基本事实。例如,“Css3Transform”或类似情况。 - Onkeltem

19

虽然ridgerunner的答案很好,但似乎无法处理出现在句子中间的全大写子串。我使用以下代码,似乎可以很好地处理这些问题:

function splitCamelCase($input)
{
    return preg_split(
        '/(^[^A-Z]+|[A-Z][^A-Z]+)/',
        $input,
        -1, /* no limit for replacement count */
        PREG_SPLIT_NO_EMPTY /*don't return empty elements*/
            | PREG_SPLIT_DELIM_CAPTURE /*don't strip anything from output array*/
    );
}

一些测试用例:

assert(splitCamelCase('lowHigh') == ['low', 'High']);
assert(splitCamelCase('WarriorPrincess') == ['Warrior', 'Princess']);
assert(splitCamelCase('SupportSEELE') == ['Support', 'SEELE']);
assert(splitCamelCase('LaunchFLEIAModule') == ['Launch', 'FLEIA', 'Module']);
assert(splitCamelCase('anotherNASATrip') == ['another', 'NASA', 'Trip']);

13

@ridgerunner的答案的函数版本。

/**
 * Converts camelCase string to have spaces between each.
 * @param $camelCaseString
 * @return string
 */
function fromCamelCase($camelCaseString) {
        $re = '/(?<=[a-z])(?=[A-Z])/x';
        $a = preg_split($re, $camelCaseString);
        return join($a, " " );
}

7
$string = preg_replace( '/([a-z0-9])([A-Z])/', "$1 $2", $string );

这个技巧是一个可重复的模式$1 $2$1 $2或者lower UPPERlower UPPERlower等等......例如helloWorld = $1匹配"hello", $2匹配"W",然后$1再次匹配"orld",因此简单来说,你可以得到$1 $2$1或者"hello World",将它们转换为小写、大写第一个单词、按空格分隔或使用_或其他字符保持它们分开。简短而简单。

5
在确定项目最佳模式时,需要考虑以下模式因素:
  1. 准确性(健壮性)- 模式在所有情况下都是正确的,并且是合理的未来方向
  2. 效率 - 模式应直接、刻意,避免不必要的劳动
  3. 简洁性 - 模式应使用适当的技术避免不必要的字符长度
  4. 可读性 - 模式应尽可能简单易懂
以上因素也恰好以层次顺序为标准。换句话说,如果第1项不满足要求,则优先考虑2、3或4并没有多大意义。对我而言,可读性是列表底部的原因是在大多数情况下我可以遵循语法。
捕获组和向前查看通常会影响模式效率。事实上,除非您在数千个输入字符串上执行此正则表达式,否则没有必要过于关注效率。更重要的是专注于模式的可读性,这可以与模式的简洁性相联系。
下面的一些模式将需要其preg_函数进行一些其他处理/标记,但这里提供了基于OP示例输入的一些模式比较: preg_split()模式:
  • /^[^A-Z]+\K|[A-Z][^A-Z]+\K/ (21步骤)
  • /(^[^A-Z]+|[A-Z][^A-Z]+)/ (26步骤)
  • /[^A-Z]+\K(?=[A-Z])/ (43步骤)
  • /(?=[A-Z])/ (50步骤)
  • /(?=[A-Z]+)/ (50步骤)
  • /([a-z]{1})[A-Z]{1}/ (53步骤)
  • /([a-z0-9])([A-Z])/ (68步骤)
  • /(?<=[a-z])(?=[A-Z])/x (94步骤) ...顺便说一下,x是无用的。
  • /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/ (134步骤)
preg_match_all()模式:
  • /[A-Z]?[a-z]+/ (14步骤)
  • /((?:^|[A-Z])[a-z]+)/ (35步骤)
我指出的是 `preg_match_all()` 和 `preg_split()` 的输出有微妙的区别。`preg_match_all()` 将输出一个二维数组,也就是说,所有的完整字符串匹配结果将在 `[0]` 子数组中; 如果使用了捕获组,那么这些子字符串将在 `[1]` 子数组中。另一方面,`preg_split()` 只输出一个一维数组,因此提供了一个更直接路径且不会臃肿的期望输出。
一些模式在处理包含 ALLCAPS/缩写子字符串的 camelCase 字符串时是不够充分的。如果这是您项目中可能出现的边缘情况,那么只考虑正确处理这些情况的模式是合理的。我不会测试 TitleCase 输入字符串,因为那太远离问题了。 新扩展的测试字符串:
oneTwoThreeFour
hasConsecutiveCAPS
newNASAModule
USAIsGreatAgain 

适用的 preg_split() 模式:
  • /[a-z]+\K|(?=[A-Z][a-z]+)/(149 步)*我必须在演示中使用 [a-z] 才能正确计数
  • /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/ (547 步)
适用的 preg_match_all() 模式:
  • /[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|$)/ (75 步)
最后,基于我的模式原则/因素层次结构,我建议使用 preg_split() 而不是 preg_match_all()(尽管模式步骤较少),以实现对所需输出结构的直接性。当然,您可以根据自己的喜好进行选择。
代码:(Demo)
$noAcronyms = 'oneTwoThreeFour';
var_export(preg_split('~^[^A-Z]+\K|[A-Z][^A-Z]+\K~', $noAcronyms, 0, PREG_SPLIT_NO_EMPTY));
echo "\n---\n";
var_export(preg_match_all('~[A-Z]?[^A-Z]+~', $noAcronyms, $out) ? $out[0] : []);

代码:(演示)
$withAcronyms = 'newNASAModule';
var_export(preg_split('~[^A-Z]+\K|(?=[A-Z][^A-Z]+)~', $withAcronyms, 0, PREG_SPLIT_NO_EMPTY));
echo "\n---\n";
var_export(preg_match_all('~[A-Z]?[^A-Z]+|[A-Z]+(?=[A-Z][^A-Z]|$)~', $withAcronyms, $out) ? $out[0] : []);

在最后的模式中,你可以将 (?=[A-Z][a-z]|$) 改为 (?![a-z]) - Casimir et Hippolyte

3
我采用了酷炫的Ridgerunner的代码(如上所示)并将其制作成一个函数:
echo deliciousCamelcase('NewNASAModule');

function deliciousCamelcase($str)
{
    $formattedStr = '';
    $re = '/
          (?<=[a-z])
          (?=[A-Z])
        | (?<=[A-Z])
          (?=[A-Z][a-z])
        /x';
    $a = preg_split($re, $str);
    $formattedStr = implode(' ', $a);
    return $formattedStr;
}

这将返回:新的NASA模块

2

另一个选项是匹配/[A-Z]?[a-z]+/ - 如果您知道输入的格式正确,它应该可以很好地工作。

[A-Z]?将匹配大写字母(或无内容)。 [a-z]+然后将匹配所有以下小写字母,直到下一个匹配。

工作示例:https://regex101.com/r/kNZfEI/1


简洁高效 - 总是偏爱这种方式。 - benjaminhull
@jbobbins - 谢谢,已更新。ideone在某个时间点过期了旧示例,因此许多旧示例仍然无法正常运行。 - Kobi
@Kobi 谢谢。只是让你知道,我从rr-的帖子中粘贴了断言文本,并且那些有多个大写字母在一起的不起作用。https://regex101.com/r/kNZfEI/2 - jbobbins

1

这个函数将camelCase转换为句子:

ucfirst(strtolower(implode(' ', preg_split('/(?=[A-Z])/', $camelCaseStr))));

"helloWorld" -> "你好,世界"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接