RegEx拆分camelCase或TitleCase(高级)


80

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

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

它按预期工作:

  • 值->值
  • camelValue-> camel /值
  • TitleValue->标题/值

例如,使用Java:

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

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

  • 情况1:VALUE-> V / A / L / U / E
  • 情况2:eclipseRCPExt-> eclipse / R / C / P / Ext

在我看来,结果应该是:

  • 情况1:VALUE
  • 情况2:日食/ RCP /外部

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

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

关于如何改善此正则表达式的任何想法吗?


似乎您可能需要在条件^后面加上一个条件修饰符,并在否定的后面加一个大写字母。还没有经过确定的测试,但是我认为这是解决问题的最佳选择。
Nightfirecat's

如果有人在检查
Clam

Answers:


112

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

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

它通过强制否定的向后看不仅在字符串的开头忽略匹配项,而且在大写字母后跟另一个大写字母的情况下也忽略匹配项。这样可以处理“ VALUE”之类的情况。

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


1
这在PHP上不起作用,而@ridgerunner则可以。在PHP上,它说“后置断言在偏移量13处不是固定长度”。
igorsantos07年

15
@Igoru:正则表达式的口味各不相同。问题是关于Java的,而不是PHP的,答案是。
NPE 2014年

1
当问题被标记为“ java”时,该问题仍然是通用的-除了代码示例(永远不可能是通用的)。所以,如果有这个正则表达式的简单版本并且也可以跨语言工作,我想有人应该指出:)
igorsantos07

7
@Igoru:“通用正则表达式”是一个虚构的概念。
Casimir et Hippolyte 2014年

3
@ igorsantos07:不,内置的正则表达式实现在平台之间千差万别。有些试图像Perl,有些试图像POSIX,有些则介于两者之间或完全不同。
ChristofferHammarström,2017年

75

看来您正在使它变得比所需的更加复杂。对于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案例。


感谢您的输入。在此示例中,我需要将RCP和Ext分开,因为我将部件转换为常量名称(样式准则:“所有大写字母均使用下划线分隔单词。”)在这种情况下,我更喜欢ECLIPSE_RCP_EXT而不是ECLIPSE_RCPEXT。
Jmini 2011年

3
谢谢您的帮助; 我已经修改了您的正则表达式,以添加几个选项来照顾字符串中的数字:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
thoroc

这是最好的答案!简单明了。但是,此答案和OP的原始RegEx不适用于Javascript和Golang!
越南


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 =>这是一个标题案例字符串
  • 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个变量
  • 3rdVariableIsHere =>第3rdVariable在这里
  • 12345NumsAtTheStartIncludedToo => 12345 Nums在开始时也包含

1
不必要的捕获组太多。您可能将其编写为:(^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))对于第一个,(^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))对于第二个。也可以删除最外面的部分,但是引用整个匹配项的语法在语言之间是不可移植的($0并且$&有两种可能)。
nhahtdh 2014年

相同的简化正则表达式:([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Alex Suhinin

3

处理更多的信件,不仅仅是A-Z

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

要么:

  • 在任何小写字母之后分割,后面跟着大写字母。

例如parseXML- > parseXML

要么

  • 在任何字母之后分割,然后是大写字母和小写字母。

例如XMLParser- > XMLParser


以更具可读性的形式:

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

3

简要

此处的两个最高答案都使用正向隐式提供了代码,并非所有正则表达式都支持。下面的正则表达式将同时捕获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


0

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

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

3
嗨,麦孔,欢迎来到StackOverflow,并感谢您的回答。尽管这可以回答问题,但并没有为其他人提供任何解释以学习如何解决问题的解释。您可以编辑答案以包含代码说明吗?谢谢!
蒂姆·马隆

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]。转换为数组当然很简单。


0

我可以确认([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)上面ctwheels给出的regex字符串可以与Microsoft regex风格一起使用。

我还想根据ctwheels的正则表达式提出以下替代方案,该替代方案处理数字字符:([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b)

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

从2019年开始驾驶B2BTrade

从2019年开始推动B2B贸易


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('!');
};

他们要求一个JavaScript解决方案,为什么还要提供两次相同的解决方案呢?如果您认为这些问题是相同的,请投票关闭一个重复的问题。
托托
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.