繁体   English   中英

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

[英]Split camelCase word into words with php preg_match (Regular Expression)

我将如何拆分这个词:

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

您可以将preg_split用作:

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

看见

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

您还可以将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.

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

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

请注意,此正则表达式(如 codacci 的'/(?=[AZ])/'解决方案 - 就像格式良好的驼峰式单词的魅力一样),仅匹配字符串中的一个位置并且根本不消耗任何文本。 这个解决方案还有一个额外的好处,它也可以正确处理格式不太好的伪驼峰词,例如: 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 的评论)。

虽然 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']);

@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, " " );
}
$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”。 然后你可以将它们小写,大写第一个单词或在空格上分解它们,或者使用 _ 或其他一些字符将它们分开。

简短而简单。

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

  1. 准确性(鲁棒性)——模式是否在所有情况下都是正确的并且是合理的面向未来
  2. 效率——模式应该是直接的、深思熟虑的,避免不必要的劳动
  3. 简洁——模式应该使用适当的技术来避免不必要的字符长度
  4. 可读性——模式应该尽可能简单

上述因素也恰好处于努力服从的等级秩序中。 换句话说,当 1 不能完全满足要求时,优先考虑 2、3 或 4 对我来说没有多大意义。 可读性对我来说是最重要的,因为在大多数情况下我可以遵循语法。

捕获组和环视通常会影响模式效率。 事实是,除非您在数以千计的输入字符串上执行此正则表达式,否则无需为效率操劳。 关注与模式简洁相关的模式可读性可能更重要。

下面的一些模式需要通过它们的preg_函数进行一些额外的处理/标记,但这里有一些基于 OP 示例输入的模式比较:

preg_split()模式:

  • /^[^AZ]+\\K|[AZ][^AZ]+\\K/ (21 步)
  • /(^[^AZ]+|[AZ][^AZ]+)/ (26 步)
  • /[^AZ]+\\K(?=[AZ])/ (43 步)
  • /(?=[AZ])/ (50 步)
  • /(?=[AZ]+)/ (50 步)
  • /([az]{1})[AZ]{1}/ (53 步)
  • /([a-z0-9])([AZ])/ (68 步)
  • /(?<=[az])(?=[AZ])/x (94 步) ...为了记录, x没有用。
  • /(?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])/ (134 步)

preg_match_all()模式:

  • /[AZ]?[az]+/ (14 步)
  • /((?:^|[AZ])[az]+)/ (35 步)

我将指出preg_match_all()preg_split()的输出之间存在细微差别。 preg_match_all()将输出一个二维数组,换句话说,所有的全字符串匹配将在[0]子数组中; 如果使用了捕获组,则这些子字符串将位于[1]子数组中。 另一方面, preg_split()只输出一个一维数组,因此提供了一个不那么臃肿和更直接的到达所需输出的路径。

在处理包含 ALLCAPS/acronym 子字符串的驼峰字符串时,某些模式是不够的。 如果这是您项目中可能出现的边缘情况,那么只考虑正确处理这些情况的模式是合乎逻辑的。 我不会测试 TitleCase 输入字符串,因为这离问题太远了。

新的扩展测试字符串电池:

oneTwoThreeFour
hasConsecutiveCAPS
newNASAModule
USAIsGreatAgain 

合适的preg_split()模式:

  • /[az]+\\K|(?=[AZ][az]+)/ (149 步) *我必须使用[az]来让演示正确计数
  • /(?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])/ (547 步)

合适的preg_match_all()模式:

  • /[AZ]?[az]+|[AZ]+(?=[AZ][az]|$)/ (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] : []);

我把很酷的家伙 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

另一种选择是匹配/[AZ]?[az]+/ - 如果您知道您的输入格式正确,它应该可以很好地工作。

[AZ]? 将匹配一个大写字母(或什么都不匹配)。 [az]+然后将匹配所有后面的小写字母,直到下一个匹配。

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

您可以将“滑动”从小写拆分为大写,因此:

$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 中每个对应的项目对中重建单词

希望这可以帮助

首先 codacci 非常感谢你的模式,它帮助了很多!

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

例如 thisIsACamelcaseSentence。

我在执行两步 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; 
}

随意使用它,如果有一种“更简单”的方法可以一步完成,请发表评论!

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

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

    return ucwords(implode($splitCamelArray, ' '));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM