Как я могу преобразовать 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"));
}
Frederik
источник
5
Сначала вам нужно будет указать правила конвертации. Например, как же PDFLoaderстать PDF Loader?
Йорн Шоу-Роде
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) + "]");
    }

Он использует регулярное выражение сопоставления нулевой длины с lookbehind и lookforward, чтобы найти место для вставки пробелов. В основном есть 3 образца, и я использую String.formatих, чтобы сделать их более читабельными.

Три модели:

UC позади меня, UC сопровождается LC передо мной

  XMLParser   AString    PDFLoader
    /\        /\           /\

не UC позади меня, UC передо мной

 MyClass   99Bottles
  /\        /\

Письмо позади меня, не письмо передо мной

 GL11    May5    BFG9000
  /\       /\      /\

Ссылки

Смежные вопросы

Использование сопоставления нулевой длины для разделения:

polygenelubricants
источник
1
Эта концепция работает и в C # (с теми же регулярными выражениями, но, конечно, с немного другой структурой регулярных выражений). Превосходная работа. Спасибо!
Гмм
Похоже, у меня не работает на Python, это может быть потому, что движок регулярных выражений не то же самое. Боюсь, мне придется попробовать заняться чем-то менее элегантным. :)
MarioVilas
2
Может кто-нибудь объяснить, что означает% s |% s |% s по отношению к тестам и вообще?
Ari53nN3o
1
@ Ari53nN3o: В « %s» «s заполнители для String.format(String format, args...)аргументов. Вы также можете позвонить по индексу:String.format("%$1s|%$2s|%$3s", ...
Мистер Polywhirl
Как это будет работать в C #? Нет relaceAllтакже, я хочу добавить разделение, если строка имеет " ." в этом.
Сароян и
119

Вы можете сделать это с помощью org.apache.commons.lang.StringUtils

StringUtils.join(
     StringUtils.splitByCharacterTypeCamelCase("ExampleTest"),
     ' '
);
Ральф
источник
9
Это решение намного лучше, чем наиболее одобренное, потому что: a) оно не изобретает колесо заново: 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вопросе, использование заглавных букв нежелательно.
Слартидан
Спасибо, что поймали ошибку, обновите ответ.
Сахил Чхабра
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();

Он проходит все тестовые случаи выше, в том числе с цифрами.

Как я уже сказал, это не так хорошо, как использовать одно регулярное выражение в некоторых других примерах здесь - но кто-то может найти его полезным.

jlb83
источник
1
Спасибо, это было здорово. Я сделал версию JavaScript .
Мистер Поливирл,
Это также единственный способ, если вы работаете с библиотекой / инструментом regex, которые не поддерживают lookbehind / lookforward (например, пакет golang regexp). Хорошая работа.
mdwhatcott
6

Вы можете использовать org.modeshape.common.text.Inflector .

В частности:

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

Первое слово пишется с заглавной буквы и превращает подчеркивания в пробелы и полосы, заканчивающиеся "_id" и любыми поставляемыми съемными токенами.

Артефакт Maven: org.modeshape: commonhape-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

Следующее регулярное выражение может быть использовано для определения заглавных букв в словах:

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

Он соответствует каждой заглавной букве, то есть эфиру после не заглавной буквы или цифры или сопровождается строчной буквой и каждой цифрой после буквы.

Как вставить пробел перед ними выходит за рамки моих навыков Java =)

Отредактировано, чтобы включить регистр цифр и регистр PDF Loader.

Jens
источник
@Yaneeve: Я только что видел цифры ... это может усложнить ситуацию. Вероятно, еще одним регулярным выражением, чтобы поймать их, будет легкий путь.
Йенс
@Jens: Будет ли это соответствовать Lв PDFLoader?
Йорн Шоу-Роде
как насчет (? <= [a-z0-9]) [A-Z0-9]?
Yaneeve
3
Я восхищаюсь твоими навыками в Regex, но я не хотел бы поддерживать это.
Крис Найт
1
@ Крис: Да, это правда. 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;
}
Xinbi
источник
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".

gerferra
источник
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, заданный polygenelubricants для разделения строки. Тогда это ToLowers каждое слово за исключением первого слова и любых сокращений. Думал, что это может быть полезно для кого-то там.

vbullinger
источник
-2

Я не ниндзя регулярных выражений, поэтому я перебрал бы строку, сохраняя индексы текущей позиции и предыдущей позиции. Если текущая позиция является заглавной буквой, я вставляю пробел после предыдущей позиции и увеличиваю каждый индекс.

Joel
источник
2
Psssh! Где в этом веселье?
vbullinger
-3

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

Вы можете связать методы String.underscore (). Humanize (), чтобы взять строку CamelCase и преобразовать ее в удобочитаемую строку.

BeesonBison
источник
2
Inflection-JS находится в Javascript. Я ищу решение Java.
Фредерик