RegEx для разделения camelCase или TitleCase (расширенный)

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

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

Работает так, как ожидалось:

  • значение → значение
  • camelValue → верблюд/значение
  • TitleValue → Название/Значение

Например, с Java:

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

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

  • Случай 1: VALUE → V/A/L/U/E
  • Случай 2: eclipseRCPExt → eclipse/R/C/P/Ext

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

  • Случай 1: VALUE
  • Случай 2: eclipse/RCP/Ext

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

Добавление - Улучшенная версия

Примечание. Этот ответ недавно получил преимущество, и я понял, что есть лучший способ...

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

(?<=[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.

Ответ 4

Я не мог заставить решение 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 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 заголовка
  • иThisOneIsCamelCASE = > , и это один из CEMEL Camel

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

((^[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 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 = > my Variable 123
  • my2Variables = > my 2 Variables
  • The3rdVariableIsHere = > 3 rdVariable Is Here
  • 12345NumsAtTheStartIncludedToo = > 12345 Nums At The Start Included Too

Ответ 5

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

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

Или:

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

Например, parseXMLparse, XML.

или

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

например. XMLParserXML, 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"));
    }    
}

Ответ 6

Краткое

В обоих верхних ответах здесь содержится код с использованием положительных lookbehind, которые не поддерживаются всеми ароматами regex. Регулярное выражение ниже будет отображать как 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

Ответ 7

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

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

Ответ 8

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

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]. Преобразование в массив, конечно, прост.