Znalazłem genialne wyrażenie RegEx do wyodrębnienia części wyrażenia camelCase lub TitleCase.
(?<!^)(?=[A-Z])
Działa zgodnie z oczekiwaniami:
- wartość -> wartość
- camelValue -> camel / Wartość
- TitleValue -> Title / Value
Na przykład w Javie:
String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
//words equals words = new String[]{"lorem","Ipsum"}
Mój problem polega na tym, że w niektórych przypadkach nie działa:
- Przypadek 1: WARTOŚĆ -> V / A / L / U / E
- Przypadek 2: eclipseRCPExt -> eclipse / R / C / P / Ext
Moim zdaniem wynik powinien być:
- Przypadek 1: VALUE
- Przypadek 2: zaćmienie / RCP / Ext
Innymi słowy, mając n wielkich liter:
- jeśli po n znakach następuje małe litery, grupami powinny być: (n-1 znaków) / (n-ty znak + dolne znaki)
- jeśli n znaków jest na końcu, grupa powinna być: (n znaków).
Masz jakiś pomysł, jak ulepszyć to wyrażenie regularne?
java
regex
camelcasing
title-case
Jmini
źródło
źródło
^
i innego warunkowego przypadku dla wielkich liter w ujemnej pozycji wstecznej. Nie testowałem na pewno, ale myślę, że byłby to najlepszy sposób na naprawienie problemu.Odpowiedzi:
Następujące wyrażenie regularne działa dla wszystkich powyższych przykładów:
public static void main(String[] args) { for (String w : "camelValue".split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) { System.out.println(w); } }
Działa poprzez wymuszenie negatywnego lookbehind, aby nie tylko ignorować dopasowania na początku ciągu, ale także ignorować dopasowania, w których duża litera jest poprzedzona inną wielką literą. Obsługuje przypadki takie jak „VALUE”.
Pierwsza część wyrażenia regularnego sama z siebie nie działa na „eclipseRCPExt”, ponieważ nie można podzielić między „RPC” i „Ext”. Taki jest cel drugiej klauzuli:
(?<!^)(?=[A-Z][a-z]
. Ta klauzula umożliwia podział przed każdą wielką literą, po której następuje mała litera, z wyjątkiem początku ciągu.źródło
Wygląda na to, że komplikujesz to bardziej niż powinno. W przypadku camelCase lokalizacja podziału jest po prostu wszędzie tam, gdzie wielka litera występuje bezpośrednio po małej:
(?<=[a-z])(?=[A-Z])
Oto jak to wyrażenie regularne dzieli przykładowe dane:
value -> value
camelValue -> camel / Value
TitleValue -> Title / Value
VALUE -> VALUE
eclipseRCPExt -> eclipse / RCPExt
Jedyna różnica w stosunku do pożądanego wyniku polega na tym
eclipseRCPExt
, że według mnie jest on tutaj poprawnie podzielony.Dodatek - ulepszona wersja
Uwaga: ta odpowiedź ostatnio zyskała uznanie i zdałem sobie sprawę, że jest lepszy sposób ...
Dodając drugą alternatywę do powyższego wyrażenia regularnego, wszystkie przypadki testowe OP są poprawnie podzielone.
(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])
Oto jak ulepszone wyrażenie regularne dzieli przykładowe dane:
value -> value
camelValue -> camel / Value
TitleValue -> Title / Value
VALUE -> VALUE
eclipseRCPExt -> eclipse / RCP / Ext
Edycja: 20130824 Dodano ulepszoną wersję do obsługi
RCPExt -> RCP / Ext
obudowy.źródło
(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
Innym rozwiązaniem byłoby użycie dedykowanej metody w języku commons-lang : StringUtils # splitByCharacterTypeCamelCase
źródło
Nie mogłem sprawić, aby rozwiązanie aix działało (i nie działa też na RegExr), więc wymyśliłem własne, które przetestowałem i wydaje się, że robi dokładnie to, czego szukasz:
((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))
a oto przykład użycia:
; 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)
Tutaj oddzielam każde słowo spacją, więc oto kilka przykładów przekształcania ciągu:
Powyższe rozwiązanie robi to, o co prosi oryginalny post, ale potrzebowałem również wyrażenia regularnego, aby znaleźć ciągi wielbłąda i pascala zawierające liczby, więc wymyśliłem również tę odmianę, aby uwzględnić liczby:
((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))
i przykład użycia:
; 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)
A oto kilka przykładów tego, jak ciąg z liczbami jest przekształcany za pomocą tego wyrażenia regularnego:
źródło
(^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))
dla pierwszego i(^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))
dla drugiego. Najbardziej zewnętrzną można również usunąć, ale składnia odnosząca się do całego dopasowania nie jest przenośna między językami ($0
i$&
są 2 możliwości).([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Aby obsłużyć więcej liter niż tylko
A-Z
:s.split("(?<=\\p{Ll})(?=\\p{Lu})|(?<=\\p{L})(?=\\p{Lu}\\p{Ll})");
Zarówno:
Eg
parseXML
->parse
,XML
.lub
Eg
XMLParser
->XML
,Parser
.W bardziej czytelnej formie:
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")); } }
źródło
Krótki
Obie najpopularniejsze odpowiedzi tutaj dostarczają kodu używającego pozytywnego lookbehinds, który nie jest obsługiwany przez wszystkie odmiany regex. Poniższe wyrażenie regularne przechwyci zarówno
PascalCase
icamelCase
i może być używany w wielu językach.Uwaga: zdaję sobie sprawę, że to pytanie dotyczy Javy, jednak widzę również wiele wzmianek o tym poście w innych pytaniach oznaczonych dla różnych języków, a także kilka komentarzy do tego pytania w tym samym.
Kod
Zobacz używane wyrażenie regularne tutaj
Wyniki
Przykładowe wejście
Przykładowe wyjście
Wyjaśnienie
[A-Z]+
[A-Z]?
, po której następuje jeden lub więcej małych znaków alfa[a-z]+
[A-Z]
lub znakiem granicy słowa\b
źródło
Możesz użyć StringUtils. splitByCharacterTypeCamelCase ("loremIpsum") z Apache Commons Lang.
źródło
Możesz użyć poniższego wyrażenia dla Java:
źródło
Zamiast szukać separatorów, których tam nie ma, możesz również rozważyć znalezienie składników nazwy (z pewnością istnieją):
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);
To wychodzi
[eclipse, 福福, RCP, Ext]
. Konwersja do tablicy jest oczywiście prosta.źródło
Mogę potwierdzić, że ciąg wyrażenia regularnego
([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)
podany przez ctwheels powyżej działa z odmianą wyrażenia regularnego firmy Microsoft.Chciałbym również zasugerować następującą alternatywę, opartą na wyrażeniu regularnym ctwheels, które obsługuje znaki numeryczne:
([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b)
.To jest w stanie podzielić ciągi, takie jak:
do
źródło
Rozwiązanie 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('!'); };
źródło