Wie konvertiere ich CamelCase in Java in lesbare Namen?


157

Ich möchte eine Methode schreiben, die CamelCase in einen für Menschen lesbaren Namen konvertiert.

Hier ist der Testfall:

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
Zunächst müssen Sie die Regeln für die Konvertierung angeben. Wie wird PDFLoaderzum Beispiel PDF Loader?
Jørn Schou-Rode

2
Ich nenne dieses Format "PascalCase". In "camelCase" sollte der erste Buchstabe klein geschrieben sein. Zumindest für Entwickler. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Antworten:


336

Dies funktioniert mit Ihren Testfällen:

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

Hier ist ein Testgeschirr:

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

Es verwendet einen übereinstimmenden regulären Ausdruck mit der Länge Null mit Lookbehind und Lookforward, um herauszufinden, wo Leerzeichen eingefügt werden sollen. Grundsätzlich gibt es 3 Muster, die ich String.formatzusammenstelle, um sie besser lesbar zu machen.

Die drei Muster sind:

UC hinter mir, UC gefolgt von LC vor mir

  XMLParser   AString    PDFLoader
    /\        /\           /\

Nicht-UC hinter mir, UC vor mir

 MyClass   99Bottles
  /\        /\

Brief hinter mir, kein Brief vor mir

 GL11    May5    BFG9000
  /\       /\      /\

Verweise

Verwandte Fragen

Verwenden von übereinstimmenden Lookarounds mit Nulllänge zum Teilen:


1
Das Konzept funktioniert auch in C # (mit denselben regulären Ausdrücken, aber natürlich mit einem etwas anderen Framework für reguläre Ausdrücke). Ausgezeichnete Arbeit. Vielen Dank!
Gmm

Scheint bei Python nicht für mich zu funktionieren, könnte daran liegen, dass die Regex-Engine nicht dieselbe ist. Ich fürchte, ich muss versuchen, etwas weniger Elegantes zu tun. :)
MarioVilas

2
Könnte jemand bitte erklären, was% s |% s |% s in Bezug auf die Testfälle und auch allgemein bedeuten?
Ari53nN3o

1
@ Ari53nN3o: Die " %s" sind Platzhalter für die String.format(String format, args...)Argumente. Sie können auch per Index anrufen:String.format("%$1s|%$2s|%$3s", ...
Mr. Polywhirl

Wie funktioniert das in c #? Es gibt auch keine relaceAllIch möchte Split hinzufügen, wenn String " ." enthält.
Sarojanand

119

Sie können es mit tun org.apache.commons.lang.StringUtils

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

9
Diese Lösung ist viel besser als die am besten bewertete, weil: a) das Rad nicht neu erfunden wird: commons-lang ist ein De-facto-Standard und funktioniert einwandfrei, wobei der Schwerpunkt auf der Leistung liegt. b) Wenn die Konvertierung häufig durchgeführt wird, ist diese Methode viel schneller als die auf Regex basierende: Dies ist mein Maßstab für die 100.000-malige Ausführung der oben genannten Tests: Die auf Regex basierende Methode dauerte 4820 Millisekunden ///// ///// commons-lang-basierte Methode dauerte 232 Millisekunden `` `das ist ungefähr 20 mal schneller als die, die Regex verwendet !!!!
Clint Eastwood

2
Ich stimme Clint in diesem Punkt definitiv zu, dies sollte die akzeptierte Antwort sein. Leistung ist eine Sache, aber die Verwendung einer kampferprobten Bibliothek ist definitiv eine gute Programmierpraxis.
Julien

1
Oder verwenden Sie die String.join () -Methode von Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

Wie konntest du Clint Eastwood nicht zustimmen? :)
Daneejela

19

Die saubere und kürzere Lösung:

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

Wie im ersten assertTeil der Frage gezeigt, ist eine Großschreibung nicht erwünscht.
Slartidan

Vielen Dank, dass Sie den Fehler behoben haben. Die Antwort wird aktualisiert.
Sahil Chhabra

10

Wenn Sie "komplizierte" Regexs nicht mögen und sich überhaupt nicht um Effizienz kümmern, habe ich dieses Beispiel verwendet, um den gleichen Effekt in drei Stufen zu erzielen.

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

Es besteht alle oben genannten Testfälle, einschließlich der mit Ziffern.

Wie gesagt, dies ist nicht so gut wie die Verwendung des einen regulären Ausdrucks in einigen anderen Beispielen hier - aber jemand könnte es nützlich finden.


1
Danke, das war großartig. Ich habe eine JavaScript-Version erstellt .
Herr Polywhirl

Dies ist auch der einzige Weg, wenn Sie mit einer Regex-Bibliothek / einem Regex-Tool arbeiten, das / das Lookbehind / Lookforward nicht unterstützt (wie das Regexp-Paket von golang). Gute Arbeit.
Mdwhatcott

6

Sie können org.modeshape.common.text.Inflector verwenden .

Speziell:

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

Großschreibt das erste Wort und wandelt Unterstriche in Leerzeichen und Streifen nach "_id" und alle mitgelieferten entfernbaren Token um.

Maven-Artefakt ist: org.modeshape: modeshape-common: 2.3.0.Final

im JBoss-Repository: https://repository.jboss.org/nexus/content/repositories/releases

Hier ist die JAR-Datei: https://repository.jboss.org/nexus/content/repositories/releases/org/modeshape/modeshape-common/2.3.0.Final/modeshape-common-2.3.0.Final.jar


1

Der folgende Regex kann verwendet werden, um die Großbuchstaben in Wörtern zu identifizieren:

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

Es entspricht jedem Großbuchstaben, dh Äther nach einem Nicht-Großbuchstaben oder einer Ziffer oder gefolgt von einem Kleinbuchstaben und jeder Ziffer nach einem Buchstaben.

Das Einfügen eines Leerzeichens vor ihnen liegt außerhalb meiner Java-Kenntnisse =)

Bearbeitet, um den Ziffern- und den PDF Loader-Fall einzuschließen.


@ Yaneeve: Ich habe gerade die Ziffern gesehen ... das könnte die Sache komplizierter machen. Wahrscheinlich wäre ein weiterer Regex, um diese zu fangen, der einfache Weg.
Jens

@Jens: Wird es zum Lin passen PDFLoader?
Jørn Schou-Rode

wie wäre es mit (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve

3
Nun, ich bewundere Ihre Regex-Fähigkeiten sehr, aber ich würde es hassen, das beibehalten zu müssen.
Chris Knight

1
@ Chris: Ja, das ist wahr. Regex ist eher eine reine Schreibsprache. =) Obwohl dieser spezielle Ausdruck nicht sehr schwer zu lesen ist, wenn Sie |als "oder" lesen . Nun ... vielleicht ist es ... ich habe Schlimmeres gesehen = /
Jens

1

Ich denke, Sie müssen über die Zeichenfolge iterieren und Änderungen von Kleinbuchstaben in Großbuchstaben, Großbuchstaben in Kleinbuchstaben, alphabetisch in numerisch, numerisch in alphabetisch erkennen. Bei jeder Änderung, die Sie feststellen, fügen Sie ein Leerzeichen ein, mit einer Ausnahme: Bei einem Wechsel von Groß- zu Kleinbuchstaben fügen Sie das Leerzeichen ein Zeichen zuvor ein.


1

Dies funktioniert in .NET ... nach Ihren Wünschen optimieren. Ich habe Kommentare hinzugefügt, damit Sie verstehen, was jedes Stück tut. (RegEx kann schwer zu verstehen sein)

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

Für die Aufzeichnung ist hier eine fast (*) kompatible Scala-Version:

  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

Einmal kompiliert, kann es direkt aus Java verwendet werden, wenn sich die entsprechende scala-library.jar im Klassenpfad befindet.

(*) Es schlägt für die Eingabe fehl, "GL11Version"für die es zurückgibt "G L11 Version".


0

Ich habe den Regex aus Polygenschmierstoffen genommen und daraus eine Erweiterungsmethode für Objekte gemacht:

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

Dies macht alles zu einem lesbaren Satz. Es führt einen ToString für das übergebene Objekt durch. Dann wird der von Polygenschmierstoffen gegebene Regex verwendet, um die Saite zu teilen. Dann verringert es jedes Wort mit Ausnahme des ersten Wortes und aller Akronyme. Ich dachte, es könnte für jemanden da draußen nützlich sein.


-2

Ich bin kein Regex-Ninja, daher würde ich über die Zeichenfolge iterieren und die Indizes der aktuellen Position und der vorherigen Position beibehalten. Wenn die aktuelle Position ein Großbuchstabe ist, würde ich nach der vorherigen Position ein Leerzeichen einfügen und jeden Index erhöhen.


2
Psssh! Wo ist der Spaß dabei?
Vbullinger

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.