RegEx для разделения CamelCase или TitleCase (дополнительно) - PullRequest
74 голосов
/ 29 сентября 2011

Я нашел блестящий RegEx для извлечения части выражения camelCase или TitleCase.

 (?<!^)(?=[A-Z])

Он работает как ожидалось:

  • значение -> значение
  • camelValue -> верблюд / значение
  • TitleValue -> заголовок / значение

Например, для Java:

String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
//words equals words = new String[]{"lorem","Ipsum"}

Myпроблема в том, что он не работает в некоторых случаях:

  • Случай 1: ЗНАЧЕНИЕ -> V / A / L / U / E
  • Случай 2: eclipseRCPExt -> eclipse / R/ C / P / Ext

На мой взгляд, результат будет:

  • Случай 1: ЗНАЧЕНИЕ
  • Случай 2: затмение / RCP /Ext

Другими словами, учитывая n символов в верхнем регистре:

  • , если после n символов следует символы нижнего регистра, группы должны быть: (n-1 символы)/ (n-й символ + нижние символы)
  • если n символов находятся в конце, группа должна быть: (n символов).

Любая идея о том, как улучшитьэто регулярное выражение?

Ответы [ 9 ]

101 голосов
/ 29 сентября 2011

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

public static void main(String[] args)
{
    for (String w : "camelValue".split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) {
        System.out.println(w);
    }
}   

Оно работает, заставляя отрицательный взгляд позади не только игнорировать совпадения в начале строки, но также игнорировать совпадения, где заглавнаябукве предшествует еще одна заглавная буква.Это обрабатывает такие случаи, как «VALUE».

Первая часть регулярного выражения сама по себе завершается с ошибкой в ​​eclipseRCPExt из-за невозможности разделения между «RPC» и «Ext».Это цель второго пункта: (?<!^)(?=[A-Z][a-z].В этом пункте разрешается разделение перед каждой заглавной буквой, за которой следует строчная буква, за исключением начала строки.

69 голосов
/ 29 сентября 2011

Кажется, вы делаете это более сложным, чем нужно.Для camelCase местоположение разделения - это просто место, где заглавная буква сразу следует за строчной:

(?<=[a-z])(?=[A-Z])

Вот как это регулярное выражение разделяет данные вашего примера:

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCPExt

Единственное отличие от желаемого результата - eclipseRCPExt, который, я бы сказал, правильно разделен здесь.

Addendum - улучшенная версия

Примечание: этот ответ недавноЯ получил возражение, и я понял, что есть лучший способ ...

Добавив вторую альтернативу приведенному выше регулярному выражению, все тестовые примеры ОП правильно разделены.

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])

Вот как улучшенное регулярное выражение разбивает данные примера:

  • value -> value
  • camelValue -> camel / Value
  • TitleValue -> Title / Value
  • VALUE -> VALUE
  • eclipseRCPExt -> eclipse / RCP / Ext

Редактировать: 20130824 Добавлена ​​улучшенная версия для обработки RCPExt -> RCP / Ext кейс.

28 голосов
/ 29 сентября 2011

Другим решением будет использование выделенного метода в commons-lang : StringUtils # splitByCharacterTypeCamelCase

10 голосов
/ 11 марта 2012

Я не смог заставить работать решение aix (и оно не работает на RegExr), поэтому я нашел свое собственное, которое я протестировал, и, похоже, делает именно то, что вы ищете:

((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))

и вот пример его использования:

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms.
;   (^[a-z]+)                       Match against any lower-case letters at the start of the string.
;   ([A-Z]{1}[a-z]+)                Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))", "$1 ")
newString := Trim(newString)

Здесь я разделяю каждое слово пробелом, поэтому вот несколько примеров преобразования строки:

  • ThisIsATitleCASEString => Это строка заголовка CASE
  • andThisOneIsCamelCASE => и это верблюд CASE

Это решение, описанное выше, делает то, о чем просит оригинальная запись, но мне также понадобилось регулярное выражение для поиска верблюжьих и паскальских строк, которые включали числа, поэтому я также придумал этот вариант, чтобы включить числа:

((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))

и пример использования:

; Regex Breakdown:  This will match against each word in Camel and Pascal case strings, while properly handling acrynoms and including numbers.
;   (^[a-z]+)                               Match against any lower-case letters at the start of the command.
;   ([0-9]+)                                Match against one or more consecutive numbers (anywhere in the string, including at the start).
;   ([A-Z]{1}[a-z]+)                        Match against Title case words (one upper case followed by lower case letters).
;   ([A-Z]+(?=([A-Z][a-z])|($)|([0-9])))    Match against multiple consecutive upper-case letters, leaving the last upper case letter out the match if it is followed by lower case letters, and including it if it's followed by the end of the string or a number.
newString := RegExReplace(oldCamelOrPascalString, "((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))", "$1 ")
newString := Trim(newString)

А вот несколько примеров того, как строка с числами преобразуется с помощью этого регулярного выражения:

  • myVariable123 => моя переменная 123
  • my2Variables => мои 2 переменные
  • The3rdVariableIsHere => 3-я переменная здесь
  • 12345NumsAtTheStartIncludedToo => 12345 Nums при запуске тоже слишком
2 голосов
/ 25 сентября 2017

Brief

Оба верхних ответа здесь предоставляют код, использующий положительные взгляды, который поддерживается не всеми разновидностями регулярных выражений. Ниже приведено регулярное выражение PascalCase и camelCase, которое можно использовать на нескольких языках.

Примечание: Я понимаю, что этот вопрос касается Java, однако я также вижу несколько упоминаний этого поста в других вопросах, помеченных для разных языков, а также некоторые комментарии по этому вопросу для одного и того же.

код

Смотрите здесь это регулярное выражение

([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)

Результаты

Пример ввода

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Пример вывода

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Объяснение

  • Соответствует одному или нескольким прописным буквенным символам [A-Z]+
  • или соответствует нулю или одному прописному буквенному символу [A-Z]?, за которым следует один или несколько строчных буквенных символов [a-z]+
  • Убедитесь, что в нижнем регистре указан буквенный символ [A-Z] или символ границы слова \b
2 голосов
/ 11 февраля 2013

Чтобы обрабатывать больше букв, чем просто A-Z:

s.split("(?<=\\p{Ll})(?=\\p{Lu})|(?<=\\p{L})(?=\\p{Lu}\\p{Ll})");

Или:

  • Разделить после любой строчной буквы, за которой следует заглавная буква.

Eg parseXML -> parse, XML.

или

  • Разделить после любой буквы, за которой следуют заглавная и строчная буквы.

например. XMLParser -> XML, Parser.


В более читабельном виде:

public class SplitCamelCaseTest {

    static String BETWEEN_LOWER_AND_UPPER = "(?<=\\p{Ll})(?=\\p{Lu})";
    static String BEFORE_UPPER_AND_LOWER = "(?<=\\p{L})(?=\\p{Lu}\\p{Ll})";

    static Pattern SPLIT_CAMEL_CASE = Pattern.compile(
        BETWEEN_LOWER_AND_UPPER +"|"+ BEFORE_UPPER_AND_LOWER
    );

    public static String splitCamelCase(String s) {
        return SPLIT_CAMEL_CASE.splitAsStream(s)
                        .collect(joining(" "));
    }

    @Test
    public void testSplitCamelCase() {
        assertEquals("Camel Case", splitCamelCase("CamelCase"));
        assertEquals("lorem Ipsum", splitCamelCase("loremIpsum"));
        assertEquals("XML Parser", splitCamelCase("XMLParser"));
        assertEquals("eclipse RCP Ext", splitCamelCase("eclipseRCPExt"));
        assertEquals("VALUE", splitCamelCase("VALUE"));
    }    
}
0 голосов
/ 02 января 2019

Я могу подтвердить, что строка регулярного выражения ([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b), указанная выше ctwheels, работает с разновидностью регулярного выражения Microsoft.

Я также хотела бы предложить следующую альтернативу, основанную на регулярном выражении ctwheels, котораяобрабатывает числовые символы: ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

Это позволяет разбивать строки, такие как:

DrivingB2BTradeIn2019Onwards

до

Вождение B2B торговля в 2019 году

0 голосов
/ 03 июня 2017

Вместо того, чтобы искать разделители, которых нет , вы могли бы также рассмотреть поиск компонентов имен (они, безусловно, есть):

String test = "_eclipse福福RCPExt";

Pattern componentPattern = Pattern.compile("_? (\\p{Upper}?\\p{Lower}+ | (?:\\p{Upper}(?!\\p{Lower}))+ \\p{Digit}*)", Pattern.COMMENTS);

Matcher componentMatcher = componentPattern.matcher(test);
List<String> components = new LinkedList<>();
int endOfLastMatch = 0;
while (componentMatcher.find()) {
    // matches should be consecutive
    if (componentMatcher.start() != endOfLastMatch) {
        // do something horrible if you don't want garbage in between

        // we're lenient though, any Chinese characters are lucky and get through as group
        String startOrInBetween = test.substring(endOfLastMatch, componentMatcher.start());
        components.add(startOrInBetween);
    }
    components.add(componentMatcher.group(1));
    endOfLastMatch = componentMatcher.end();
}

if (endOfLastMatch != test.length()) {
    String end = test.substring(endOfLastMatch, componentMatcher.start());
    components.add(end);
}

System.out.println(components);

Это выводит [eclipse, 福福, RCP, Ext].Преобразование в массив, конечно, просто.

0 голосов
/ 11 июля 2016

Вы можете использовать выражение ниже для Java:

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?=[A-Z][a-z])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)
...