RegEx для поділу camelCase або TitleCase (додатково)


81

Я знайшов блискучий RegEx для вилучення частини виразу camelCase або TitleCase.

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

Це працює, як очікувалося:

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

Наприклад, з Java:

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

Моя проблема полягає в тому, що це не працює в деяких випадках:

  • Випадок 1: ЦІННІСТЬ -> V / A / L / U / E
  • Випадок 2: eclipseRCPExt -> eclipse / R / C / P / Ext

На мою думку, результатом має бути:

  • Випадок 1: ЦІННІСТЬ
  • Випадок 2: затемнення / RCP / Ext

Іншими словами, дано n великих символів:

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

Будь-яка ідея про те, як покращити цей регулярний вираз?


Здається, вам, мабуть, знадобиться умовний модифікатор на ^та ще один умовний регістр великих літер із негативним вигляду позаду. Не тестував напевно, але я думаю, що це був би ваш найкращий вибір для вирішення проблеми.
Nightfirecat

Якщо хтось оглядає
молюск

Відповіді:


112

Наступний регулярний вираз працює для всіх наведених прикладів:

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]. Цей пункт допускає розділення перед кожною великою літерою, після якої йде мала літера, за винятком початку рядка.


1
цей не працює на PHP, тоді як @ ridgerunner's. На PHP написано "твердження lookbehind не має фіксованої довжини зі зміщенням 13".
igorsantos07

15
@Igoru: Смаки регулярних виразів різняться. Питання стосується Java, а не PHP, і відповідь також.
NPE

1
тоді як запитання позначено як "java", воно все ще є загальним - крім зразків коду (які ніколи не можуть бути загальними). Отже, якщо існує простіша версія цього регулярного виразу, і вона також працює між мовами, я думав, хтось повинен на це вказати :)
igorsantos07

7
@Igoru: "загальний регулярний вираз" - це уявне поняття.
Casimir et Hippolyte

3
@ igorsantos07: Ні, вбудовані реалізації регулярних виразів сильно відрізняються між платформами. Хтось намагається бути схожим на Perl, хтось намагається бути схожим на POSIX, а хтось щось середнє або зовсім інше.
Christoffer Hammarström

78

Здається, ви робите це складніше, ніж це повинно бути. Для 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справи.


Дякуємо за ваш внесок. Мені потрібно відокремити RCP та Ext у цьому прикладі, оскільки я перетворюю частини в ім'я константи (вказівка ​​щодо стилю: "усі великі літери використовують підкреслення для відокремлення слів.") У цьому випадку я віддаю перевагу ECLIPSE_RCP_EXT ECLIPSE_RCPEXT.
Jmini

4
Дякую за допомогу; Я змінив ваш регулярний вираз, щоб додати кілька варіантів для догляду за цифрами в рядку:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
thoroc

Це найкраща відповідь! Просто і зрозуміло. Однак ця відповідь та оригінальний RegEx від OP не працюють для Javascript & Golang!
В'єт


10

Я не зміг змусити рішення 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 => і це КАМЕР КЕЙЛ

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

((^[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 => Третя змінна вже тут
  • 12345NumsAtTheStartIncludedToo => 12345 Номери на початку також включені

1
Забагато непотрібних груп захоплення. Ви могли б написати це як (^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))для першого, так і (^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))для другого. Зовнішню частину також можна видалити, але синтаксис, що стосується цілого збігу, не переноситься між мовами ( $0і $&це 2 можливості).
nhahtdh

Той самий спрощений регулярний вираз:([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Алекс Сухінін

3

Щоб обробити більше листів, ніж просто A-Z:

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

Або:

  • Розділити після будь-якої малої літери, після якої йде велика літера.

Наприклад , 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"));
    }    
}

3

Короткий

Обидві найпопулярніші відповіді тут містять код, використовуючи позитивний погляд, який не підтримується усіма варіантами регулярних виразів. Регулярний вираз, наведений нижче, охоплює як 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


0

Ви можете використовувати вираз нижче для Java:

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

3
Привіт, Maicon, ласкаво просимо до StackOverflow і дякуємо за вашу відповідь. Хоча це може відповісти на запитання, воно не дає жодних пояснень для інших, щоб дізнатися, як це вирішує проблему. Не могли б ви відредагувати свою відповідь, включивши пояснення вашого коду? Дякую!
Тім Малоун

0

Замість того, щоб шукати сепаратори, яких немає, ви можете також розглянути можливість пошуку компонентів імен (вони, безумовно, є):

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

Я можу підтвердити, що рядок регулярних ([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)виразів, вказаний вище ctwheels, працює зі смаком регулярного виразу Microsoft.

Я також хотів би запропонувати наступну альтернативу, засновану на регулярних виразах ctwheels ', який обробляє цифрові символи: ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

Це може розділити рядки, такі як:

ВодінняB2BTradeIn2019Нагорі

до

Сприяння торгівлі B2B у 2019 році


0

Рішення JavaScript

/**
 * howToDoThis ===> ["", "how", "To", "Do", "This"]
 * @param word word to be split
 */
export const splitCamelCaseWords = (word: string) => {
    if (typeof word !== 'string') return [];
    return word.replace(/([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)/g, '!$&').split('!');
};

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