Làm cách nào để chuyển đổi CamelCase thành tên người có thể đọc được trong Java?


157

Tôi muốn viết một phương thức chuyển đổi CamelCase thành một tên dễ đọc với con người.

Đây là trường hợp thử nghiệm:

public void testSplitCamelCase() {
    assertEquals("lowercase", splitCamelCase("lowercase"));
    assertEquals("Class", splitCamelCase("Class"));
    assertEquals("My Class", splitCamelCase("MyClass"));
    assertEquals("HTML", splitCamelCase("HTML"));
    assertEquals("PDF Loader", splitCamelCase("PDFLoader"));
    assertEquals("A String", splitCamelCase("AString"));
    assertEquals("Simple XML Parser", splitCamelCase("SimpleXMLParser"));
    assertEquals("GL 11 Version", splitCamelCase("GL11Version"));
}

5
Đầu tiên, bạn sẽ cần chỉ định các quy tắc chuyển đổi. Ví dụ, làm thế nào để PDFLoadertrở thành PDF Loader?
Jørn Schou-Rode

2
Tôi gọi định dạng đó là "PascalCase". Trong "camelCase", chữ cái đầu tiên nên viết thường. Ít nhất là về các nhà phát triển có liên quan. msdn.microsoft.com/en-us/l Library
x2dbyw72 (v = vs.71) .aspx

Câu trả lời:


336

Điều này hoạt động với testcase của bạn:

static String splitCamelCase(String s) {
   return s.replaceAll(
      String.format("%s|%s|%s",
         "(?<=[A-Z])(?=[A-Z][a-z])",
         "(?<=[^A-Z])(?=[A-Z])",
         "(?<=[A-Za-z])(?=[^A-Za-z])"
      ),
      " "
   );
}

Đây là một khai thác thử nghiệm:

    String[] tests = {
        "lowercase",        // [lowercase]
        "Class",            // [Class]
        "MyClass",          // [My Class]
        "HTML",             // [HTML]
        "PDFLoader",        // [PDF Loader]
        "AString",          // [A String]
        "SimpleXMLParser",  // [Simple XML Parser]
        "GL11Version",      // [GL 11 Version]
        "99Bottles",        // [99 Bottles]
        "May5",             // [May 5]
        "BFG9000",          // [BFG 9000]
    };
    for (String test : tests) {
        System.out.println("[" + splitCamelCase(test) + "]");
    }

Nó sử dụng regex phù hợp với độ dài bằng không với lookbehind và nhìn về phía trước để tìm nơi chèn khoảng trắng. Về cơ bản có 3 mẫu và tôi sử dụng String.formatđể đặt chúng lại với nhau để dễ đọc hơn.

Ba mẫu là:

UC phía sau tôi, UC theo sau LC trước mặt tôi

  XMLParser   AString    PDFLoader
    /\        /\           /\

không phải UC phía sau tôi, UC trước mặt tôi

 MyClass   99Bottles
  /\        /\

Thư phía sau tôi, không thư trước mặt tôi

 GL11    May5    BFG9000
  /\       /\      /\

Người giới thiệu

Câu hỏi liên quan

Sử dụng giao diện kết hợp độ dài bằng không để phân chia:


1
Khái niệm này cũng hoạt động trong C # (với cùng một biểu thức chính quy, nhưng một khung biểu thức chính quy khác nhau một chút, tất nhiên). Công việc tuyệt vời Cảm ơn!
gmm

Dường như không hoạt động với tôi trên Python, có thể là do công cụ regex không giống nhau. Tôi sẽ phải cố gắng làm một cái gì đó kém thanh lịch, tôi sợ. :)
MarioVilas

2
Ai đó có thể vui lòng giải thích% s |% s |% s có nghĩa gì đối với các mẫu thử và nói chung không?
Ari53nN3o

1
@ Ari53nN3o: " %s" là chỗ dành cho các String.format(String format, args...)đối số. Bạn cũng có thể gọi theo chỉ mục:String.format("%$1s|%$2s|%$3s", ...
Ông Polywhirl

Làm thế nào điều này sẽ làm việc trong c #? relaceAllTôi cũng không muốn thêm split nếu chuỗi có " ." trong đó.
sarojanand

119

Bạn có thể làm điều đó bằng cách sử dụng org.apache.commons.lang.StringUtils

StringUtils.join(
     StringUtils.splitByCharacterTypeCamelCase("ExampleTest"),
     ' '
);

9
Giải pháp này tốt hơn nhiều so với giải pháp nâng cao nhất vì: a) Nó không phát minh lại bánh xe: commons-lang là một tiêu chuẩn thực tế và nó hoạt động tốt, rất tập trung vào hiệu suất. b) Khi quá trình chuyển đổi được thực hiện rất nhiều lần, phương thức này nhanh hơn nhiều so với phương thức dựa trên regex: đây là điểm chuẩn của tôi để thực hiện các thử nghiệm đã nói ở trên 100.000 lần: `` `Phương thức dựa trên regex mất 4820 mili giây ///// ///// Phương thức dựa trên commons-lang mất 232 mili giây `` `nhanh hơn khoảng 20 lần so với phương thức sử dụng regex !!!!
Clint Eastwood

2
Tôi chắc chắn đồng ý với Clint về điều này, đây sẽ là câu trả lời được chấp nhận. Hiệu suất là một điều nhưng sử dụng một thư viện thử nghiệm trận chiến chắc chắn là một thực hành lập trình tốt.
Julien

1
Hoặc bằng cách sử dụng phương thức String.join () của Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("exampleTest"));
dk7

Làm thế nào bạn có thể không đồng ý với Clint Eastwood? :)
daneejela

19

Giải pháp gọn gàng và ngắn gọn hơn:

StringUtils.capitalize(StringUtils.join(StringUtils.splitByCharacterTypeCamelCase("yourCamelCaseText"), StringUtils.SPACE)); // Your Camel Case Text

Như thể hiện trong assertcâu hỏi đầu tiên , viết hoa là không mong muốn.
slartidan

Cảm ơn đã bắt lỗi, sẽ cập nhật câu trả lời.
Sahil Chhabra

10

Nếu bạn không thích regex "phức tạp" và hoàn toàn không bận tâm về hiệu quả, thì tôi đã sử dụng ví dụ này để đạt được hiệu quả tương tự trong ba giai đoạn.

String name = 
    camelName.replaceAll("([A-Z][a-z]+)", " $1") // Words beginning with UC
             .replaceAll("([A-Z][A-Z]+)", " $1") // "Words" of only UC
             .replaceAll("([^A-Za-z ]+)", " $1") // "Words" of non-letters
             .trim();

Nó vượt qua tất cả các trường hợp thử nghiệm ở trên, bao gồm cả những trường hợp có chữ số.

Như tôi nói, điều này không tốt bằng việc sử dụng một biểu thức chính quy trong một số ví dụ khác ở đây - nhưng ai đó có thể thấy nó hữu ích.


1
Cảm ơn, điều này thật tuyệt Tôi đã tạo một phiên bản JavaScript .
Ông Polywhirl

Đây cũng là cách duy nhất để bạn làm việc với thư viện / công cụ regex không hỗ trợ lookbehind / lookforward (như gói regrec của golang). Công việc tốt đẹp.
mdwhatcott

6

Bạn có thể sử dụng org.modeshape.common.text.Inflector .

Đặc biệt:

String humanize(String lowerCaseAndUnderscoredWords,
    String... removableTokens) 

Viết hoa từ đầu tiên và biến dấu gạch dưới thành khoảng trắng và dải "_id" và bất kỳ mã thông báo có thể tháo rời nào được cung cấp.

Cổ vật của Maven là: org.modeshape: modeshape-common: 2.3.0.Final

trên kho lưu trữ JBoss: https://reposective.jboss.org/nexus/content/repos khu /

Đây là tệp JAR: https://reposective.jboss.org/nexus/content/repos khu /ở / nah


1

Regex sau đây có thể được sử dụng để xác định chữ hoa trong các từ:

"((?<=[a-z0-9])[A-Z]|(?<=[a-zA-Z])[0-9]]|(?<=[A-Z])[A-Z](?=[a-z]))"

Nó khớp với mọi chữ in hoa, đó là ether sau một chữ cái không viết hoa hoặc chữ số hoặc theo sau là chữ cái viết thường và mỗi chữ số sau một chữ cái.

Cách chèn một khoảng trắng trước chúng vượt quá các kỹ năng Java của tôi =)

Đã chỉnh sửa để bao gồm trường hợp chữ số và trường hợp Trình tải PDF.


@Yaneeve: Tôi chỉ thấy các chữ số ... điều này có thể làm cho mọi thứ phức tạp hơn. Có lẽ một Regex khác để bắt những người đó sẽ là cách dễ dàng.
Jens

@Jens: Liệu nó có phù hợp với Ltrong PDFLoader?
Jørn Schou-Rode

làm thế nào về (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve

3
Bây giờ, tôi vô cùng ngưỡng mộ kỹ năng Regex của bạn, nhưng tôi ghét phải duy trì điều đó.
Chris Knight

1
@Chris: Đúng, đúng vậy. Regex là một ngôn ngữ chỉ viết. =) Mặc dù biểu thức cụ thể này không khó đọc lắm, nếu bạn đọc |là "hoặc". Chà ... có lẽ là ... tôi đã thấy tệ hơn = /
Jens

1

Tôi nghĩ bạn sẽ phải lặp lại chuỗi và phát hiện các thay đổi từ chữ thường sang chữ hoa, chữ hoa sang chữ thường, chữ cái sang số, chữ số sang chữ cái. Trên mỗi thay đổi, bạn phát hiện chèn một khoảng trắng với một ngoại lệ: trên một thay đổi từ chữ hoa sang chữ thường, bạn chèn khoảng trắng một ký tự trước đó.


1

Điều này hoạt động trong .NET ... tối ưu hóa theo ý thích của bạn. Tôi đã thêm ý kiến ​​để bạn có thể hiểu những gì mỗi phần đang làm. (RegEx có thể khó hiểu)

public static string SplitCamelCase(string str)
{
    str = Regex.Replace(str, @"([A-Z])([A-Z][a-z])", "$1 $2");  // Capital followed by capital AND a lowercase.
    str = Regex.Replace(str, @"([a-z])([A-Z])", "$1 $2"); // Lowercase followed by a capital.
    str = Regex.Replace(str, @"(\D)(\d)", "$1 $2"); //Letter followed by a number.
    str = Regex.Replace(str, @"(\d)(\D)", "$1 $2"); // Number followed by letter.
    return str;
}

0

Đối với bản ghi, đây là phiên bản Scala tương thích (*):

  object Str { def unapplySeq(s: String): Option[Seq[Char]] = Some(s) }

  def splitCamelCase(str: String) =
    String.valueOf(
      (str + "A" * 2) sliding (3) flatMap {
        case Str(a, b, c) =>
          (a.isUpper, b.isUpper, c.isUpper) match {
            case (true, false, _) => " " + a
            case (false, true, true) => a + " "
            case _ => String.valueOf(a)
          }
      } toArray
    ).trim

Sau khi được biên dịch, nó có thể được sử dụng trực tiếp từ Java nếu scala-library.jar tương ứng nằm trong đường dẫn lớp.

(*) nó không thành công cho đầu vào "GL11Version"mà nó trả về "G L11 Version".


0

Tôi lấy Regex từ polygenelubricants và biến nó thành một phương thức mở rộng trên các đối tượng:

    /// <summary>
    /// Turns a given object into a sentence by:
    /// Converting the given object into a <see cref="string"/>.
    /// Adding spaces before each capital letter except for the first letter of the string representation of the given object.
    /// Makes the entire string lower case except for the first word and any acronyms.
    /// </summary>
    /// <param name="original">The object to turn into a proper sentence.</param>
    /// <returns>A string representation of the original object that reads like a real sentence.</returns>
    public static string ToProperSentence(this object original)
    {
        Regex addSpacesAtCapitalLettersRegEx = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
        string[] words = addSpacesAtCapitalLettersRegEx.Split(original.ToString());
        if (words.Length > 1)
        {
            List<string> wordsList = new List<string> { words[0] };
            wordsList.AddRange(words.Skip(1).Select(word => word.Equals(word.ToUpper()) ? word : word.ToLower()));
            words = wordsList.ToArray();
        }
        return string.Join(" ", words);
    }

Điều này biến mọi thứ thành một câu có thể đọc được. Nó thực hiện một ToString trên đối tượng được thông qua. Sau đó, nó sử dụng Regex được đưa ra bởi polygenelubricants để phân tách chuỗi. Sau đó, nó ToLowers từng từ trừ từ đầu tiên và bất kỳ từ viết tắt nào. Nghĩ rằng nó có thể hữu ích cho một ai đó ngoài kia.


-2

Tôi không phải là ninja regex, vì vậy tôi sẽ lặp lại chuỗi, giữ các chỉ mục của vị trí hiện tại được kiểm tra & vị trí trước đó. Nếu vị trí hiện tại là chữ in hoa, tôi sẽ chèn một khoảng trắng sau vị trí trước đó và tăng từng chỉ mục.


2
Psssh! Đâu là niềm vui trong đó?
vbullinger

-3

http://code.google.com.vn/p/inflection-js/

Bạn có thể xâu chuỗi các phương thức String.underscore (). Humanize () để lấy chuỗi CamelCase và chuyển đổi nó thành chuỗi có thể đọc được.


2
Inflection-js là trong Javascript. Tôi đang tìm kiếm một giải pháp Java.
Frederik
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.