ฉันจะแปลง CamelCase เป็นชื่อที่มนุษย์สามารถอ่านได้ใน Java ได้อย่างไร?


157

ฉันต้องการเขียนวิธีการที่แปลง CamelCase เป็นชื่อที่มนุษย์อ่านได้

นี่คือกรณีทดสอบ:

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
ก่อนอื่นคุณจะต้องระบุกฎของการแปลง เช่นจะPDFLoaderกลายเป็นPDF Loaderอย่างไร
Jørn Schou-Rode

2
ฉันเรียกรูปแบบนั้นว่า "PascalCase" ใน "camelCase" ตัวอักษรตัวแรกควรเป็นตัวพิมพ์เล็ก อย่างน้อยที่สุดเท่าที่นักพัฒนามีความกังวล msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

คำตอบ:


337

สิ่งนี้ใช้ได้กับ testcases ของคุณ:

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

นี่คือสายรัดทดสอบ:

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

มันใช้ regex จับคู่ความยาวเป็นศูนย์กับ lookbehind และ lookforward เพื่อค้นหาตำแหน่งที่จะแทรกช่องว่าง โดยทั่วไปมี 3 รูปแบบและฉันใช้String.formatเพื่อรวมเข้าด้วยกันเพื่อให้อ่านง่ายขึ้น

สามรูปแบบคือ:

UC อยู่ข้างหลังฉัน UC ตามด้วย LC ข้างหน้าฉัน

  XMLParser   AString    PDFLoader
    /\        /\           /\

ไม่ใช่ฉันข้างหลังฉัน UC อยู่ข้างหน้าฉัน

 MyClass   99Bottles
  /\        /\

จดหมายอยู่ข้างหลังผม, ที่ไม่ใช่ตัวอักษรในด้านหน้าของฉัน

 GL11    May5    BFG9000
  /\       /\      /\

อ้างอิง

คำถามที่เกี่ยวข้อง

การใช้การจับคู่การจับคู่ที่ไม่มีความยาวศูนย์เพื่อแยก


1
แนวคิดทำงานใน C # เช่นกัน (พร้อมนิพจน์ทั่วไปเหมือนกัน แต่เป็นกรอบการแสดงออกปกติที่แตกต่างกันเล็กน้อยแน่นอน) ทำงานดีมาก ขอบคุณ!
gmm

ดูเหมือนจะใช้งานไม่ได้กับฉันใน Python อาจเป็นเพราะเอ็นจิ้น regex ไม่เหมือนกัน ฉันจะต้องลองทำสิ่งที่หรูหราน้อยกว่าฉันกลัว :)
MarioVilas

2
ใครช่วยอธิบายได้ว่า% s |% s |% s หมายถึงอะไรเกี่ยวกับการทดสอบและโดยทั่วไป
Ari53nN3o

1
@ Ari53nN3o: ตัว" %s"เป็นตัวยึดตำแหน่งสำหรับString.format(String format, args...)อาร์กิวเมนต์ นอกจากนี้คุณยังสามารถเรียกโดยดัชนี:String.format("%$1s|%$2s|%$3s", ...
นาย Polywhirl

สิ่งนี้จะทำงานใน c # ได้อย่างไร ยังไม่มีrelaceAllฉันต้องการเพิ่มแยกถ้าสตริงมี " ." ในนั้น
sarojanand

119

คุณสามารถทำได้โดยใช้ org.apache.commons.lang.StringUtils

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

9
การแก้ปัญหานี้จะดีกว่าหนึ่ง upvoted ที่สุดเพราะก) มันไม่ได้อีกคิดค้นล้อ: คอมมอน-lang เป็นมาตรฐานโดยพฤตินัยและทำงานดีมากมุ่งเน้นไปที่ผลการดำเนินงาน ข) เมื่อแปลงเสร็จแล้วหลายครั้งวิธีนี้คือเร็วกว่า regex ตามหนึ่ง: นี่คือมาตรฐานของฉันสำหรับการดำเนินการทดสอบดังกล่าวข้างต้น 100,000 ครั้ง: `` `วิธี regex ตามเอา 4820 มิลลิวินาที ///// ///// วิธีที่ใช้คอมมอนส์ใช้เวลา 232 มิลลิวินาที `` `นั่นเร็วกว่าวิธีที่ใช้ regex ประมาณ 20 เท่า !!!!
Clint Eastwood

2
ฉันเห็นด้วยกับคลินต์กับอันนี้น่าจะเป็นคำตอบที่ยอมรับได้ ประสิทธิภาพเป็นสิ่งหนึ่ง แต่การใช้ไลบรารี่ที่ทดสอบการต่อสู้นั้นเป็นวิธีการเขียนโปรแกรมที่ดี
Julien

1
หรือโดยใช้วิธีการ String.join () ของ Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

คุณไม่เห็นด้วยกับ Clint Eastwood ได้อย่างไร :)
daneejela

19

ทางออกที่เรียบร้อยและสั้นกว่า:

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

ดังที่แสดงในassertคำถามแรกไม่ต้องการการใช้อักษรตัวพิมพ์ใหญ่
slartidan

ขอบคุณที่ติดตามข้อผิดพลาดจะอัปเดตคำตอบ
Sahil Chhabra

10

ถ้าคุณทำไม่ได้เช่น "ซับซ้อน" ของ regex และไม่ได้ที่ทุกคนใส่ใจเกี่ยวกับประสิทธิภาพแล้วผมเคยใช้ตัวอย่างนี้เพื่อให้บรรลุผลเช่นเดียวกันในสามขั้นตอน

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

มันผ่านทุกกรณีทดสอบข้างต้นรวมถึงผู้ที่มีตัวเลข

อย่างที่ฉันบอกว่านี่ไม่ดีเท่ากับการใช้นิพจน์ทั่วไปในตัวอย่างอื่นที่นี่ - แต่บางคนอาจพบว่ามันมีประโยชน์


1
ขอบคุณนี่เยี่ยมมาก ฉันทำรุ่น JavaScript
นาย Polywhirl

นี่เป็นวิธีเดียวที่จะไปได้ถ้าคุณทำงานกับไลบรารี / เครื่องมือ regex ที่ไม่สนับสนุน lookbehind / lookforward (เช่นแพ็คเกจ regexp ของ golang) ทำได้ดีมาก
mdwhatcott

6

คุณสามารถใช้org.modeshape.common.text.Inflector

โดยเฉพาะ:

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

Capitalizes คำแรกและขีดลงในช่องว่างและแถบผลัดต่อท้าย "_id" และราชสกุลที่ถอดออกใด ๆ ที่จัดทำ

สิ่งประดิษฐ์ Maven คือ: org.modeshape: modeshape-common: 2.3.0.Final

พื้นที่เก็บข้อมูลบน JBoss: https://repository.jboss.org/nexus/content/repositories/releases

นี่คือไฟล์ JAR นี้: https://repository.jboss.org/nexus/content/repositories/releases/org/modeshape/modeshape-common/2.3.0.Final/modeshape-common-2.3.0.Final.jar


1

Regex ต่อไปนี้สามารถใช้ระบุเมืองหลวงในคำศัพท์:

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

มันตรงกับตัวพิมพ์ใหญ่ทุกตัวนั่นคืออีเธอร์หลังจากตัวอักษรหรือตัวเลขที่ไม่ใช่ตัวพิมพ์ใหญ่หรือตามด้วยตัวอักษรตัวพิมพ์เล็กและทุกตัวอักษรหลังตัวอักษร

วิธีแทรกช่องว่างก่อนหน้านั้นอยู่นอกเหนือทักษะ Java ของฉัน =)

แก้ไขเพื่อรวมตัวพิมพ์ใหญ่และตัวพิมพ์ใหญ่ PDF Loader


@ Yaneeve: ฉันเพิ่งเห็นตัวเลข ... นี่อาจทำให้สิ่งที่ซับซ้อนมากขึ้น อาจเป็นอีก Regex ที่จะจับเหล่านั้นเป็นวิธีที่ง่าย
เจนส์

@Jens: มันจะตรงกับLในPDFLoaderหรือไม่
Jørn Schou-Rode

วิธีการเกี่ยวกับ (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve

3
ตอนนี้ฉันชื่นชมทักษะ Regex ของคุณอย่างมากมาย แต่ฉันเกลียดที่จะต้องรักษามันไว้
Chris Knight

1
@Chris: ใช่แล้วมันเป็นเรื่องจริง Regex เป็นภาษาเขียนอย่างเดียวมากกว่า =) แม้ว่านิพจน์นี้จะอ่านไม่ยากนักหากคุณอ่าน|ว่า "หรือ" ... บางทีมันอาจเป็น ... ฉันเคยเห็นแย่ลง = /
เจนส์

1

ฉันคิดว่าคุณจะต้องวนซ้ำสตริงและตรวจสอบการเปลี่ยนแปลงจากตัวพิมพ์เล็กเป็นตัวพิมพ์ใหญ่, ตัวพิมพ์ใหญ่เป็นตัวพิมพ์เล็ก, ตัวอักษรเป็นตัวเลข, ตัวเลขและตัวอักษร ในการเปลี่ยนแปลงทุกครั้งที่คุณตรวจพบการแทรกช่องว่างด้วยข้อยกเว้นหนึ่งข้อ: ในการเปลี่ยนแปลงจากตัวอักษรใหญ่ถึงตัวพิมพ์เล็กคุณจะต้องเว้นวรรคหนึ่งตัวอักษรก่อนหน้า


1

งานนี้ใน .NET ... เพิ่มประสิทธิภาพเพื่อความชอบของคุณ ฉันเพิ่มความคิดเห็นเพื่อให้คุณสามารถเข้าใจสิ่งที่แต่ละชิ้นกำลังทำอยู่ (RegEx อาจเข้าใจยาก)

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

สำหรับบันทึกนี่เป็นเวอร์ชั่นสกาล่าที่เข้ากันได้เกือบ (*):

  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

เมื่อรวบรวมมันสามารถนำมาใช้โดยตรงจาก Java ถ้าสอดคล้องสกาล่า-library.jar อยู่ในคลาสพา ธ

(*) ก็ล้มเหลวสำหรับการป้อนข้อมูลที่จะส่งกลับ"GL11Version""G L11 Version"


0

ฉันใช้ Regex จาก polygenelubricants และเปลี่ยนเป็นวิธีการต่อกับวัตถุ:

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

ทำให้ทุกอย่างกลายเป็นประโยคที่อ่านได้ มันเป็น ToString บนวัตถุที่ส่งผ่าน จากนั้นจะใช้ Regex ที่กำหนดโดย polygenelubricants เพื่อแยกสตริง จากนั้นก็จะ ToLowers แต่ละคำยกเว้นคำแรกและตัวย่อใด ๆ คิดว่าอาจเป็นประโยชน์สำหรับใครบางคนที่นั่น


-2

ฉันไม่ใช่นินจา regex ดังนั้นฉันจึงวนซ้ำสตริงรักษาดัชนีของตำแหน่งปัจจุบันที่ถูกตรวจสอบและตำแหน่งก่อนหน้า หากตำแหน่งปัจจุบันเป็นตัวพิมพ์ใหญ่ฉันจะแทรกช่องว่างหลังตำแหน่งก่อนหน้าและเพิ่มแต่ละดัชนี


2
Psssh! ความสนุกในนั้นอยู่ที่ไหน
vbullinger

-3

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

คุณสามารถโยงเมธอด String.underscore (). humanize ()เพื่อใช้สตริง CamelCase และแปลงเป็นสตริงที่มนุษย์สามารถอ่านได้


2
inflection-js อยู่ใน Javascript ฉันกำลังมองหาโซลูชัน Java
Frederik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.