88

我找到了一个出色的 RegEx来提取 camelCase 或 TitleCase 表达式的一部分。

 (?<!^)(?=[A-Z])

它按预期工作:

  • 价值 -> 价值
  • 骆驼值->骆驼/价值
  • TitleValue -> 标题/值

以 Java 为例:

String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
//words equals words = new String[]{"lorem","Ipsum"}

我的问题是它在某些情况下不起作用:

  • 案例 1:值 -> V / A / L / U / E
  • 案例2:eclipseRCPExt -> eclipse / R / C / P / Ext

在我看来,结果应该是:

  • 案例 1:价值
  • 案例2:eclipse/RCP/Ext

换句话说,给定 n 个大写字符:

  • 如果 n 个字符后跟小写字符,则组应为:(n-1 个字符)/(第 n 个字符 + 小写字符)
  • 如果 n 个字符在末尾,则该组应为:(n 个字符)。

关于如何改进这个正则表达式的任何想法?

4

11 回答 11

117

以下正则表达式适用于上述所有示例:

public static void main(String[] args)
{
    for (String w : "camelValue".split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) {
        System.out.println(w);
    }
}   

它的工作原理是强制否定的lookbehind不仅忽略字符串开头的匹配,而且还忽略一个大写字母前面有另一个大写字母的匹配。这处理像“VALUE”这样的情况。

由于未能在“RPC”和“Ext”之间拆分,正则表达式的第一部分本身在“eclipseRCPExt”上失败。这就是第二条的目的:(?<!^)(?=[A-Z][a-z]. 此子句允许在每个大写字母后跟一个小写字母之前进行拆分,字符串开头除外。

于 2011-09-29T07:45:57.340 回答
86

看起来你让这比它需要的更复杂。对于camelCase,拆分位置只是一个大写字母紧跟一个小写字母的任何地方:

(?<=[a-z])(?=[A-Z])

以下是此正则表达式如何拆分您的示例数据:

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCPExt

与您所需输出的唯一区别是eclipseRCPExt,我认为在此处正确拆分。

附录 - 改进版

注意:这个答案最近得到了赞成,我意识到有更好的方法......

通过为上述正则表达式添加第二个替代方案,所有 OP 的测试用例都被正确拆分。

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

以下是改进的正则表达式如何拆分示例数据:

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCP / Ext

编辑:20130824添加改进版本来处理RCPExt -> RCP / Ext案例。

于 2011-09-29T15:27:26.147 回答
32

另一种解决方案是在commons-lang中使用专用方法:StringUtils#splitByCharacterTypeCamelCase

于 2011-09-29T18:56:27.833 回答
10

我无法让 aix 的解决方案正常工作(它也不能在 RegExr 上工作),所以我想出了我自己的,我已经测试过并且似乎完全符合您的要求:

((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))

这是一个使用它的例子:

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms.
;   (^[a-z]+)                       Match against any lower-case letters at the start of the string.
;   ([A-Z]{1}[a-z]+)                Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))", "$1 ")
newString := Trim(newString)

在这里,我用空格分隔每个单词,所以这里有一些如何转换字符串的示例:

  • ThisIsATitleCASEString => 这是标题 CASE 字符串
  • andThisOneIsCamelCASE => 这个是 Camel CASE

上面的这个解决方案可以满足原始帖子的要求,但我还需要一个正则表达式来查找包含数字的骆驼和帕斯卡字符串,所以我还想出了这个变体来包含数字:

((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))

以及使用它的一个例子:

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms and including numbers.
;   (^[a-z]+)                               Match against any lower-case letters at the start of the command.
;   ([0-9]+)                                Match against one or more consecutive numbers (anywhere in the string, including at the start).
;   ([A-Z]{1}[a-z]+)                        Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)|([0-9])))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string or a number.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))", "$1 ")
newString := Trim(newString)

以下是一些示例,说明如何使用此正则表达式转换带有数字的字符串:

  • myVariable123 => 我的变量 123
  • my2Variables => 我的 2 个变量
  • The3rdVariableIsHere => 第 3 个 rdVariable 在这里
  • 12345NumsAtTheStartIncludedToo => 12345 开头的数字也包括在内
于 2012-03-11T06:40:53.697 回答
4

要处理更多的字母,而不仅仅是A-Z

s.split("(?<=\\p{Ll})(?=\\p{Lu})|(?<=\\p{L})(?=\\p{Lu}\\p{Ll})");

任何一个:

  • 在任何小写字母后拆分,后跟大写字母。

例如parseXML-> parse, XML.

或者

  • 在任何字母后拆分,即后跟大写字母和小写字母。

例如XMLParser-> XML, Parser.


以更易读的形式:

public class SplitCamelCaseTest {

    static String BETWEEN_LOWER_AND_UPPER = "(?<=\\p{Ll})(?=\\p{Lu})";
    static String BEFORE_UPPER_AND_LOWER = "(?<=\\p{L})(?=\\p{Lu}\\p{Ll})";

    static Pattern SPLIT_CAMEL_CASE = Pattern.compile(
        BETWEEN_LOWER_AND_UPPER +"|"+ BEFORE_UPPER_AND_LOWER
    );

    public static String splitCamelCase(String s) {
        return SPLIT_CAMEL_CASE.splitAsStream(s)
                        .collect(joining(" "));
    }

    @Test
    public void testSplitCamelCase() {
        assertEquals("Camel Case", splitCamelCase("CamelCase"));
        assertEquals("lorem Ipsum", splitCamelCase("loremIpsum"));
        assertEquals("XML Parser", splitCamelCase("XMLParser"));
        assertEquals("eclipse RCP Ext", splitCamelCase("eclipseRCPExt"));
        assertEquals("VALUE", splitCamelCase("VALUE"));
    }    
}
于 2013-02-11T16:09:56.027 回答
4

简短的

此处的两个最佳答案都提供了使用正向回溯的代码,并非所有正则表达式都支持该代码。下面的正则表达式将同时捕获PascalCase并且camelCase可以用于多种语言。

注意:我确实意识到这个问题是关于 Java 的,但是,我也看到在针对不同语言标记的其他问题中多次提到了这篇文章,以及对此问题的一些评论。

代码

在此处查看此正则表达式

([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)

结果

样本输入

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

样本输出

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

解释

  • 匹配一个或多个大写字母字符[A-Z]+
  • 匹配零个或一个大写字母字符[A-Z]?,后跟一个或多个小写字母字符[a-z]+
  • 确保后面是大写字母字符[A-Z]或单词边界字符\b
于 2017-09-25T15:54:43.393 回答
2

您可以使用 StringUtils。splitByCharacterTypeCamelCase(“loremIpsum”)来自 Apache Commons Lang。

于 2020-03-13T14:15:41.403 回答
0

您可以将以下表达式用于 Java:

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?=[A-Z][a-z])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)
于 2016-07-10T23:31:25.343 回答
0

除了寻找不存在的分隔符之外,您还可以考虑查找名称组件(那些肯定存在):

String test = "_eclipse福福RCPExt";

Pattern componentPattern = Pattern.compile("_? (\\p{Upper}?\\p{Lower}+ | (?:\\p{Upper}(?!\\p{Lower}))+ \\p{Digit}*)", Pattern.COMMENTS);

Matcher componentMatcher = componentPattern.matcher(test);
List<String> components = new LinkedList<>();
int endOfLastMatch = 0;
while (componentMatcher.find()) {
    // matches should be consecutive
    if (componentMatcher.start() != endOfLastMatch) {
        // do something horrible if you don't want garbage in between

        // we're lenient though, any Chinese characters are lucky and get through as group
        String startOrInBetween = test.substring(endOfLastMatch, componentMatcher.start());
        components.add(startOrInBetween);
    }
    components.add(componentMatcher.group(1));
    endOfLastMatch = componentMatcher.end();
}

if (endOfLastMatch != test.length()) {
    String end = test.substring(endOfLastMatch, componentMatcher.start());
    components.add(end);
}

System.out.println(components);

这输出[eclipse, 福福, RCP, Ext]. 转换为数组当然很简单。

于 2017-06-03T16:16:57.587 回答
0

我可以确认([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)上面 ctwheels 给出的正则表达式字符串适用于微软的正则表达式。

我还想建议以下替代方案,基于 ctwheels 的正则表达式,它处理数字字符:([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

这能够拆分字符串,例如:

驾驶 B2B 贸易在 2019 年以后

2019 年以后推动 B2B 贸易

于 2019-01-02T16:31:27.420 回答
0

JavaScript 解决方案

/**
 * howToDoThis ===> ["", "how", "To", "Do", "This"]
 * @param word word to be split
 */
export const splitCamelCaseWords = (word: string) => {
    if (typeof word !== 'string') return [];
    return word.replace(/([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)/g, '!$&').split('!');
};
于 2020-07-28T04:38:08.783 回答