RegEx để tách camelCase hoặc TitleCase (nâng cao)


81

Tôi đã tìm thấy một RegEx tuyệt vời để trích xuất một phần của biểu thức camelCase hoặc TitleCase.

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

Nó hoạt động như mong đợi:

  • giá trị -> giá trị
  • camelValue -> lạc đà / Giá trị
  • TitleValue -> Tiêu đề / Giá trị

Ví dụ với Java:

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

Vấn đề của tôi là nó không hoạt động trong một số trường hợp:

  • Trường hợp 1: VALUE -> V / A / L / U / E
  • Trường hợp 2: eclipseRCPExt -> eclipse / R / C / P / Ext

Theo suy nghĩ của tôi, kết quả sẽ là:

  • Trường hợp 1: VALUE
  • Trường hợp 2: eclipse / RCP / Ext

Nói cách khác, n ký tự hoa cho trước:

  • nếu n ký tự được theo sau bởi ký tự viết thường, các nhóm phải là: (n-1 ký tự) / (n-thứ ký tự + ký tự thường)
  • nếu n ký tự ở cuối, nhóm phải là: (n ký tự).

Bất kỳ ý tưởng về cách cải thiện regex này?


Có vẻ như bạn có thể sẽ cần một bổ ngữ có điều kiện trên ^và một trường hợp có điều kiện khác cho các chữ cái viết hoa ở dạng phủ định. Chưa được kiểm tra chắc chắn, nhưng tôi nghĩ đó là cách tốt nhất để bạn khắc phục sự cố.
Nightfirecat

Nếu ai đang kiểm tra
Clam

Câu trả lời:


112

Regex sau hoạt động cho tất cả các ví dụ trên:

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

Nó hoạt động bằng cách buộc cái nhìn sau phủ định không chỉ bỏ qua các kết quả phù hợp ở đầu chuỗi mà còn bỏ qua các kết quả phù hợp có một chữ hoa đứng trước một chữ hoa khác. Điều này xử lý các trường hợp như "VALUE".

Phần đầu tiên của regex không thành công trên "eclipseRCPExt" do không thể phân chia giữa "RPC" và "Ext". Đây là mục đích của mệnh đề thứ hai: (?<!^)(?=[A-Z][a-z]. Mệnh đề này cho phép phân tách trước mỗi ký tự hoa theo sau là một ký tự thường, ngoại trừ ở đầu chuỗi.


1
cái này không hoạt động trên PHP, trong khi @ ridgerunner thì có. Trên PHP, nó nói "xác nhận lookbehind không có độ dài cố định tại offset 13".
igorsantos07

15
@Igoru: Hương vị của Regex khác nhau. Câu hỏi là về Java, không phải PHP, và câu trả lời cũng vậy.
NPE

1
trong khi câu hỏi được gắn thẻ là "java" thì câu hỏi vẫn là chung chung - bên cạnh các mẫu mã (không bao giờ có thể là chung chung). Vì vậy, nếu có một phiên bản đơn giản của regex này và điều đó cũng làm việc qua ngôn ngữ, tôi nghĩ một người nào đó phải chỉ mà :)
igorsantos07

7
@Igoru: "regex chung" là một khái niệm tưởng tượng.
Casimir et Hippolyte

3
@ igorsantos07: Không, việc triển khai regex tích hợp sẵn rất khác nhau giữa các nền tảng. Một số đang cố gắng trở nên giống Perl, một số đang cố gắng trở nên giống POSIX và một số thì lại ở giữa hoặc hoàn toàn khác.
Christoffer Hammarström

76

Có vẻ như bạn đang làm cho việc này phức tạp hơn mức cần thiết. Đối với camelCase , vị trí phân tách chỉ đơn giản là bất kỳ nơi nào một ký tự hoa ngay sau một ký tự viết thường:

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

Đây là cách regex này chia nhỏ dữ liệu mẫu của bạn:

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

Sự khác biệt duy nhất so với đầu ra mong muốn của bạn là với eclipseRCPExt, mà tôi cho rằng được phân chia chính xác ở đây.

Phụ lục - Phiên bản cải tiến

Lưu ý: Câu trả lời này gần đây đã nhận được một ủng hộ và tôi nhận ra rằng có một cách tốt hơn ...

Bằng cách thêm phương án thay thế thứ hai vào regex ở trên, tất cả các trường hợp thử nghiệm của OP đều được phân tách chính xác.

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

Đây là cách regex cải tiến chia nhỏ dữ liệu mẫu:

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

Chỉnh sửa: 20130824 Đã thêm phiên bản cải tiến để xử lý RCPExt -> RCP / Exttrường hợp.


Cảm ơn vì đầu vào của bạn. Tôi cần tách RCP và Ext trong ví dụ này, vì tôi chuyển đổi các phần thành một tên không đổi (Hướng dẫn kiểu: "tất cả chữ hoa sử dụng dấu gạch dưới để phân tách các từ.") Trong trường hợp này, tôi thích ECLIPSE_RCP_EXT hơn ECLIPSE_RCPEXT.
Jmini

4
Cảm ơn đã giúp đỡ; Tôi đã sửa đổi regex của bạn để thêm một vài tùy chọn để chăm sóc cho các số trong chuỗi:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
thoroc

Đây là câu trả lời tốt nhất! Đơn giản và rõ ràng. Tuy nhiên câu trả lời này và RegEx ban đầu của OP không hoạt động cho Javascript & Golang!
Việt


10

Tôi không thể làm cho giải pháp của aix hoạt động (và nó cũng không hoạt động trên RegExr), vì vậy tôi đã đưa ra giải pháp của riêng mình mà tôi đã thử nghiệm và dường như làm chính xác những gì bạn đang tìm kiếm:

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

và đây là một ví dụ về việc sử dụng nó:

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

Ở đây tôi đang phân tách từng từ bằng dấu cách, vì vậy đây là một số ví dụ về cách chuỗi được chuyển đổi:

  • ThisIsATitleCASEString => Đây là chuỗi trường hợp tiêu đề
  • andThisOneIsCamelCASE => và This One Is Camel CASE

Giải pháp ở trên thực hiện những gì bài đăng gốc yêu cầu, nhưng tôi cũng cần một regex để tìm các chuỗi lạc đà và pascal bao gồm các số, vì vậy tôi cũng đã đưa ra biến thể này để bao gồm các số:

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

và một ví dụ về việc sử dụng nó:

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

Và đây là một số ví dụ về cách một chuỗi với các số được chuyển đổi với regex này:

  • myVariable123 => Biến 123 của tôi
  • my2Variables => 2 biến của tôi
  • The3rdVariableIsHere => The 3 rdVariable is Here
  • 12345NumsAtTheStartIncludedToo => 12345 Nums lúc bắt đầu cũng được bao gồm

1
Quá nhiều nhóm chụp không cần thiết. Bạn có thể viết nó thành: (^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))cho cái đầu tiên và (^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))cho cái thứ hai. Phần lớn bên ngoài cũng có thể được loại bỏ, nhưng cú pháp để tham chiếu đến toàn bộ kết hợp không thể di động giữa các ngôn ngữ ( $0$&là 2 khả năng).
nhahtdh

Cùng một regexp được đơn giản hóa:([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Alex Suhinin

3

Để xử lý nhiều chữ cái hơn là chỉ A-Z:

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

Hoặc:

  • Phân tách sau bất kỳ ký tự thường nào, được theo sau bởi ký tự hoa.

Ví dụ: parseXML-> parse, XML.

hoặc là

  • Phân chia sau bất kỳ chữ cái nào, theo sau là chữ hoa và chữ thường.

Ví dụ: XMLParser-> XML, Parser.


Ở dạng dễ đọc hơn:

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

Tóm tắt

Cả hai câu trả lời hàng đầu ở đây đều cung cấp mã sử dụng giao diện tích cực, không được hỗ trợ bởi tất cả các phiên bản regex. Regex bên dưới sẽ ghi lại cả hai PascalCasecamelCasevà có thể được sử dụng bằng nhiều ngôn ngữ.

Lưu ý: Tôi nhận thấy câu hỏi này liên quan đến Java, tuy nhiên, tôi cũng thấy nhiều đề cập về bài đăng này trong các câu hỏi khác được gắn thẻ cho các ngôn ngữ khác nhau, cũng như một số nhận xét về câu hỏi này cho cùng một câu hỏi.

Xem regex này đang được sử dụng tại đây

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

Các kết quả

Đầu vào mẫu

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Đầu ra mẫu

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Giải trình

  • Khớp một hoặc nhiều ký tự alpha viết hoa [A-Z]+
  • Hoặc khớp với 0 hoặc một ký tự alpha viết hoa [A-Z]?, theo sau là một hoặc nhiều ký tự alpha viết thường[a-z]+
  • Đảm bảo những gì sau đây là ký tự alpha viết hoa [A-Z]hoặc ký tự ranh giới từ\b


0

Bạn có thể sử dụng biểu thức bên dưới cho Java:

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

3
Xin chào Maicon, chào mừng bạn đến với StackOverflow và cảm ơn bạn đã trả lời. Mặc dù điều này có thể trả lời câu hỏi, nhưng nó không cung cấp bất kỳ lời giải thích nào để người khác tìm hiểu cách giải quyết vấn đề. Bạn có thể chỉnh sửa câu trả lời của mình để bao gồm phần giải thích mã của bạn không? Cảm ơn bạn!
Tim Malone

0

Thay vì tìm kiếm các dấu phân cách không có ở đó, bạn cũng có thể xem xét việc tìm các thành phần tên (chắc chắn là ở đó):

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

Kết quả này [eclipse, 福福, RCP, Ext]. Tất nhiên, việc chuyển đổi sang một mảng rất đơn giản.


0

Tôi có thể xác nhận rằng chuỗi regex ([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)được cung cấp bởi ctwheels, ở trên, hoạt động với phiên bản regex của Microsoft.

Tôi cũng muốn để đề nghị thay thế sau đây, dựa trên regex ctwheels', mà xử lý ký tự số: ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

Điều này có thể chia các chuỗi chẳng hạn như:

DrivingB2BTradeIn2019Onwards

đến

Thúc đẩy giao dịch B2B trong năm 2019 trở đi


0

Giải pháp 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('!');
};

Họ yêu cầu một giải pháp JavaScript Và tại sao bạn lại đưa ra hai giải pháp giống nhau ? Nếu bạn cho rằng những câu hỏi đó là thụt lề, hãy bỏ phiếu để đóng một câu hỏi là trùng lặp.
Toto
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.