Як перетворити CamelCase в імена, прочитані людиною на Java?


157

Я хотів би написати метод, який перетворює CamelCase у читабельне для людини ім'я.

Ось тестовий випадок:

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
Спочатку вам потрібно буде вказати правила перетворення. Наприклад, як це PDFLoaderстає PDF Loader?
Jørn Schou-Rode

2
Я називаю цей формат "PascalCase". У "camelCase" перша літера повинна бути малою. Принаймні, що стосується розробників. msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx
Muhd

Відповіді:


337

Це працює з вашими тестами:

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

Ось тестовий джгут:

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

Він використовує регулярний вирівнювання нульової довжини з видом наперед і з нетерпінням, щоб знайти, де вставити пробіли. В основному є 3 візерунки, і я використовую String.formatїх для складання, щоб зробити його більш зрозумілим.

Три схеми:

UC позаду мене, UC передуючи LC перед мною

  XMLParser   AString    PDFLoader
    /\        /\           /\

не-UC позаду мене, UC переді мною

 MyClass   99Bottles
  /\        /\

Лист позаду мене, без письма переді мною

 GL11    May5    BFG9000
  /\       /\      /\

Список літератури

Пов'язані питання

Використовуючи цілі пошуку, що відповідають нульовій довжині, щоб розділити:


1
Концепція працює і в C # (з тими ж регулярними виразами, але дещо іншими рамками регулярних виразів, звичайно). Відмінна робота. Дякую!
гмм

Здається, це не працює для мене на Python, це могло бути тому, що двигун регулярного вибору не той. Мені потрібно спробувати зробити щось менш елегантне. :)
MarioVilas

2
Чи не могли б хто-небудь пояснити, що означає% s |% s |% s стосовно тестів, а також взагалі?
Ari53nN3o

1
@ Ari53nN3o: " %s" є заповнювачами String.format(String format, args...)аргументів. Ви також можете зателефонувати за індексом:String.format("%$1s|%$2s|%$3s", ...
Містер Polywhirl

Як це буде працювати в c #? Там немає relaceAllі я хочу , щоб додати розкол , якщо рядок має « .» в цьому.
sarojanand

119

Ви можете це зробити, використовуючи org.apache.commons.lang.StringUtils

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

9
Це рішення набагато краще, ніж найактуальніше, оскільки: а) воно не вигадує колесо: commons-lang є де-факто стандартом і працює чудово, дуже орієнтуючись на продуктивність. б) Коли перетворення робиться багато разів, цей метод набагато швидший, ніж на основі регулярних виразів: це мій орієнтир для виконання вищезгаданих тестів в 100 000 разів: `` `метод, заснований на регулярному вираженні, зайняв 4820 мілісекунд ///// ///// метод на основі commons-lang зайняв 232 мілісекунди `` `це приблизно в 20 разів швидше, ніж той, який використовує регулярний вираз !!!!
Клінт Іствуд

2
Я точно погоджуюсь з Клінтом у цьому, це має бути прийнятою відповіддю. Продуктивність - це річ, але використання випробуваної на бій бібліотеки, безумовно, є хорошою практикою програмування.
Жульєн

1
Або використовуючи метод String.join () Java 8: String.join ("", StringUtils.splitByCharacterTypeCamelCase ("ExampleTest"));
dk7

як ти не міг погодитися з Клінтом Іствудом? :)
daneejela

19

Акуратне і коротше рішення:

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

Як показано в першому assertпитанні, використання великих літер не бажане.
slartidan

Дякуємо, що знайшли помилку, відповідь буде оновлено.
Сахіл Чхабра

10

Якщо вам не подобаються «складні» регекси, і ви зовсім не турбуєтесь про ефективність, то я використовував цей приклад для досягнення того ж ефекту в три етапи.

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

Він проходить усі тестові випадки вище, включаючи ті, що мають цифри.

Як я кажу, це не так добре, як використання одного регулярного виразу в деяких інших прикладах тут, але хтось може вважати його корисним.


1
Спасибі, це було чудово. Я зробив версію JavaScript .
Містер Polywhirl

Це також єдиний шлях, якщо ви працюєте з бібліотекою / інструментом регулярних виразів, який не підтримує погляд назад / lookforward (як пакет regexp голагу). Хороша робота.
mdwhatcott

6

Ви можете використовувати org.modeshape.common.text.Inflector .

Конкретно:

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

З великої літери пишете перше слово і перетворює підкреслення на пробіли та смужки, що вказують на "_id" та будь-які додані знімні маркери.

Артефакт Мейвена - це: org.modeshape: modehape-common: 2.3.0. final

у сховищі JBoss: https://repository.jboss.org/nexus/content/repositories/releases

Ось файл 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

Наступний Regex можна використовувати для ідентифікації великих літер у словах:

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

Він відповідає кожній великій букві, тобто ефіру після великої літери або цифри, а за ним - малої літери та кожної цифри після літери.

Як вставити пробіл перед ними виходить за рамки моїх навичок Java =)

Відредаговано, щоб включити цифровий регістр та регістр завантажувача PDF.


@Yaneeve: Я щойно бачив цифри ... це може ускладнити справи. Можливо, ще один Регекс, щоб зловити їх, був би найпростішим способом.
Єнс

@Jens: Чи буде відповідати Lв PDFLoader?
Jørn Schou-Rode

як щодо (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve

3
Зараз я дуже захоплююсь вашою майстерністю Regex, але мені б не хотілося цього підтримувати.
Кріс Найт

1
@Chris: Так, це правда. Regex - це більше мова лише для запису. =) Хоча цей конкретний вираз читати не дуже важко, якщо ви читаєте |як "чи". Ну ... можливо, це ... Я бачив гірше = /
Єнс

1

Я думаю, вам доведеться перебирати рядки та виявляти зміни від малого до верхнього, великого до малого, буквеного до цифрового, числового до алфавітного. При кожній зміні, яку ви виявляєте, вставляйте пробіл за одним винятком: при зміні від верхнього до нижнього регістру ви вставляєте пробіл одним символом раніше.


1

Це працює в .NET ... оптимізувати на свій смак. Я додав коментарі, щоб ви могли зрозуміти, що робить кожен твір. (RegEx важко зрозуміти)

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

Для запису, ось майже (*) сумісна версія 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

Після компіляції його можна використовувати безпосередньо з Java, якщо відповідна scala-library.jar знаходиться в класі.

(*) не вдається для входу, "GL11Version"за який він повертається "G L11 Version".


0

Я взяв Regex з полігеномастичних матеріалів і перетворив його на метод розширення на об'єкти:

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

Це перетворює все на читабельне речення. Він робить ToString на переданому об'єкті. Потім він використовує Regex, поданий полігеномастилами, щоб розділити рядок. Тоді цеЗамовляє кожне слово, крім першого слова та будь-яких абревіатур. Думав, що це може бути корисним для когось там.


-2

Я не регекс-ніндзя, тому я повторюю рядок, зберігаючи індекси поточної позиції, що перевіряється, і попередньої позиції. Якщо поточна позиція є великою літерою, я б вставив пробіл після попередньої позиції та збільшив кожен індекс.


2
Psssh! Де в цьому веселощі?
vbullinger

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.