正则expression式来分割camelCase或TitleCase(高级)

我发现了一个辉煌的RegEx来提取驼峰或TitleCaseexpression的一部分。

(?<!^)(?=[AZ]) 

它按预期工作:

  • 价值 – >价值
  • camelValue – > camel / Value
  • TitleValue – >标题/值

例如用Java:

 String s = "loremIpsum"; words = s.split("(?<!^)(?=[AZ])"); //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个字符)。

任何想法如何改善这个正则expression式?

以下正则expression式适用于以上所有示例:

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

它通过强制负面lookbehind来工作,不仅忽略string的开始处的匹配,而且也忽略大写字母前面有另一个大写字母的匹配。 这处理像“VALUE”的情况。

正则expression式的第一部分自己在“eclipseRCPExt”上失败,不能在“RPC”和“Ext”之间进行分割。 这是第二个子句的目的: (?<!^)(?=[AZ][az] 。该子句允许在每个大写字母后紧跟一个小写字母,除了在string的开头之外。

看来你正在使这个比需要更复杂。 对于camelCase ,拆分的位置只是大写字母紧随小写字母的任何位置:

(?<=[az])(?=[AZ])

下面是这个正则expression式如何分割您的示例数据:

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

与你想要的输出唯一的区别是eclipseRCPExt ,我认为这是正确的分裂在这里。

附录 – 改进版本

注:这个答案最近得到了upvote,我意识到有一个更好的办法…

通过添加上述正则expression式的第二个替代方法,所有OP的testing用例都被正确地分割。

(?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])

以下是改进后的正则expression式如何分割示例数据:

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

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

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

我无法让aix的解决scheme工作(而且它也不能在RegExr上工作),所以我想出了我自己的testing,并且似乎完全符合你的要求:

 ((^[az]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)))) 

这里有一个使用它的例子:

 ; Regex Breakdown: This will match against each word in Camel and Pascal case strings, while properly handling acrynoms. ; (^[az]+) Match against any lower-case letters at the start of the string. ; ([AZ]{1}[az]+) Match against Title case words (one upper case followed by lower case letters). ; ([AZ]+(?=([AZ][az])|($))) 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, "((^[az]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($))))", "$1 ") newString := Trim(newString) 

在这里我用空格分隔每个单词,所以下面是一些如何转换string的例子:

  • ThisIsATitleCASEString =>这是一个标题CASEstring
  • 和ThisOneIsCamelCASE =>和这一个是骆驼CASE

上面的解决scheme做了原来的post要求,但我也需要一个正则expression式来find包含数字的骆驼和pascalstring,所以我也想出了这个变化,包括数字:

 ((^[az]+)|([0-9]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)|([0-9])))) 

以及使用它的一个例子:

 ; Regex Breakdown: This will match against each word in Camel and Pascal case strings, while properly handling acrynoms and including numbers. ; (^[az]+) 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). ; ([AZ]{1}[az]+) Match against Title case words (one upper case followed by lower case letters). ; ([AZ]+(?=([AZ][az])|($)|([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, "((^[az]+)|([0-9]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)|([0-9]))))", "$1 ") newString := Trim(newString) 

下面是一些数字string如何用这个正则expression式转换的例子:

  • myVariable123 =>我的variables123
  • my2Variables =>我的2个variables
  • The3rdVariableIsHere =>第三variables在这里
  • 12345NumsAtTheStartIncludedToo => 12345 Nums在开始包含太多

处理更多的字母,而不仅仅是AZ

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

或者:

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

比如parseXML – > parseXML

要么

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

例如XMLParser – > XMLParser


更可读的forms:

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

您可以使用下面的expression式来表示Java:

 (?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])|(?=[AZ][az])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D) 

而不是寻找不在那里的分隔符,你也可以考虑find名称组件(那些肯定存在):

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

简要

这两个顶部的答案提供了使用积极lookbehinds的代码,这是不支持所有正则expression式风味。 下面的正则expression式将捕获PascalCasecamelCase并且可以用于多种语言。

注:我意识到这个问题是关于Java,但是,我也看到这个post在其他问题标签为不同的语言多个提及,以及对这个问题的一些意见相同。

看到这里使用正则expression式

 ([AZ]+|[AZ]?[az]+)(?=[AZ]|\b) 

结果

示例input

 eclipseRCPExt SomethingIsWrittenHere TEXTIsWrittenHERE VALUE loremIpsum 

示例输出

 eclipse RCP Ext Something Is Written Here TEXT Is Written HERE VALUE lorem Ipsum 

说明

  • 匹配一个或多个大写字母字符[AZ]+
  • 匹配零个或一个大写字母字符[AZ]? ,后跟一个或多个小写字母字符[az]+
  • 确保接下来是大写字母字符[AZ]或字边界字符\b
Interesting Posts