RegEx do dzielenia camelCase lub TitleCase (zaawansowane)

Znalazłembrilliant RegEx Aby wyodrębnić część wyrażenia camelCase lub TitleCase.

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

Działa zgodnie z oczekiwaniami:

  • value - > value
  • camelValue - > camel / Value
  • TitleValue - > Title / Value

Na przykład z Java:

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

Według mnie wynik powinien być:

  • Case 1: VALUE
  • Przypadek 2: eclipse / RCP / Ext

Innymi słowy, podane n wielkich znaków:

  • Jeśli po n znakach pojawiają się małe litery, grupy powinny być: (n-1 znak) / (n-th znak + małe znaki)
  • Jeśli n znaków znajduje się na końcu, grupa powinna być: (N znaków).

Jakiś pomysł, jak poprawić ten regex?

Author: Jmini, 2011-09-29

8 answers

Następujące wyrażenia regularne działają 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);
    }
}   

To działa poprzez wymuszenie negatywnego lookbehind, aby nie tylko ignorował dopasowania na początku łańcucha, ale także ignorował dopasowania, w których wielka litera jest poprzedzona inną wielką literą. To obsługuje przypadki takie jak "wartość".

Pierwsza część wyrażenia regularnego sama w sobie nie działa na "eclipseRCPExt", nie dzieląc się między "RPC" i "Ext". Taki jest cel drugiej klauzuli: (?<!^)(?=[A-Z][a-z]. Klauzula ta pozwala na podział przed każdą wielką literą, po której następuje mała litera, z wyjątkiem początku łańcucha.

 92
Author: NPE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-29 18:50:07

Wygląda na to, że komplikujesz to bardziej, niż musi być. Dla camelCase , miejsce podziału jest po prostu wszędzie tam, gdzie wielka litera natychmiast następuje po małej literze:

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

Oto jak ten regex dzieli twoje przykładowe dane:

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

Jedyną różnicą od pożądanego wyniku jest eclipseRCPExt, co moim zdaniem jest poprawne rozdzielmy się.

Dodatek-ulepszona wersja

Uwaga: Ta odpowiedź dostała ostatnio pozytywną opinię 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 ulepszony regex dzieli przykładowe dane:

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

Edit: 20130824 Dodano ulepszoną wersję do obsługi obudowy RCPExt -> RCP / Ext.

 60
Author: ridgerunner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-24 15:17:11

Innym rozwiązaniem byłoby użycie dedykowanej metody w commons-lang: StringUtils#splitByCharacterTypeCamelCase

 22
Author: YMomb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-31 09:04:52

Nie mogłem uruchomić rozwiązania Aixa (i nie działa również na RegExr), więc wymyśliłem własne, które przetestowałem i wydaje się robić dokładnie to, czego szukasz:

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

A oto przykład jego 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:

  • ThisIsATitleCASEString = > jest to ciąg przypadków tytułowych
  • andThisOneIsCamelCASE = > a ten to Wielbłąd CASE

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, które zawierały liczby, więc wymyśliłem również tę odmianę, aby zawierać liczby:

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

I przykład jego 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 jak łańcuch z liczbami jest przekształcany za pomocą tego wyrażenia regularnego:

  • myVariable123 = > Moja zmienna 123
  • my2Variables = > my 2 Zmienne
  • The3rdVariableIsHere = > 3 rdVariable jest tutaj
  • 12345numsatthestartincludedtoo => 12345 Nums na początku włączone zbyt
 8
Author: deadlydog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-12 15:09:06

Aby obsłużyć więcej liter niż tylko A-Z:

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

Albo:

  • podziel po małej literze, po której następuje wielka litera.

E. g parseXML -> parse, XML.

Lub

  • podziel po dowolnej literze, po której następuje wielka litera i mała litera.

E. g. 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"));
    }    
}
 2
Author: Christoffer Hammarström,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-15 17:14:49

Krótki

Obie najlepsze odpowiedzi tutaj dostarczają kodu za pomocą dodatnich lookbehinds, który nie jest obsługiwany przez wszystkie wyrażenia regularne. Poniższy regex przechwytuje zarówno PascalCase, jak i camelCase 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 na to pytanie dla to samo.

Kod

Zobacz ten regex w użyciu tutaj

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

Wyniki

Przykładowe Wejście

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Przykładowe Wyjście

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Wyjaśnienie

  • Dopasuj jeden lub więcej wielkich znaków Alfa [A-Z]+
  • lub dopasuj zero lub jeden duży znak Alfa [A-Z]?, po którym następuje jeden lub więcej małych znaków alfa [a-z]+
  • upewnij się, że poniżej znajduje się duży znak Alfa [A-Z] lub granica słowa znak \b
 2
Author: ctwheels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 15:54:43

Możesz użyć poniższego wyrażenia dla Javy:

(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?=[A-Z][a-z])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D)
 0
Author: Maicon Zucco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-10 23:31:25

Zamiast szukać separatorów, których nie ma Możesz również rozważyć znalezienie składników nazwy (te na pewno tam są):

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 wyjście [eclipse, 福福, RCP, Ext]. Konwersja na tablicę jest oczywiście prosta.

 0
Author: Maarten Bodewes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-03 16:43:05