Comment convertir CamelCase en noms lisibles par l'homme en Java?


157

J'aimerais écrire une méthode qui convertit CamelCase en un nom lisible par l'homme.

Voici le cas de test:

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
Tout d'abord, vous devrez spécifier les règles de la conversion. Par exemple, comment PDFLoaderdevient-il PDF Loader?
Jørn Schou-Rode le

2
J'appelle ce format "PascalCase". Dans "camelCase", la première lettre doit être en minuscule. Au moins en ce qui concerne les développeurs. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Réponses:


337

Cela fonctionne avec vos cas de test:

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

Voici un harnais de test:

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

Il utilise une expression régulière de correspondance de longueur nulle avec lookbehind et lookforward pour trouver où insérer des espaces. Fondamentalement, il y a 3 modèles, et j'utilise String.formatpour les assembler pour le rendre plus lisible.

Les trois modèles sont:

UC derrière moi, UC suivie de LC devant moi

  XMLParser   AString    PDFLoader
    /\        /\           /\

non-UC derrière moi, UC devant moi

 MyClass   99Bottles
  /\        /\

Lettre derrière moi, non-lettre devant moi

 GL11    May5    BFG9000
  /\       /\      /\

Références

Questions connexes

Utilisation de lookarounds de correspondance de longueur nulle pour diviser:


1
Le concept fonctionne également en C # (avec les mêmes expressions régulières, mais un cadre d'expressions régulières un peu différent, bien sûr). Excellent travail. Merci!
gmm

Cela ne semble pas fonctionner pour moi sur Python, c'est peut-être parce que le moteur regex n'est pas le même. Je vais devoir essayer de faire quelque chose de moins élégant, j'en ai peur. :)
MarioVilas

2
Quelqu'un pourrait-il expliquer ce que signifie% s |% s |% s par rapport aux cas de test et aussi en général?
Ari53nN3o

1
@ Ari53nN3o: Les " %s" sont des espaces réservés pour les String.format(String format, args...)arguments. Vous pouvez également appeler par index:String.format("%$1s|%$2s|%$3s", ...
Mr. Polywhirl

Comment cela fonctionnera-t-il en c #? Il n'y a pas non relaceAllplus que je souhaite ajouter une division si la chaîne contient " .".
sarojanand

119

Vous pouvez le faire en utilisant org.apache.commons.lang.StringUtils

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

9
Cette solution est bien meilleure que la plus votée parce que: a) Elle ne réinvente pas la roue: commons-lang est une norme de facto et elle fonctionne très bien, très axée sur les performances. b) Lorsque la conversion est effectuée plusieurs fois, cette méthode est beaucoup plus rapide que celle basée sur les regex: c'est mon point de repère pour exécuter les tests mentionnés ci-dessus 100000 fois: `` La méthode basée sur les regex a pris 4820 millisecondes ///// ///// La méthode basée sur commons-lang a pris 232 millisecondes `` c'est environ 20 fois plus rapide que celle qui utilise regex !!!!
Clint Eastwood

2
Je suis définitivement d'accord avec Clint sur celui-ci, cela devrait être la réponse acceptée. Les performances sont une chose, mais utiliser une bibliothèque testée au combat est définitivement une bonne pratique de programmation.
Julien

1
Ou en utilisant la méthode String.join () de Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

comment pourriez-vous ne pas être d'accord avec Clint Eastwood? :)
daneejela

19

La solution nette et plus courte:

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

Comme le montre la première assertquestion, la capitalisation n'est pas souhaitée.
slartidan

Merci d'avoir attrapé le bogue, mettra à jour la réponse.
Sahil Chhabra

10

Si vous n'aimez pas les expressions régulières "compliquées" et que vous n'êtes pas du tout préoccupé par l'efficacité, alors j'ai utilisé cet exemple pour obtenir le même effet en trois étapes.

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

Il réussit tous les cas de test ci-dessus, y compris ceux avec des chiffres.

Comme je l'ai dit, ce n'est pas aussi bon que d'utiliser une seule expression régulière dans d'autres exemples ici - mais quelqu'un pourrait bien la trouver utile.


1
Merci, c'était super. J'ai fait une version JavaScript .
M. Polywhirl

C'est aussi la seule façon de procéder si vous travaillez avec une bibliothèque / un outil de regex qui ne prend pas en charge lookbehind / lookforward (comme le paquet regexp de golang). Bon travail.
mdwhatcott

6

Vous pouvez utiliser org.modeshape.common.text.Inflector .

Plus précisément:

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

Met en majuscule le premier mot et transforme les traits de soulignement en espaces et supprime "_id" à la fin et tous les jetons amovibles fournis.

L'artefact Maven est: org.modeshape: modeshape-common: 2.3.0.Final

sur le référentiel JBoss: https://repository.jboss.org/nexus/content/repositories/releases

Voici le fichier 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

Le Regex suivant peut être utilisé pour identifier les majuscules à l'intérieur des mots:

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

Il correspond à chaque lettre majuscule, c'est-à-dire à l'éther après une lettre ou un chiffre non majuscule ou suivi d'une lettre minuscule et de chaque chiffre après une lettre.

Comment insérer un espace devant eux dépasse mes compétences Java =)

Modifié pour inclure le cas des chiffres et le cas du chargeur PDF.


@Yaneeve: Je viens de voir les chiffres ... cela pourrait compliquer les choses. Probablement un autre Regex pour les attraper serait le moyen le plus simple.
Jens

@Jens: Sera -t- correspondre au Ldans PDFLoader?
Jørn Schou-Rode le

et (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve

3
Maintenant, j'admire énormément votre compétence Regex, mais je détesterais devoir maintenir cela.
Chris Knight

1
@Chris: Oui, c'est vrai. Regex est plus un langage en écriture seule. =) Bien que cette expression particulière ne soit pas très difficile à lire, si vous lisez |comme "ou". Eh bien ... peut-être que c'est ... j'ai vu pire = /
Jens

1

Je pense que vous devrez itérer sur la chaîne et détecter les changements de minuscules à majuscules, majuscules à minuscules, alphabétiques à numériques, numériques à alphabétiques. À chaque changement, vous détectez insérer un espace à une exception près: lors d'un changement de majuscule à minuscule, vous insérez l'espace un caractère avant.


1

Cela fonctionne dans .NET ... optimisez à votre goût J'ai ajouté des commentaires pour que vous puissiez comprendre ce que fait chaque pièce. (RegEx peut être difficile à comprendre)

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

Pour mémoire, voici une version Scala presque (*) compatible:

  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

Une fois compilé, il peut être utilisé directement depuis Java si le scala-library.jar correspondant se trouve dans le chemin de classe.

(*) il échoue pour l'entrée "GL11Version"pour laquelle il retourne "G L11 Version".


0

J'ai pris le Regex de polygenelubricants et l'ai transformé en une méthode d'extension sur les objets:

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

Cela transforme tout en une phrase lisible. Il effectue un ToString sur l'objet passé. Ensuite, il utilise l'expression régulière donnée par les lubrifiants polygéniques pour diviser la chaîne. Ensuite, il abaisse chaque mot à l'exception du premier mot et des acronymes. J'ai pensé que cela pourrait être utile pour quelqu'un là-bas.


-2

Je ne suis pas un ninja regex, donc je vais itérer sur la chaîne, en gardant les index de la position actuelle en cours de vérification et la position précédente. Si la position actuelle est une lettre majuscule, j'insérerais un espace après la position précédente et incrémenterais chaque index.


2
Psssh! Où est le plaisir là-dedans?
vbullinger

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.