web-dev-qa-db-de.com

RegEx zum Teilen von camelCase oder TitleCase (erweitert)

Ich habe ein brilliant RegEx gefunden, um den Teil eines camelCase- oder TitleCase-Ausdrucks zu extrahieren.

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

Es funktioniert wie erwartet:

  • wert -> Wert
  • camelValue -> camel/Value
  • TitleValue -> Titel/Wert

Zum Beispiel mit Java:

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

Mein Problem ist, dass es in einigen Fällen nicht funktioniert:

  • Fall 1: VALUE -> V/A/L/U/E
  • Fall 2: EclipseRCPExt -> Eclipse/R/C/P/Ext

Meiner Meinung nach sollte das Ergebnis sein:

  • Fall 1: VALUE
  • Fall 2: Eclipse/RCP/Ext

Mit anderen Worten, gegebene n Großbuchstaben:

  • wenn auf die n Zeichen Zeichen aus Kleinbuchstaben folgen, sollten die Gruppen lauten: (n-1 Zeichen)/(n-te Zeichen + untere Zeichen)
  • wenn die n Zeichen am Ende stehen, sollte die Gruppe sein: (n Zeichen).

Irgendeine Idee, wie man diese Regex verbessern kann?

70
Jmini

Die folgende Regex funktioniert für alle obigen Beispiele:

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

Es bewirkt, dass der negative Look nicht nur Übereinstimmungen am Anfang der Zeichenfolge ignoriert, sondern auch Übereinstimmungen ignoriert, bei denen einem Großbuchstaben ein weiterer Großbuchstabe vorangestellt ist. Dies behandelt Fälle wie "VALUE".

Der erste Teil des regulären Ausdrucks allein schlägt bei "eclipseRCPExt" fehl, da er nicht zwischen "RPC" und "Ext" aufteilen kann. Dies ist der Zweck der zweiten Klausel: (?<!^)(?=[A-Z][a-z]. Diese Klausel erlaubt eine Aufteilung vor jedem Großbuchstaben, auf den ein Kleinbuchstabe folgt, außer am Anfang der Zeichenfolge.

97
NPE

Es scheint, dass Sie das komplizierter machen, als es sein muss. Bei camelCase ist der Split-Speicherort einfach überall, wo ein Großbuchstabe unmittelbar auf einen Kleinbuchstaben folgt:

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

Diese Regex teilt Ihre Beispieldaten folgendermaßen auf:

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

Der einzige Unterschied zu Ihrer gewünschten Ausgabe besteht in der eclipseRCPExt, von der ich behaupte, dass sie hier richtig aufgeteilt ist.

Nachtrag - Verbesserte Version

Hinweis: Diese Antwort wurde kürzlich positiv bewertet und mir wurde klar, dass es einen besseren Weg gibt ...

Durch Hinzufügen einer zweiten Alternative zum obigen Regex werden alle Testfälle des OP korrekt aufgeteilt.

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

So teilt der verbesserte reguläre Ausdruck die Beispieldaten auf:

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

Edit: 20130824 Verbesserte Version für RCPExt -> RCP / Ext Fall hinzugefügt.

64
ridgerunner

Eine andere Lösung wäre die Verwendung einer speziellen Methode in commons-lang : StringUtils # splitByCharacterTypeCamelCase

24
YMomb

Ich konnte die Lösung von aix nicht zum Laufen bringen (und auch nicht mit RegExr), also habe ich mir meine eigene Lösung ausgedacht, die ich getestet habe und genau das zu tun, wonach Sie suchen:

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

und hier ist ein Beispiel für die Verwendung:

; 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)

Hier trenne ich jedes Wort durch ein Leerzeichen, daher sind hier einige Beispiele, wie der String transformiert wird:

  • ThisIsATitleCASEString => Dies ist ein Titel-CASE-String 
  • andThisOneIsCamelCASE => und This Is Camel CASE

Diese Lösung oben macht das, was der ursprüngliche Beitrag verlangt, aber ich brauchte auch einen Regex, um Kamel- und Pascal-Strings zu finden, die Zahlen enthielten. Daher kam ich auch zu dieser Variation mit Zahlen:

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

und ein Beispiel für die Verwendung:

; 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)

Und hier sind einige Beispiele, wie eine Zeichenfolge mit Zahlen mit dieser Regex umgewandelt wird:

  • myVariable123 => meine Variable 123
  • my2Variables => meine 2 Variablen
  • The3rdVariableIsHere => Die 3. Variable ist hier
  • 12345NumsAtTheStartIncludedToo => 12345 Nums am Anfang enthalten 
9
deadlydog

Um mehr Buchstaben als nur A-Z zu behandeln:

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

Entweder:

  • Trennen Sie nach jedem Kleinbuchstaben, gefolgt von Großbuchstaben.

ZB parseXML -> parse, XML.

oder 

  • Nach jedem Buchstaben aufteilen, gefolgt von Großbuchstaben und Kleinbuchstaben.

Z.B. XMLParser -> XML, Parser.


In besser lesbarer Form:

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"));
    }    
}

Kurz

Beide Top-Antworten bieten Code mit positiven LookHinds, die nicht von allen Regex-Varianten unterstützt werden. Der folgende reguläre Ausdruck erfasst sowohl PascalCase als auch camelCase und kann in mehreren Sprachen verwendet werden.

Anmerkung: Ich weiß, dass diese Frage Java betrifft, ich sehe jedoch mehrere Erwähnungen dieses Beitrags in anderen Fragen, die für verschiedene Sprachen getaggt sind, sowie einige Kommentare zu dieser Frage.

Code

Sehen Sie diese Regex hier in Verwendung

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

Ergebnisse

Probeneingang

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Beispielausgabe

Eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Erläuterung

  • Übereinstimmung mit einem oder mehreren Buchstaben in Großbuchstaben [A-Z]+
  • Oder entspricht keinem oder einem Großbuchstaben [A-Z]?, gefolgt von einem oder mehreren Kleinbuchstaben [a-z]+
  • Stellen Sie sicher, dass das folgende Zeichen ein Großbuchstabe-[A-Z] oder ein Word-Begrenzungszeichen \b ist.
2
ctwheels

Sie können den folgenden Ausdruck für Java verwenden:

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

Ich kann bestätigen, dass der von ctwheels oben angegebene Regex-String ([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b) mit der Microsoft-Variante von Regex funktioniert.

Ich möchte auch die folgende Alternative vorschlagen, basierend auf dem Regex von ctwheels, der numerische Zeichen verarbeitet: ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

Dies ermöglicht das Aufteilen von Strings wie:

DrivingB2BTradeIn2019Onwards

zu

B2B-Handel ab 2019 vorantreiben

0
William Bell

Anstatt nach Trennzeichen zu suchen, die nicht vorhanden sind könnten Sie auch die Namenskomponenten suchen (die sicherlich vorhanden sind)

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);

Dies gibt [Eclipse, 福福, RCP, Ext] aus. Die Konvertierung in ein Array ist natürlich einfach.

0
Maarten Bodewes