Como faço para converter o CamelCase em nomes legíveis por humanos em Java?

157

Eu gostaria de escrever um método que converta o CamelCase em um nome legível por humanos.

Aqui está o caso de teste:

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
fonte
5
Primeiro, você precisará especificar as regras da conversão. Por exemplo, como se PDFLoadertorna PDF Loader?
Jørn Schou-Rode
2
Eu chamo esse formato de "PascalCase". Em "camelCase" a primeira letra deve estar em minúscula. Pelo menos no que diz respeito aos desenvolvedores. msdn.microsoft.com/pt-br/library/x2dbyw72(v=vs.71).aspx
Muhd

Respostas:

337

Isso funciona com seus casos de teste:

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

Aqui está um chicote de teste:

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

Ele usa a expressão regular de comprimento zero com lookbehind e forwardforward para descobrir onde inserir espaços. Basicamente, existem três padrões, e eu costumo String.formatreuni-los para torná-los mais legíveis.

Os três padrões são:

UC atrás de mim, UC seguido por LC na minha frente

  XMLParser   AString    PDFLoader
    /\        /\           /\

não UC atrás de mim, UC na minha frente

 MyClass   99Bottles
  /\        /\

Carta atrás de mim, sem letra na minha frente

 GL11    May5    BFG9000
  /\       /\      /\

Referências

Perguntas relacionadas

Usando lookarounds correspondentes a comprimento zero para dividir:

poligenelubricants
fonte
1
O conceito também funciona em C # (com as mesmas expressões regulares, mas um pouco diferente da estrutura de expressão regular, é claro). Excelente trabalho. Obrigado!
gmm 7/01/13
Não parece estar funcionando para mim no Python, pode ser porque o mecanismo regex não é o mesmo. Terei que tentar fazer algo menos elegante, receio. :)
MarioVilas
2
Alguém poderia explicar o que% s |% s |% s significa em relação aos casos de teste e também geralmente?
Ari53nN3o 11/11
1
@ Ari53nN3o: " %s" 's são espaços reservados para os String.format(String format, args...)argumentos. Você também pode chamar pelo índice:String.format("%$1s|%$2s|%$3s", ...
Sr. Polywhirl
Como isso funcionará em c #? Não há relaceAlltambém que eu queira adicionar divisão se a string " ." tiver isso.
23415 sarojanand
119

Você pode fazer isso usando org.apache.commons.lang.StringUtils

StringUtils.join(
     StringUtils.splitByCharacterTypeCamelCase("ExampleTest"),
     ' '
);
Ralph
fonte
9
Essa solução é muito melhor que a mais votada porque: a) Ela não reinventa a roda: o commons-lang é um padrão de fato e funciona bem, com foco no desempenho. b) Quando a conversão é feita muitas vezes, esse método é muito mais rápido que o baseado em regex: esta é minha referência para executar os testes mencionados 100.000 vezes: `` `` o método baseado em regex levou 4820 milissegundos ///// O método baseado em commons-lang levou 232 milissegundos ``, cerca de 20 vezes mais rápido que o que usa regex !!!!
Clint Eastwood
2
Definitivamente, eu concordo com Clint, essa deve ser a resposta aceita. O desempenho é uma coisa, mas usar uma biblioteca testada em batalha é definitivamente uma boa prática de programação.
Julien
1
Ou usando o método String.join () do Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7
como você não concorda com Clint Eastwood? :)
daneejela
19

A solução elegante e mais curta:

StringUtils.capitalize(StringUtils.join(StringUtils.splitByCharacterTypeCamelCase("yourCamelCaseText"), StringUtils.SPACE)); // Your Camel Case Text
Sahil Chhabra
fonte
Como mostrado na primeira assertpergunta, a capitalização não é desejada.
slartidan
Obrigado por capturar o bug, atualizará a resposta.
Sahil Chhabra
10

Se você não gosta de regex "complicado" e não se preocupa com eficiência, usei este exemplo para obter o mesmo efeito em três estágios.

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

Ele passa em todos os casos de teste acima, incluindo aqueles com dígitos.

Como eu disse, isso não é tão bom quanto usar a expressão regular em alguns outros exemplos aqui - mas alguém pode achar útil.

jlb83
fonte
1
Obrigado, isso foi ótimo. Eu fiz uma versão JavaScript .
Mr. Polywhirl
Este também é o único caminho a percorrer se você estiver trabalhando com uma biblioteca / ferramenta regex que não suporta lookbehind / lookforward (como o pacote regexp da golang). Bom trabalho.
Mdwhatcott 20/05
6

Você pode usar org.modeshape.common.text.Inflector .

Especificamente:

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

Coloca em maiúscula a primeira palavra e transforma sublinhados em espaços e faixas à direita de "_id" e em todos os tokens removíveis fornecidos.

O artefato do Maven é: org.modeshape: modeshape-common: 2.3.0.Final

no repositório JBoss: https://repository.jboss.org/nexus/content/repositories/releases

Aqui está o arquivo 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
fonte
1

O Regex a seguir pode ser usado para identificar as letras maiúsculas nas palavras:

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

Corresponde a todas as letras maiúsculas, que são éteres após uma letra ou dígito que não sejam maiúsculas ou são seguidas por uma letra minúscula e todos os dígitos após uma letra.

Como inserir um espaço antes deles está além das minhas habilidades em Java =)

Editado para incluir a caixa de dígitos e a caixa do PDF Loader.

Jens
fonte
@ Yaneeve: Acabei de ver os dígitos ... isso pode tornar as coisas mais complicadas. Provavelmente outro Regex para capturá-los seria o caminho mais fácil.
Jens
@Jens: ele irá corresponder a Lno PDFLoader?
Jørn Schou-Rode
que tal (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve
3
Agora, eu admiro muito sua habilidade no Regex, mas eu odiaria ter que manter isso.
Chris Knight
1
@ Chris: Sim, isso é verdade. Regex é mais uma linguagem somente de gravação. =) Embora essa expressão em particular não seja muito difícil de ler, se você ler |como "ou". Bem ... talvez seja ... eu já vi pior = /
Jens
1

Eu acho que você terá que percorrer a string e detectar alterações de minúsculas para maiúsculas, maiúsculas para minúsculas, alfabético para numérico, numérico para alfabético. Em todas as alterações detectadas, insira um espaço com uma exceção: em uma alteração de maiúscula para minúscula, insira o espaço um caractere antes.

Felix
fonte
1

Isso funciona no .NET ... otimizar ao seu gosto. Adicionei comentários para que você possa entender o que cada peça está fazendo. (RegEx pode ser difícil de entender)

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
fonte
0

Para o registro, aqui está uma versão Scala quase (*) compatível:

  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

Depois de compilado, ele pode ser usado diretamente do Java se o scala-library.jar correspondente estiver no caminho de classe.

(*) falha na entrada "GL11Version"para a qual retorna "G L11 Version".

gerferra
fonte
0

Peguei o Regex de poligenelubricants e o transformei em um método de extensão em objetos:

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

Isso transforma tudo em uma frase legível. Faz um ToString no objeto passado. Em seguida, ele usa o Regex fornecido por poligenelubricants para dividir a string. Em seguida, abaixa cada palavra, exceto a primeira e quaisquer acrônimos. Achei que poderia ser útil para alguém por aí.

vbullinger
fonte
-2

Eu não sou um ninja regex, então eu iria percorrer a string, mantendo os índices da posição atual sendo verificados e a posição anterior. Se a posição atual for uma letra maiúscula, insiro um espaço após a posição anterior e incremento cada índice.

Joel
fonte
2
Psssh! Onde está a diversão nisso?
precisa saber é o seguinte
-3

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

Você pode encadear os métodos String.underscore (). Humanize () para pegar uma sequência CamelCase e convertê-la em uma sequência legível por humanos.

BeesonBison
fonte
2
inflection-js está em Javascript. Estou procurando uma solução Java.
Frederik