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


71

我将如何拆分单词:

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 stackoverflow.com/questions/4502273/...
Gondim

Answers:


81

您还可以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]?
亚伦J朗

2
@AaronJLang否,因为外部括号捕获了整个组,包括子组。这是一个小组,他不想使$ matches集合混乱。
Eli Gassert 2014年

2
这对我来说失败了,使用了“ TestID”,使用了:“ preg_match_all('/((?:^ | [AZ])[az] +)/',$ key,$ matches); die(implode('',$ matches [ 0]));“ 因为它不喜欢CONSECUTIVE CAPS问题。我需要用空格分隔大小写更改,@ blak3r的解决方案为我工作:stackoverflow.com/a/17122207/539149
Zack Morris

1
HTMLParser这样的字符串更好的解决方案将起作用:stackoverflow.com/a/6572999/1697320
Maciej Sz

按照@TarranJones的规定(尽管表达不清晰),您不需要外部括号。匹配的字符串'/(?:^|[A-Z])[a-z]+/'足以产生一个数组(而不是两个)。这是因为preg_match_all()无需您特别指定即可自动捕获匹配的所有实例。
cartbeforehorse

77

您可以preg_split用作:

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

看见

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


1
美丽的解决方案
Diego Sagrera

54

我知道这是一个可以接受的老问题,但是恕我直言,有一个更好的解决方案:

<?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单词的魅力一样工作),仅匹配字符串中的位置,并且完全不消耗文本。此解决方案的另一个好处是,它对于格式不正确的伪大写字母单词也可以正确工作,例如:StartsWithCap和:hasConsecutiveCAPS

输入:

oneTwoThreeFour
StartsWithCap
hasConsecutiveCAPS
NewNASAModule

输出:

Word 1 of 4 = "one"
Word 2 of 4 = "Two"
Word 3 of 4 = "Three"
Word 4 of 4 = "Four"

Word 1 of 3 = "Starts"
Word 2 of 3 = "With"
Word 3 of 3 = "Cap"

Word 1 of 3 = "has"
Word 2 of 3 = "Consecutive"
Word 3 of 3 = "CAPS"

Word 1 of 3 = "New"
Word 2 of 3 = "NASA"
Word 3 of 3 = "Module"

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


这是一个更好的解决方案,可以第一次使用(其他人向数组中添加了空白值,这是完美的!谢谢!+1
Anil

1
字符串似乎有问题NewNASAModule((输出:[New, NASAModule];我期望[New, NASA, Module]))
rr-

1
@rr-是的,您是正确的。请参阅我的其他更新答案,它拆分为:NewNASAModule正确:RegEx拆分camelCase或TitleCase(高级)
ridgerunner 2014年

它不包含数字的情况。由于某些原因,其他回复者也忽略了这个基本事实。例如“ Css3Transform”或类似内容
Onkeltem

15

尽管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, " " );
}

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

诀窍是可重复的模式$ 1 $ 2 $ 1 $ 2或更低的UPPERlower UPPERlower等....例如helloWorld = $ 1匹配“ hello”,$ 2匹配“ W”,$ 1再次匹配“ orld”,因此总之,您得到$ 1 $ 2 $ 1或“ hello World”,将HelloWorld匹配为$ 2 $ 1 $ 2 $ 1或再次匹配“ Hello World”。然后,您可以将第一个单词大写,或者在空格处将它们爆炸,或者使用_或其他字符将它们分开。

简短而简单。


4

在确定适合您项目的最佳模式时,您需要考虑以下模式因素:

  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 / acronym子字符串的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()(虽然具有较少的步骤的图案)作为直接的期望的输出结构的问题。(当然,选择您喜欢的任何东西)

代码:(演示

$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] : []);

2

我拿了酷家伙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;
}

这将返回: New NASA Module


1

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

[A-Z]?会匹配一个大写字母(或什么都不匹配)。[a-z]+然后将匹配以下所有小写字母,直到下一个匹配为止。

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


好又瘦-总是喜欢这种方式。
benjaminhull

@jbobbins-谢谢,更新。ideone在某些时候使旧示例过期了,因此许多旧示例仍然无效。
Kobi

@Kobi谢谢。请注意,我在rr-上粘贴了帖子中的断言文本,而多个大写字母的断言文本却不起作用。 regex101.com/r/kNZfEI/2
jbobbins,

0

您可以将“滑行”从小写变为大写:

$parts = preg_split('/([a-z]{1})[A-Z]{1}/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);        
//PREG_SPLIT_DELIM_CAPTURE to also return bracketed things
var_dump($parts);

烦人的是,您将不得不从$ parts中每个对应的项目对重建单词

希望这可以帮助


糟糕,这可能会在CONSECUTIVE CAPS问题上失败
Daniel Rhodes

0

首先,codaddict谢谢您的模式,它对您有很大帮助!

我需要一个可以在介词“ a”存在的情况下工作的解决方案:

例如,这是ACamelcase句子。

我通过两步preg_match找到了解决方案,并使用一些选项创建了一个函数:

/*
 * input: 'thisIsACamelCaseSentence' output: 'This Is A Camel Case Sentence'
 * options $case: 'allUppercase'[default] >> 'This Is A Camel Case Sentence'
 *                'allLowerCase'          >> 'this is a camel case sentence'
 *                'firstUpperCase'        >> 'This is a camel case sentence'
 * @return: string
 */

function camelCaseToWords($string, $case = null){
    isset($case) ? $case = $case : $case = 'allUpperCase';

    // Find first occurances of two capitals
    preg_match_all('/((?:^|[A-Z])[A-Z]{1})/',$string, $twoCapitals);

    // Split them with the 'zzzzzz' string. e.g. 'AZ' turns into 'AzzzzzzZ'
    foreach($twoCapitals[0] as $match){
        $firstCapital = $match[0];
        $lastCapital = $match[1];
        $temp = $firstCapital.'zzzzzz'.$lastCapital;
        $string = str_replace($match, $temp, $string);  
    }

    // Now split words
    preg_match_all('/((?:^|[A-Z])[a-z]+)/', $string, $words);

    $output = "";
    $i = 0;
    foreach($words[0] as $word){

            switch($case){
                case 'allUpperCase':
                $word = ucfirst($word);
                break;

                case 'allLowerCase': 
                $word = strtolower($word);
                break;

                case 'firstUpperCase':
                ($i == 0) ? $word = ucfirst($word) : $word = strtolower($word);
                break;                  
            }

            // remove te 'zzzzzz' from a word if it has
            $word = str_replace('zzzzzz','', $word);    
            $output .= $word." ";
            $i++;
    }
    return $output; 
}

随时使用它,如果有一步的“简便”方法,请发表评论!


0

基于@codaddict答案的完整功能:

function splitCamelCase($str) {
    $splitCamelArray = preg_split('/(?=[A-Z])/', $str);

    return ucwords(implode($splitCamelArray, ' '));
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.