RegEx zum Teilen von camelCase oder TitleCase (erweitert)


80

Ich habe ein brillantes 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: WERT -> V / A / L / U / E.
  • Fall 2: eclipseRCPExt -> eclipse / R / C / P / Ext

Meiner Meinung nach sollte das Ergebnis sein:

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

Mit anderen Worten, wenn n Großbuchstaben angegeben sind:

  • Wenn auf die n Zeichen Zeichen in Kleinbuchstaben folgen, sollten die Gruppen sein: (n-1 Zeichen) / (n-te Zeichen + Kleinzeichen)
  • Wenn die n Zeichen am Ende sind, sollte die Gruppe sein: (n Zeichen).

Irgendeine Idee, wie man diesen regulären Ausdruck verbessern kann?


Scheint, dass Sie wahrscheinlich einen bedingten Modifikator für den ^und einen anderen bedingten Fall für Großbuchstaben im negativen Lookbehind benötigen würden. Ich bin mir nicht sicher, aber ich denke, das wäre die beste Wahl, um das Problem zu beheben.
Nightfirecat

Wenn jemand untersucht
Clam

Antworten:


112

Der folgende reguläre Ausdruck funktioniert für alle oben genannten 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 funktioniert, indem das negative Lookbehind gezwungen wird, Übereinstimmungen am Anfang der Zeichenfolge nicht nur zu ignorieren, sondern auch Übereinstimmungen zu ignorieren, bei denen einem Großbuchstaben ein anderer Großbuchstabe vorangestellt ist. Dies behandelt Fälle wie "VALUE".

Der erste Teil des regulären Ausdrucks schlägt bei "eclipseRCPExt" fehl, indem er nicht zwischen "RPC" und "Ext" aufgeteilt werden kann. Dies ist der Zweck der zweiten Klausel : (?<!^)(?=[A-Z][a-z]. Diese Klausel ermöglicht eine Aufteilung vor jedem Großbuchstaben, gefolgt von einem Kleinbuchstaben, außer am Anfang der Zeichenfolge.


1
Dieser funktioniert nicht mit PHP, während @ ridgerunner dies tut. Auf PHP heißt es "Lookbehind Assertion ist keine feste Länge bei Offset 13".
igorsantos07

15
@Igoru: Regex Aromen variieren. Die Frage bezieht sich auf Java, nicht auf PHP, ebenso wie die Antwort.
NPE

1
Während die Frage als "Java" markiert ist, ist die Frage immer noch generisch - abgesehen von Codebeispielen (die niemals generisch sein könnten). Also, wenn es eine einfachere Version dieses
regulären Ausdrucks

7
@Igoru: Der "generische Regex" ist ein imaginäres Konzept.
Casimir et Hippolyte

3
@ igorsantos07: Nein, integrierte Regex-Implementierungen variieren stark zwischen den Plattformen. Einige versuchen, Perl-ähnlich zu sein, andere versuchen, POSIX-ähnlich zu sein, und andere sind etwas dazwischen oder völlig anders.
Christoffer Hammarström

75

Es scheint, dass Sie dies komplizierter machen, als es sein muss. Bei camelCase befindet sich die geteilte Position einfach überall dort, wo ein Großbuchstabe unmittelbar auf einen Kleinbuchstaben folgt:

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

So teilt dieser Regex Ihre Beispieldaten 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 behaupten würde, dass sie hier korrekt 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 regulären Ausdruck werden alle Testfälle des OP korrekt aufgeteilt.

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

So teilt der verbesserte Regex die Beispieldaten auf:

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

Bearbeiten: 20130824 Verbesserte Version hinzugefügt, um RCPExt -> RCP / ExtFall zu behandeln .


Danke für deinen Beitrag. In diesem Beispiel muss RCP und Ext getrennt werden, da ich die Teile in einen konstanten Namen konvertiere (Stilrichtlinie: "Alle Großbuchstaben verwenden Unterstriche, um Wörter zu trennen."). In diesem Fall bevorzuge ich ECLIPSE_RCP_EXT gegenüber ECLIPSE_RCPEXT.
Jmini

3
Danke für die Hilfe; Ich habe Ihre Regex geändert, um ein paar Optionen hinzuzufügen, um Ziffern in der Zeichenfolge zu pflegen:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
Thoroc

Das ist die beste Antwort! Einfach und klar. Diese Antwort und die ursprüngliche RegEx des OP funktionieren jedoch nicht für Javascript & Golang!
Viet


10

Ich konnte die Lösung von aix nicht zum Laufen bringen (und sie funktioniert auch nicht mit RegExr), also habe ich mir eine eigene ausgedacht, die ich getestet habe und die genau das zu tun scheint, 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. Hier sind einige Beispiele, wie die Zeichenfolge transformiert wird:

  • ThisIsATitleCASEString => Dies ist eine Titel-CASE-Zeichenfolge
  • andThisOneIsCamelCASE => und This One Is Camel CASE

Diese obige Lösung macht das, was der ursprüngliche Beitrag verlangt, aber ich brauchte auch einen regulären Ausdruck, um Kamel- und Pascal-Strings zu finden, die Zahlen enthielten. Deshalb habe ich mir auch diese Variante ausgedacht, um Zahlen einzuschließen:

((^[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 diesem regulären Ausdruck transformiert wird:

  • myVariable123 => meine Variable 123
  • my2Variables => meine 2 Variablen
  • The3rdVariableIsHere => Die 3. rdVariable ist da
  • 12345NumsAtTheStartIncludedToo => 12345 Nums am Anfang ebenfalls enthalten

1
Zu viele unnötige Erfassungsgruppen. Sie hätten es schreiben können als: (^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))für das erste und (^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))für das zweite. Das äußerste kann auch entfernt werden, aber die Syntax, die sich auf die gesamte Übereinstimmung bezieht, ist nicht zwischen Sprachen portierbar ( $0und es $&gibt zwei Möglichkeiten).
nhahtdh

Das gleiche vereinfachte reguläre Ausdruck:([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Alex Suhinin

3

So verarbeiten Sie mehr Buchstaben als nur A-Z:

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

Entweder:

  • Nach Kleinbuchstaben teilen, gefolgt von Großbuchstaben.

ZB parseXML-> parse, XML.

oder

  • Nach jedem Buchstaben teilen, gefolgt von Groß- und Kleinbuchstaben.

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

3

Kurz

Beide Top-Antworten hier liefern Code mit positiven Lookbehinds, der nicht von allen Regex-Varianten unterstützt wird. Die Regex unten erfassen wird sowohl PascalCaseund camelCaseund kann in mehreren Sprachen verwendet werden.

Hinweis: Mir ist klar, dass sich diese Frage auf Java bezieht. Ich sehe jedoch auch mehrere Erwähnungen dieses Beitrags in anderen Fragen, die für verschiedene Sprachen markiert sind, sowie einige Kommentare zu dieser Frage für dieselbe.

Code

Sehen Sie diesen regulären Ausdruck hier

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

Ergebnisse

Probeneingabe

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Beispielausgabe

eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Erläuterung

  • Entspricht einem oder mehreren Alpha-Großbuchstaben [A-Z]+
  • Oder stimmen Sie mit null oder einem Großbuchstaben überein [A-Z]?, gefolgt von einem oder mehreren Kleinbuchstaben[a-z]+
  • Stellen Sie sicher, dass das Folgende ein Alpha-Zeichen in Großbuchstaben [A-Z]oder ein Wortbegrenzungszeichen ist\b


0

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)

3
Hallo Maicon, willkommen bei StackOverflow und vielen Dank für Ihre Antwort. Dies kann zwar die Frage beantworten, bietet jedoch keine Erklärung für andere, um zu erfahren, wie das Problem gelöst wird. Könnten Sie Ihre Antwort so bearbeiten, dass sie eine Erklärung Ihres Codes enthält? Vielen Dank!
Tim Malone

0

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

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 aus [eclipse, 福福, RCP, Ext]. Die Konvertierung in ein Array ist natürlich einfach.


0

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

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

Dies kann Zeichenfolgen wie:

FahrenB2BTradeIn2019Onwards

zu

Den B2B-Handel ab 2019 vorantreiben


0

Eine JavaScript-Lösung

/**
 * 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('!');
};

Sie fragen nach einer JavaScript-Lösung. Und warum geben Sie doppelt die gleiche Lösung ? Wenn Sie der Meinung sind, dass diese Fragen indentisch sind, stimmen Sie ab, um eine Frage als Duplikat zu schließen.
Toto
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.