Bagaimana cara mengubah CamelCase menjadi nama yang dapat dibaca manusia di Jawa?

157

Saya ingin menulis metode yang mengubah CamelCase menjadi nama yang bisa dibaca manusia.

Inilah test case-nya:

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"));
}
Frederik
sumber
5
Pertama, Anda harus menentukan aturan konversi. Misalnya, bagaimana PDFLoaderjadinya PDF Loader?
Jørn Schou-Rode
2
Saya menyebutnya format "PascalCase". Dalam "camelCase" huruf pertama harus berupa huruf kecil. Setidaknya sejauh menyangkut pengembang. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Jawaban:

337

Ini berfungsi dengan testcases Anda:

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

Berikut ini adalah test harness:

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

Ini menggunakan regex pencocokan panjang nol dengan lookbehind dan lookforward untuk menemukan tempat untuk memasukkan spasi. Pada dasarnya ada 3 pola, dan saya gunakan String.formatuntuk menyatukannya agar lebih mudah dibaca.

Tiga pola tersebut adalah:

UC di belakang saya, UC diikuti oleh LC di depan saya

  XMLParser   AString    PDFLoader
    /\        /\           /\

non-UC di belakangku, UC di depanku

 MyClass   99Bottles
  /\        /\

Surat di belakang saya, tanpa huruf di depan saya

 GL11    May5    BFG9000
  /\       /\      /\

Referensi

Pertanyaan-pertanyaan Terkait

Menggunakan lookaround pencocokan panjang nol untuk membelah:

polygenelubricants
sumber
1
Konsep ini bekerja di C # juga (dengan ekspresi reguler yang sama, tetapi kerangka kerja ekspresi reguler yang sedikit berbeda, tentu saja). Kerja bagus. Terima kasih!
gmm
Sepertinya tidak berfungsi untuk saya di Python, bisa jadi karena mesin regex tidak sama. Saya harus mencoba melakukan sesuatu yang kurang elegan, saya rasa. :)
MarioVilas
2
Bisakah seseorang tolong jelaskan apa arti% s |% s |% s sehubungan dengan testcases dan juga secara umum?
Ari53nN3o
1
@ Ari53nN3o: " %s" adalah placeholder untuk String.format(String format, args...)argumen. Anda juga dapat menelepon berdasarkan indeks:String.format("%$1s|%$2s|%$3s", ...
Tn. Polywhirl
Bagaimana ini akan bekerja di c #? Tidak ada relaceAlljuga saya ingin menambahkan split jika string memiliki " ." di dalamnya.
sarojanand
119

Anda bisa melakukannya menggunakan org.apache.commons.lang.StringUtils

StringUtils.join(
     StringUtils.splitByCharacterTypeCamelCase("ExampleTest"),
     ' '
);
Muntah
sumber
9
Solusi ini jauh lebih baik daripada yang paling banyak dipilih karena: a) Tidak menemukan kembali roda: commons-lang adalah standar de-facto dan berfungsi dengan baik, sangat fokus pada kinerja. b) Ketika konversi dilakukan berkali-kali, metode ini jauh lebih cepat daripada yang berbasis regex: ini adalah tolok ukur saya untuk melakukan tes yang disebutkan di atas 100.000 kali: `` `Metode berbasis-regex membutuhkan 4820 milidetik ///// ///// Metode berbasis-commons-lang mengambil 232 milidetik `` `itu sekitar 20 kali lebih cepat daripada yang menggunakan regex !!!!
Clint Eastwood
2
Saya pasti setuju dengan Clint yang satu ini, ini seharusnya jawaban yang diterima. Performa adalah sesuatu tetapi menggunakan perpustakaan yang diuji pertempuran jelas merupakan praktik pemrograman yang baik.
Julien
1
Atau dengan menggunakan metode String.join () Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7
bagaimana mungkin Anda tidak setuju dengan Clint Eastwood? :)
daneejela
19

Solusi yang rapi dan lebih pendek:

StringUtils.capitalize(StringUtils.join(StringUtils.splitByCharacterTypeCamelCase("yourCamelCaseText"), StringUtils.SPACE)); // Your Camel Case Text
Sahil Chhabra
sumber
Seperti ditunjukkan dalam pertanyaan pertama assert, kapitalisasi tidak diinginkan.
slartidan
Terima kasih telah menangkap bug, akan memperbarui jawabannya.
Sahil Chhabra
10

Jika Anda tidak suka regex "rumit", dan sama sekali tidak peduli tentang efisiensi, maka saya telah menggunakan contoh ini untuk mencapai efek yang sama dalam tiga tahap.

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

Itu melewati semua kasus uji di atas, termasuk yang memiliki angka.

Seperti yang saya katakan, ini tidak sebagus menggunakan satu ekspresi reguler dalam beberapa contoh lain di sini - tetapi seseorang mungkin menganggapnya berguna.

jlb83
sumber
1
Terima kasih, ini luar biasa. Saya membuat versi JavaScript .
Tn. Polywhirl
Ini juga satu-satunya cara untuk pergi jika Anda bekerja dengan perpustakaan regex / alat yang tidak mendukung lookbehind / lookforward (seperti paket regexp golang). Kerja bagus.
mdwhatcott
6

Anda dapat menggunakan org.modeshape.common.text.Inflector .

Secara khusus:

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

Mengapitalisasi kata pertama dan mengubah garis bawah menjadi spasi dan strip mengekor "_id" dan token yang dapat dilepas yang disediakan.

Artefak Maven adalah: org.modeshape: modeshape-common: 2.3.0.Final

pada repositori JBoss: https://repository.jboss.org/nexus/content/repositories/releases

Ini file JAR: https://repository.jboss.org/nexus/content/repositories/releases/org/modeshape/modeshape-common/2.3.0.Final/modeshape-common-2.3.0.Final.jar

Hendy Irawan
sumber
1

Regex berikut dapat digunakan untuk mengidentifikasi huruf kapital di dalam kata-kata:

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

Ini cocok dengan setiap huruf kapital, yaitu eter setelah huruf atau digit non-kapital atau diikuti oleh huruf kecil dan setiap digit setelah huruf.

Cara memasukkan spasi sebelum mereka di luar kemampuan Java saya =)

Diedit untuk memasukkan case digit dan case Loader PDF.

Jens
sumber
@ Yaneeve: Saya baru saja melihat angka ... ini mungkin membuat segalanya lebih rumit. Mungkin Regex lain untuk menangkap mereka akan menjadi cara yang mudah.
Jens
@Jens: Apakah cocok dengan Lin PDFLoader?
Jørn Schou-Rode
bagaimana dengan (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve
3
Sekarang, saya sangat mengagumi keahlian Regex Anda, tetapi saya benci harus mempertahankannya.
Chris Knight
1
@ Chris: Ya, itu benar. Regex lebih merupakan bahasa tulis saja. =) Meskipun ungkapan khusus ini tidak terlalu sulit untuk dibaca, jika Anda membaca |sebagai "atau". Yah ... mungkin itu ... Saya telah melihat yang lebih buruk = /
Jens
1

Saya pikir Anda harus beralih pada string dan mendeteksi perubahan dari huruf kecil ke huruf besar, huruf besar ke huruf kecil, alfabet ke numerik, numerik ke alfabet. Pada setiap perubahan Anda mendeteksi memasukkan spasi dengan satu pengecualian: pada perubahan dari huruf besar ke huruf kecil Anda memasukkan spasi satu karakter sebelumnya.

Felix
sumber
1

Ini berfungsi di .NET ... optimalkan sesuai keinginan Anda. Saya menambahkan komentar sehingga Anda dapat memahami apa yang dilakukan masing-masing bagian. (RegEx bisa sulit dimengerti)

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;
}
Xinbi
sumber
0

Sebagai catatan, ini adalah versi Scala yang hampir kompatibel:

  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

Setelah dikompilasi, ia dapat digunakan langsung dari Jawa jika scala-library.jar yang sesuai ada di classpath.

(*) gagal untuk input "GL11Version"yang dikembalikannya "G L11 Version".

gerferra
sumber
0

Saya mengambil Regex dari polygenelubricants dan mengubahnya menjadi metode ekstensi pada objek:

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

Ini mengubah segalanya menjadi kalimat yang bisa dibaca. Itu melakukan ToString pada objek yang dilewati. Kemudian ia menggunakan Regex yang diberikan oleh polygenelubricants untuk membagi string. Kemudian ToLowers setiap kata kecuali untuk kata pertama dan akronim apa pun. Kupikir itu mungkin berguna untuk seseorang di luar sana.

vbullinger
sumber
-2

Saya bukan seorang ninja regex, jadi saya akan mengulangi string, menjaga indeks posisi saat ini sedang diperiksa & posisi sebelumnya. Jika posisi saat ini adalah huruf kapital, saya akan memasukkan spasi setelah posisi sebelumnya dan menambah setiap indeks.

Joel
sumber
2
Psssh! Di mana kesenangannya?
vbullinger
-3

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

Anda bisa menggunakan metode String.underscore (). Humanize () untuk mengambil string CamelCase dan mengubahnya menjadi string yang dapat dibaca manusia.

BeesonBison
sumber
2
inflection-js adalah dalam Javascript. Saya mencari solusi Java.
Frederik