Come posso convertire CamelCase in nomi leggibili dall'uomo in Java?


157

Vorrei scrivere un metodo che converte CamelCase in un nome leggibile dall'uomo.

Ecco il caso di prova:

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
Innanzitutto, dovrai specificare le regole della conversione. Ad esempio, come PDFLoaderdiventa PDF Loader?
Jørn Schou-Rode,

2
Chiamo quel formato "PascalCase". In "camelCase" la prima lettera deve essere minuscola. Almeno per quanto riguarda gli sviluppatori. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Risposte:


337

Funziona con i tuoi 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])"
      ),
      " "
   );
}

Ecco un cablaggio di prova:

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

Utilizza regex di corrispondenza a lunghezza zero con lookbehind e lookforward per trovare dove inserire spazi. Fondamentalmente ci sono 3 modelli, e li uso String.formatper metterli insieme per renderlo più leggibile.

I tre modelli sono:

UC dietro di me, UC seguito da LC davanti a me

  XMLParser   AString    PDFLoader
    /\        /\           /\

non UC alle mie spalle, UC davanti a me

 MyClass   99Bottles
  /\        /\

Lettera dietro di me, non lettera davanti a me

 GL11    May5    BFG9000
  /\       /\      /\

Riferimenti

Domande correlate

Utilizzo di lookaround di corrispondenza di lunghezza zero per suddividere:


1
Il concetto funziona anche in C # (con le stesse espressioni regolari, ma ovviamente un framework leggermente diverso per le espressioni regolari). Lavoro eccellente. Grazie!
gmm,

Non sembra funzionare per me su Python, potrebbe essere perché il motore regex non è lo stesso. Dovrò provare a fare qualcosa di meno elegante, temo. :)
MarioVilas,

2
Qualcuno potrebbe spiegare cosa significa% s |% s |% s rispetto alle prove e anche in generale?
Ari53nN3o

1
@ Ari53nN3o: I " %s" sono segnaposto per gli String.format(String format, args...)argomenti. Puoi anche chiamare per indice:String.format("%$1s|%$2s|%$3s", ...
Mr. Polywhirl,

Come funzionerà in c #? Inoltre, non relaceAllvoglio aggiungere split se la stringa contiene " ." in quello.
sarojan e

119

Puoi farlo usando org.apache.commons.lang.StringUtils

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

9
Questa soluzione è molto migliore di quella più votata perché: a) Non reinventa la ruota: commons-lang è uno standard di fatto e funziona bene, molto focalizzato sulle prestazioni. b) Quando la conversione viene eseguita molte volte questo metodo è molto più veloce di quello basato su regex: questo è il mio punto di riferimento per eseguire i test di cui sopra 100.000 volte: `` `` il metodo basato su regex ha richiesto 4820 millisecondi ///// ///// Il metodo basato su commons-lang ha richiesto 232 millisecondi `` `circa 20 volte più veloce di quello che utilizza regex !!!!
Clint Eastwood,

2
Sono assolutamente d'accordo con Clint su questo, questa dovrebbe essere la risposta accettata. Le prestazioni sono una cosa, ma usare una libreria testata in battaglia è sicuramente una buona pratica di programmazione.
Julien,

1
O usando il metodo String.join () di Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

come hai potuto non essere d'accordo con Clint Eastwood? :)
daneejela,

19

La soluzione pulita e più breve:

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

Come mostrato nella prima assertdomanda, la capitalizzazione non è desiderata.
slartidan,

Grazie per aver scoperto il bug, aggiornerà la risposta.
Sahil Chhabra,

10

Se non ti piacciono i regex "complicati" e non ti preoccupi affatto dell'efficienza, allora ho usato questo esempio per ottenere lo stesso effetto in tre fasi.

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

Passa tutti i casi di test sopra, inclusi quelli con cifre.

Come ho detto, questo non è buono come usare l'espressione regolare in alcuni altri esempi qui - ma qualcuno potrebbe trovarlo utile.


1
Grazie, è stato grandioso. Ho creato una versione JavaScript .
Mr. Polywhirl,

Questo è anche l'unico modo per andare se stai lavorando con una libreria / strumento regex che non supporta lookbehind / lookforward (come il pacchetto regexp di golang). Bel lavoro.
mdwhatcott,

6

Puoi usare org.modeshape.common.text.Inflector .

In particolare:

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

Mette in maiuscolo la prima parola e trasforma i caratteri di sottolineatura in spazi e strisce che trascinano "_id" e tutti i token rimovibili forniti.

Il manufatto di Maven è: org.modeshape: modeshape-common: 2.3.0

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

Ecco il 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


1

Il seguente Regex può essere utilizzato per identificare le maiuscole all'interno delle parole:

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

Corrisponde a ogni lettera maiuscola, ovvero etere dopo una lettera o una lettera non maiuscola o seguita da una lettera minuscola e ogni cifra dopo una lettera.

Come inserire uno spazio prima di loro va oltre le mie abilità Java =)

Modificato per includere il caso delle cifre e il caso Caricatore PDF.


@Yaneeve: ho appena visto le cifre ... questo potrebbe rendere le cose più complicate. Probabilmente un altro Regex per catturarli sarebbe il modo più semplice.
Jens

@Jens: corrisponderà a Lin PDFLoader?
Jørn Schou-Rode,

che ne dici di (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve,

3
Ora, ammiro enormemente la tua abilità nel Regex, ma odio doverlo mantenere.
Chris Knight,

1
@ Chris: Sì, è vero. Regex è più un linguaggio di sola scrittura. =) Sebbene questa particolare espressione non sia molto difficile da leggere, se leggi |come "o". Beh ... forse lo è ... ho visto di peggio = /
Jens

1

Penso che dovrai scorrere la stringa e rilevare le modifiche da minuscole a maiuscole, maiuscole a minuscole, da alfabetico a numerico, da numerico a alfabetico. Ad ogni modifica rilevata, inserisci uno spazio con un'eccezione: in una modifica da maiuscolo a minuscolo inserisci lo spazio di un carattere prima.


1

Funziona in .NET ... ottimizza a tuo piacimento. Ho aggiunto commenti in modo da poter capire cosa sta facendo ogni pezzo. (RegEx può essere difficile da capire)

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

Per la cronaca, ecco una versione Scala quasi (*) compatibile:

  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

Una volta compilato, può essere utilizzato direttamente da Java se il corrispondente scala-library.jar si trova nel percorso di classe.

(*) non riesce per l'input "GL11Version"per cui ritorna "G L11 Version".


0

Ho preso il Regex dai poligenelubrificanti e l'ho trasformato in un metodo di estensione sugli oggetti:

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

Questo trasforma tutto in una frase leggibile. Fa un ToString sull'oggetto passato. Quindi utilizza il Regex dato dai poligenelubrificanti per dividere la stringa. Quindi ToLowers ogni parola tranne la prima parola e tutti gli acronimi. Ho pensato che potesse essere utile per qualcuno là fuori.


-2

Non sono un regex ninja, quindi ripeterei la stringa, mantenendo controllati gli indici della posizione corrente e della posizione precedente. Se la posizione corrente è una lettera maiuscola, inserirei uno spazio dopo la posizione precedente e incrementerei ogni indice.


2
Psssh! Dov'è il divertimento in questo?
vbullinger,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.