camelCase 또는 TitleCase를 분할하는 RegEx (고급)


81

camelCase 또는 TitleCase 표현의 일부를 추출 하는 훌륭한 RegEx 를 찾았습니다 .

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

예상대로 작동합니다.

  • 가치-> 가치
  • camelValue-> 낙타 / 값
  • 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 : eclipse / RCP / Ext

즉, n 개의 대문자가 주어지면 :

  • n 문자 다음에 소문자가 오면 그룹은 다음과 같아야합니다. (n-1 문자) / (n 번째 문자 + 낮은 문자)
  • n 개의 문자가 끝에 있으면 그룹은 (n 개의 문자) 여야합니다.

이 정규식을 개선하는 방법에 대한 아이디어가 있습니까?


^네거티브 lookbehind에서 대문자에 대한 조건부 수정 자 와 다른 조건부 케이스 가 필요할 것입니다 . 확실하게 테스트하지는 않았지만 문제를 해결하기위한 최선의 방법이라고 생각합니다.
Nightfirecat 2011 년

사람이 검사하는 경우
조개

답변:


112

다음 정규식은 위의 모든 예에서 작동합니다.

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]. 이 절은 문자열의 시작 부분을 제외하고 소문자가 뒤 따르는 모든 대문자 앞에서 분할을 허용합니다.


1
이것은 PHP에서 작동하지 않는 반면 @ridgerunner는 작동합니다. PHP에서는 "lookbehind assertion이 오프셋 13에서 고정 된 길이가 아닙니다"라고 말합니다.
igorsantos07 2014 년

15
@Igoru : 정규식 맛은 다양합니다. 문제는 PHP가 아니라 Java에 관한 것이므로 대답도 마찬가지입니다.
NPE 2014 년

1
질문이 "java"로 태그가 지정되어있는 동안 질문은 코드 샘플 외에 여전히 일반적입니다 (일반적이지 않음). 거기에이 정규식의 간단한 버전이고 그 또한 교차 언어를 작동한다면, 나는 누군가가 그 :) 지적해야한다고 생각
igorsantos07

7
@Igoru : "일반 정규식"은 가상의 개념입니다.
Casimir et Hippolyte 2014

3
@ igorsantos07 : 아니요, 기본 제공 정규식 구현은 플랫폼마다 크게 다릅니다. 일부는 Perl과 유사하고 일부는 POSIX와 유사하고 일부는 중간 또는 완전히 다른 것입니다.
Christoffer Hammarström

78

필요한 것보다 더 복잡하게 만드는 것 같습니다. 들어 낙타 표기법 , 분할 위치는 단순히 어디 대문자 즉시 소문자 문자를 다음입니다 :

(?<=[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

편집 : 20130824RCPExt -> RCP / Ext 케이스 를 처리하기 위해 개선 된 버전을 추가했습니다 .


귀하의 의견에 감사드립니다. 이 예에서는 파트를 상수 이름으로 변환하기 때문에 RCP와 Ext를 구분해야합니다 (스타일 지침 : "단어를 구분하기 위해 밑줄을 사용하는 모두 대문자").이 경우 ECLIPSE_RCP_EXT보다 ECLIPSE_RCPEXT를 선호합니다.
Jmini

4
도와 주셔서 감사합니다; 문자열의 숫자를 관리하는 몇 가지 옵션을 추가하도록 정규식을 수정했습니다.(?<=[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에서 작동하지 않습니다!
Viet


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 => 세 번째 rdVariable이 여기에 있습니다.
  • 12345NumsAtTheStartIncludedToo => 시작시 12345 숫자도 포함됨

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

동일한 단순화 된 정규식 :([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Alex Suhinin 19

3

단순한 것보다 더 많은 문자를 처리하려면 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"));
    }    
}

3

간결한

여기의 두 가지 최고 답변은 모든 정규식 유형에서 지원되지 않는 긍정적 인 lookbehinds를 사용하는 코드를 제공합니다. 아래 정규식은 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]+
  • 또는 0 개 또는 1 개의 대문자 알파벳 문자 [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
안녕하세요 Maicon, StackOverflow에 오신 것을 환영합니다. 답변 해 주셔서 감사합니다. 이것이 질문에 답할 수는 있지만 다른 사람들 이 문제를 해결하는 방법 을 배울 수있는 설명은 제공하지 않습니다 . 코드에 대한 설명을 포함하도록 답변을 편집 할 수 있습니까? 감사합니다!
Tim Malone

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에 의해 제공된 이 Microsoft의 정규식과 작동 함을 .

또한 숫자 문자를 처리하는 ctwheels의 정규식을 기반으로 다음 대안을 제안하고 싶습니다 ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b)..

다음과 같은 문자열을 분할 할 수 있습니다.

운전 B2BT 무역 2019 년

...에

2019 년 B2B 거래 추진


0

자바 스크립트 솔루션

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

그들은 자바 스크립트 솔루션을 요구하는데 왜 동일한 솔루션 을 두 배로 제공 합니까? 이러한 질문이 부정확하다고 생각되면 투표를 통해 중복으로 마감하십시오.
Toto
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.