web-dev-qa-db-fra.com

RegEx pour séparer camelCase ou TitleCase (advanced)

J'ai trouvé un brillant RegEx pour extraire la partie d'une expression camelCase ou TitleCase.

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

Cela fonctionne comme prévu:

  • valeur -> valeur
  • camelValue -> camel/Valeur
  • TitleValue -> Titre/Valeur

Par exemple avec Java:

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

Mon problème est que cela ne fonctionne pas dans certains cas:

  • Cas 1: VALEUR -> V/A/L/U/E
  • Cas 2: eclipseRCPExt -> Eclipse/R/C/P/Ext

À mon avis, le résultat devrait être:

  • Cas 1: VALEUR
  • Cas 2: Eclipse/RCP/Ext

En d'autres termes, n caractères majuscules étant donnés:

  • si les n caractères sont suivis par des caractères minuscules, les groupes doivent être: (n-1 caractères)/(nième caractère + caractères inférieurs)
  • si les n caractères sont à la fin, le groupe devrait être: (n caractères).

Une idée sur la façon d'améliorer cette regex?

70
Jmini

La regex suivante fonctionne pour tous les exemples ci-dessus:

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

Cela fonctionne en forçant le regard négatif à ignorer non seulement les correspondances au début de la chaîne, mais également les correspondances dans lesquelles une majuscule est précédée d'une autre majuscule. Cela gère les cas comme "valeur".

La première partie de l'expression rationnelle échoue seule sur "eclipseRCPExt" en ne se séparant pas entre "RPC" et "Ext". C'est l'objet de la deuxième clause: (?<!^)(?=[A-Z][a-z]. Cette clause autorise une division avant chaque lettre majuscule suivie d'une lettre minuscule, sauf au début de la chaîne.

97
NPE

Il semble que vous rendiez cela plus compliqué que nécessaire. Pour camelCase, l'emplacement de division est simplement n'importe où une lettre majuscule suit immédiatement une lettre minuscule:

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

Voici comment cette regex divise vos données d'exemple:

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

La seule différence par rapport à la sortie désirée concerne la eclipseRCPExt, qui, à mon avis, est correctement divisée ici.

Addendum - Version améliorée

Remarque: cette réponse a récemment suscité un vote positif et je me suis rendu compte qu'il existe un meilleur moyen ...

En ajoutant une seconde alternative à l'expression rationnelle ci-dessus, tous les tests élémentaires de l'OP sont correctement divisés.

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

Voici comment la regex améliorée divise les données d'exemple:

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

Edit: 20130824 Ajout d'une version améliorée pour gérer le cas RCPExt -> RCP / Ext.

64
ridgerunner

Une autre solution consisterait à utiliser une méthode dédiée dans commons-lang : StringUtils # splitByCharacterTypeCamelCase

24
YMomb

Je ne pouvais pas obtenir la solution aix au travail (et cela ne marche pas non plus avec RegExr), alors je me suis inventé avec le mien que j'ai testé et qui semble faire exactement ce que vous cherchez:

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

et voici un exemple d'utilisation:

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

Ici, je sépare chaque mot par un espace, voici donc quelques exemples de la transformation de la chaîne:

  • ThisIsATitleCASEString => Ceci est une chaîne de titre CASE 
  • andThisOneIsCamelCASE => et celui-ci est camel CASE

Cette solution ci-dessus fait ce que l'article original demande, mais j'avais également besoin d'une expression rationnelle pour trouver les chaînes camel et Pascal qui incluaient des nombres. J'ai donc également proposé cette variante pour inclure des nombres:

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

et un exemple d'utilisation:

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

Et voici quelques exemples de la transformation d'une chaîne de chiffres avec cette expression rationnelle:

  • myVariable123 => ma variable 123
  • my2Variables => mes 2 variables
  • The3rdVariableIsHere => La 3 ème Variable est ici
  • 12345NumsAtTheStartIncludedToo => 12345 au début inclus également 
9
deadlydog

Pour gérer plus de lettres que simplement A-Z:

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

Non plus:

  • Fractionner après une lettre minuscule, suivie d'une lettre majuscule.

E.g parseXML -> parse, XML.

ou 

  • Fractionner après une lettre, suivi d'une lettre majuscule et d'une lettre minuscule.

Par exemple. XMLParser -> XML, Parser.


Sous une forme plus lisible:

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

Bref

Les deux réponses principales ici fournissent un code utilisant des recherches positives, qui ne sont pas prises en charge par toutes les variantes de regex. Les expressions rationnelles ci-dessous captureront à la fois PascalCase et camelCase et peuvent être utilisées dans plusieurs langues.

Remarque: Je me rends bien compte que cette question concerne Java, cependant, je vois également de nombreuses mentions de ce message dans d'autres questions marquées pour différentes langues, ainsi que des commentaires sur cette question pour les mêmes.

Code

Voir cette expression rationnelle utilisée ici

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

Résultats

Exemple de saisie

eclipseRCPExt

SomethingIsWrittenHere

TEXTIsWrittenHERE

VALUE

loremIpsum

Exemple de sortie

Eclipse
RCP
Ext

Something
Is
Written
Here

TEXT
Is
Written
HERE

VALUE

lorem
Ipsum

Explication

  • Correspond à un ou plusieurs caractères alpha majuscules [A-Z]+
  • Ou correspond à zéro ou à un caractère alpha majuscule [A-Z]?, suivi d'un ou de plusieurs caractères alpha minuscules [a-z]+
  • Assurez-vous que ce qui suit est un caractère alpha majuscule [A-Z] ou un caractère de limite Word \b
2
ctwheels

Vous pouvez utiliser l'expression ci-dessous pour Java:

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

Je peux confirmer que la chaîne regex ([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b) donnée par ctwheels, ci-dessus, fonctionne avec la version Microsoft de regex.

Je voudrais également suggérer l’alternative suivante, basée sur l’expression rationnelle de ctwheels, qui gère les caractères numériques: ([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b).

Cela permet de scinder des chaînes telles que:

DrivingB2BTradeIn2019Onwards

à

Stimuler le commerce B2B à partir de 2019

0
William Bell

Au lieu de rechercher des séparateurs qui ne sont pas là vous pourriez aussi envisager de trouver les composants de nom (ceux-ci sont certainement là):

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

Ceci génère [Eclipse, 福福, RCP, Ext]. La conversion en tableau est bien sûr simple.

0
Maarten Bodewes