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
.
Ответ 3
Другим решением будет использование выделенного метода в commons-lang: StringUtils # splitByCharacterTypeCamelCase
Ответ 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})");
Или:
- Разделение после любой строчной буквы, за которой следует буква верхнего регистра.
Например, 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"));
}
}
Ответ 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]
. Преобразование в массив, конечно, прост.