RegEx para dividir camelCase o TitleCase (avanzado)

Encontré un RegEx shiny para extraer la parte de una expresión camelCase o TitleCase.

(?<!^)(?=[AZ]) 

Funciona como se esperaba:

  • valor -> valor
  • camelValue -> camel / Valor
  • TitleValue -> Título / Valor

Por ejemplo con Java:

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

Mi problema es que no funciona en algunos casos:

  • Caso 1: VALOR -> V / A / L / U / E
  • Caso 2: eclipseRCPExt -> eclipse / R / C / P / Ext

En mi opinión, el resultado debería ser:

  • Caso 1: VALOR
  • Caso 2: eclipse / RCP / Ext

En otras palabras, dado n caracteres en mayúsculas:

  • si los n caracteres son seguidos por caracteres en minúsculas, los grupos deben ser: (n-1 caracteres) / (n-ésimo caracteres + caracteres inferiores)
  • si los n caracteres están al final, el grupo debe ser: (n caracteres).

¿Alguna idea sobre cómo mejorar esta expresión regular?

La siguiente expresión regular funciona para todos los ejemplos anteriores:

 public static void main(String[] args) { for (String w : "camelValue".split("(? 

Funciona obligando al aspecto negativo detrás de no solo a ignorar las coincidencias al comienzo de la cadena, sino también a ignorar las coincidencias donde una letra mayúscula va precedida por otra letra mayúscula. Esto maneja casos como "VALOR".

La primera parte de la expresión regular por sí sola falla en "eclipseRCPExt" al no poder dividir entre "RPC" y "Ext". Este es el propósito de la segunda cláusula: (? . Esta cláusula permite una división antes de cada letra mayúscula seguida de una letra minúscula, excepto al comienzo de la cadena.

Parece que estás haciendo esto más complicado de lo necesario. Para camelCase , la ubicación dividida es simplemente en cualquier lugar donde una letra mayúscula sigue inmediatamente a una letra minúscula:

(?<=[az])(?=[AZ])

Así es como esta expresión regular divide sus datos de ejemplo:

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

La única diferencia con el resultado deseado es con el eclipseRCPExt , que, según mi eclipseRCPExt , está dividido correctamente aquí.

Adición - Versión mejorada

Nota: Esta respuesta recibió recientemente un voto positivo y me di cuenta de que hay una mejor manera ...

Al agregar una segunda alternativa a la expresión regular anterior, todos los casos de prueba de OP se dividen correctamente.

(?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])

Así es como la expresión regular mejorada divide los datos de ejemplo:

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

Editar: 20130824 Se agregó una versión mejorada para manejar RCPExt -> RCP / Ext .

Otra solución sería usar un método dedicado en commons-lang : StringUtils # splitByCharacterTypeCamelCase

No pude lograr que la solución de Aix funcionara (y tampoco funciona en RegExr), así que creé la mía que probé y que parece hacer exactamente lo que está buscando:

 ((^[az]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)))) 

y aquí hay un ejemplo de su uso:

 ; Regex Breakdown: This will match against each word in Camel and Pascal case strings, while properly handling acrynoms. ; (^[az]+) Match against any lower-case letters at the start of the string. ; ([AZ]{1}[az]+) Match against Title case words (one upper case followed by lower case letters). ; ([AZ]+(?=([AZ][az])|($))) 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, "((^[az]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($))))", "$1 ") newString := Trim(newString) 

Aquí estoy separando cada palabra con un espacio, así que aquí hay algunos ejemplos de cómo se transforma la cadena:

  • ThisIsATitleCASEString => This Is A Title CASE String
  • andThisOneIsCamelCASE => y This One Is Camel CASE

Esta solución de arriba hace lo que pide la publicación original, pero también necesitaba una expresión regular para buscar cadenas de camello y pascal que incluyeran números, así que también se me ocurrió esta variación para incluir números:

 ((^[az]+)|([0-9]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)|([0-9])))) 

y un ejemplo de usarlo:

 ; Regex Breakdown: This will match against each word in Camel and Pascal case strings, while properly handling acrynoms and including numbers. ; (^[az]+) 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). ; ([AZ]{1}[az]+) Match against Title case words (one upper case followed by lower case letters). ; ([AZ]+(?=([AZ][az])|($)|([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, "((^[az]+)|([0-9]+)|([AZ]{1}[az]+)|([AZ]+(?=([AZ][az])|($)|([0-9]))))", "$1 ") newString := Trim(newString) 

Y aquí hay algunos ejemplos de cómo una cadena con números se transforma con esta expresión regular:

  • myVariable123 => mi Variable 123
  • my2Variables => mis 2 variables
  • The3rdVariableIsHere => The 3 rdVariable Is Here
  • 12345NumsAtTheStartIncludedToo => 12345 Nums Al inicio incluido también

Para manejar más letras que solo AZ :

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

Ya sea:

  • Dividir después de cualquier letra minúscula, que es seguido por letra mayúscula.

Por ejemplo, parseXML -> parse , XML .

o

  • Dividir después de cualquier letra, que es seguido por letra mayúscula y letra minúscula.

Por ejemplo, XMLParser -> XML , Parser .


En forma más legible:

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

Breve

Ambas respuestas principales aquí proporcionan código utilizando lookbehinds positivo, que no es compatible con todos los sabores de regex. La siguiente expresión regular capturará tanto PascalCase como camelCase y se puede usar en múltiples idiomas.

Nota: Me doy cuenta de que esta pregunta es sobre Java, sin embargo, también veo múltiples menciones de esta publicación en otras preguntas etiquetadas para diferentes idiomas, así como algunos comentarios sobre esta pregunta para el mismo.

Código

Ver esta expresión regular en uso aquí

 ([AZ]+|[AZ]?[az]+)(?=[AZ]|\b) 

Resultados

Entrada de muestra

 eclipseRCPExt SomethingIsWrittenHere TEXTIsWrittenHERE VALUE loremIpsum 

Muestra de salida

 eclipse RCP Ext Something Is Written Here TEXT Is Written HERE VALUE lorem Ipsum 

Explicación

  • Coincide con uno o más caracteres alfabéticos en mayúsculas [AZ]+
  • ¿O coincide con cero o un carácter alfabético en mayúsculas [AZ]? , seguido de uno o más caracteres alfabéticos en minúscula [az]+
  • Asegúrese de que lo que sigue sea un carácter alfabético en mayúscula [AZ] o un carácter de límite de palabra \b

Puedes usar la expresión a continuación para Java:

 (?<=[az])(?=[AZ])|(?<=[AZ])(?=[AZ][az])|(?=[AZ][az])|(?<=\\d)(?=\\D)|(?=\\d)(?<=\\D) 

En lugar de buscar separadores que no están allí , también podría considerar encontrar los componentes del nombre (ciertamente están ahí):

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

Esto produce [eclipse, 福福, RCP, Ext] . La conversión a una matriz es, por supuesto, simple.

    Intereting Posts