Jak przekonwertować CamelCase na nazwy czytelne dla człowieka w Javie?


157

Chciałbym napisać metodę, która konwertuje CamelCase na nazwę czytelną dla człowieka.

Oto przypadek testowy:

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
Najpierw musisz określić zasady konwersji. Na przykład, jak się PDFLoaderstaje PDF Loader?
Jørn Schou-Rode

2
Nazywam ten format „PascalCase”. W „camelCase” pierwsza litera powinna być mała. Przynajmniej jeśli chodzi o deweloperów. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Odpowiedzi:


337

Działa to z twoimi przypadkami testowymi:

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

Oto uprząż testowa:

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

Używa dopasowania wyrażenia regularnego o zerowej długości z lookbehind i lookforward, aby znaleźć miejsce na wstawienie spacji. Zasadniczo istnieją 3 wzory i używam String.formatich do połączenia, aby było bardziej czytelne.

Te trzy wzory to:

UC za mną, UC i LC przed mną

  XMLParser   AString    PDFLoader
    /\        /\           /\

non-UC za mną, UC przede mną

 MyClass   99Bottles
  /\        /\

List za mną, nie list przede mną

 GL11    May5    BFG9000
  /\       /\      /\

Bibliografia

Powiązane pytania

Używanie lookarounds dopasowania o zerowej długości do podziału:


1
Koncepcja działa również w C # (z tymi samymi wyrażeniami regularnymi, ale oczywiście z nieco inną strukturą wyrażeń regularnych). Wspaniała robota. Dzięki!
gmm,

Wydaje się, że nie działa dla mnie w Pythonie, może to być spowodowane tym, że silnik regex nie jest taki sam. Obawiam się, że będę musiał zrobić coś mniej eleganckiego. :)
MarioVilas

2
Czy mógłby ktoś wyjaśnić, co% s |% s |% s oznacza w odniesieniu do przypadków testowych, a także ogólnie?
Ari53nN3o

1
@ Ari53nN3o: %s to symbole zastępcze String.format(String format, args...)argumentów. Możesz również zadzwonić według indeksu:String.format("%$1s|%$2s|%$3s", ...
Pan Polywhirl

Jak to będzie działać w C #? Nie ma relaceAllteż, chcę dodać podział, jeśli łańcuch zawiera " .".
sarojanand

119

Możesz to zrobić za pomocą org.apache.commons.lang.StringUtils

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

9
To rozwiązanie jest o wiele lepsze niż te, które cieszą się największą popularnością, ponieważ: a) Nie wymyślają na nowo koła: commons-lang jest de facto standardem i działa dobrze, bardzo koncentruje się na wydajności. b) Gdy konwersja jest wykonywana wiele razy, ta metoda jest znacznie szybsza niż metoda oparta na wyrażeniach regularnych: to jest mój punkt odniesienia dla wykonania wspomnianych testów 100000 razy: `` Metoda oparta na wyrażeniach regularnych zajęła 4820 milisekund ///// Metoda ///// oparta na języku commons-lang zajęła 232 milisekundy `` czyli około 20 razy szybciej niż ta, która używa wyrażenia regularnego !!!!
Clint Eastwood

2
Zdecydowanie zgadzam się z Clintem w tej sprawie, to powinna być akceptowana odpowiedź. Wydajność to rzecz, ale korzystanie z biblioteki przetestowanej w boju jest zdecydowanie dobrą praktyką programistyczną.
Julien

1
Lub za pomocą metody String.join () w Javie 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

jak mogłeś nie zgodzić się z Clintem Eastwoodem? :)
daneejela

19

Zgrabne i krótsze rozwiązanie:

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

Jak pokazano w pierwszym assertz pytań, kapitalizacja nie jest pożądana.
slartidan

Dzięki za złapanie błędu, zaktualizuję odpowiedź.
Sahil Chhabra

10

Jeśli nie lubisz „skomplikowanych” wyrażeń regularnych i wcale nie przejmujesz się wydajnością, skorzystałem z tego przykładu, aby osiągnąć ten sam efekt w trzech etapach.

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

Przechodzi pomyślnie wszystkie powyższe przypadki testowe, w tym te z cyframi.

Jak mówiłem, nie jest to tak dobre, jak użycie jednego wyrażenia regularnego w innych przykładach tutaj - ale ktoś może uznać to za przydatne.


1
Dzięki, to było świetne. Zrobiłem wersję JavaScript .
Pan Polywhirl

Jest to również jedyna droga, jeśli pracujesz z biblioteką / narzędziem regex, które nie obsługuje lookbehind / lookforward (jak pakiet regexp golanga). Dobra robota.
mdwhatcott

6

Możesz użyć org.modeshape.common.text.Inflector .

Konkretnie:

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

Zapisuje pierwsze słowo wielką literą i zamienia znaki podkreślenia na spacje i usuwa końcowe „_id” i wszelkie dostarczone wymienne tokeny.

Artefakt Mavena to: org.modeshape: modeshape-common: 2.3.0

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

Oto plik 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

Następujący Regex może służyć do identyfikacji wielkich liter w wyrazach wewnętrznych:

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

Dopasowuje każdą wielką literę, czyli eter po innej niż wielka literze lub cyfrze lub po której następuje mała litera i każda cyfra po literze.

Jak wstawić spację przed nimi, wykracza poza moje umiejętności Java =)

Edytowano, aby uwzględnić wielkość liter i przypadek modułu ładującego PDF.


@Yaneeve: Właśnie zobaczyłem cyfry ... to może skomplikować sprawę. Prawdopodobnie kolejny Regex, który złapie je, byłby prostym sposobem.
Jens

@Jens: będzie dopasować Lsię PDFLoader?
Jørn Schou-Rode

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

3
Ogromnie podziwiam twoje umiejętności Regex, ale nie chciałbym musieć tego utrzymywać.
Chris Knight

1
@Chris: Tak, to prawda. Regex jest bardziej językiem tylko do zapisu. =) Chociaż to konkretne wyrażenie nie jest zbyt trudne do odczytania, jeśli czytasz |jako „lub”. Cóż ... może to ... Widziałem gorzej = /
Jens

1

Myślę, że będziesz musiał iterować po ciągu i wykrywać zmiany z małych na wielkie, z wielkich na małe, alfabetyczne na numeryczne, numeryczne na alfabetyczne. Przy każdej wykrytej zmianie wstaw spację z jednym wyjątkiem: przy zmianie z wielkich na małe litery wstawiasz spację o jeden znak przed.


1

Działa to w .NET ... optymalizuj według własnych upodobań. Dodałem komentarze, abyś mógł zrozumieć, co robi każdy kawałek. (RegEx może być trudny do zrozumienia)

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

Dla przypomnienia, oto prawie (*) kompatybilna wersja Scala:

  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

Po skompilowaniu może być używany bezpośrednio z Javy, jeśli odpowiednia biblioteka scala.jar znajduje się w ścieżce klas.

(*) kończy się niepowodzeniem dla danych wejściowych, "GL11Version"dla których zwraca "G L11 Version".


0

Wziąłem Regex z polygenelubricants i przekształciłem go w metodę rozszerzającą na obiektach:

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

To zmienia wszystko w czytelne zdanie. Wykonuje ToString na przekazanym obiekcie. Następnie używa Regex podanego przez polygenelubricants, aby podzielić ciąg. Następnie To Obniża każde słowo z wyjątkiem pierwszego słowa i wszelkich akronimów. Pomyślałem, że to może być przydatne dla kogoś tam.


-2

Nie jestem ninja wyrażeń regularnych, więc iterowałem po ciągu, zachowując indeksy bieżącej sprawdzanej pozycji i poprzedniej pozycji. Jeśli bieżąca pozycja jest wielką literą, wstawiłbym spację po poprzedniej pozycji i zwiększyłby każdy indeks.


2
Psssh! Gdzie w tym zabawa?
vbullinger

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.