Encontré una expresión regular brillante para extraer la parte de una expresión camelCase o TitleCase.
(?<!^)(?=[A-Z])
Funciona como se esperaba:
- valor -> valor
- camelValue -> camel / Value
- TitleValue -> Título / Valor
Por ejemplo con Java:
String s = "loremIpsum";
words = s.split("(?<!^)(?=[A-Z])");
//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, dados n caracteres en mayúscula:
- si los n caracteres van seguidos de minúsculas, los grupos deben ser: (n-1 caracteres) / (n-ésimo carácter + caracteres inferiores)
- si los n caracteres están al final, el grupo debería ser: (n caracteres).
¿Alguna idea de cómo mejorar esta expresión regular?
java
regex
camelcasing
title-case
Jmini
fuente
fuente
^
y otro caso condicional para letras mayúsculas en la búsqueda hacia atrás negativa. No lo he probado con seguridad, pero creo que esa sería su mejor opción para solucionar el problema.Respuestas:
La siguiente expresión regular funciona para todos los ejemplos anteriores:
public static void main(String[] args) { for (String w : "camelValue".split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")) { System.out.println(w); } }
Funciona obligando a la búsqueda hacia atrás negativa no solo para ignorar las coincidencias al comienzo de la cadena, sino también para ignorar las coincidencias en las que una letra mayúscula está 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 dividirse entre "RPC" y "Ext". Este es el propósito de la segunda cláusula:
(?<!^)(?=[A-Z][a-z]
. 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.fuente
Parece que está haciendo esto más complicado de lo necesario. Para camelCase , la ubicación de división es simplemente cualquier lugar donde una letra mayúscula siga inmediatamente a una letra minúscula:
(?<=[a-z])(?=[A-Z])
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 yo diría que está dividido correctamente aquí.Anexo - Versión mejorada
Nota: Esta respuesta recientemente recibió 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 del OP se dividen correctamente.
(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])
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 el
RCPExt -> RCP / Ext
estuche.fuente
(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[0-9])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=[0-9])
Otra solución sería utilizar un método dedicado en commons-lang : StringUtils # splitByCharacterTypeCamelCase
fuente
No pude hacer que la solución de aix funcionara (y tampoco funciona en RegExr), así que se me ocurrió la mía propia que probé y parece hacer exactamente lo que estás buscando:
((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))
y aquí hay un ejemplo de cómo usarlo:
; 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)
Aquí estoy separando cada palabra con un espacio, así que aquí hay algunos ejemplos de cómo se transforma la cadena:
Esta solución anterior hace lo que pide la publicación original, pero también necesitaba una expresión regular para encontrar cadenas de camello y pascal que incluían números, por lo que también se me ocurrió esta variación para incluir números:
((^[a-z]+)|([0-9]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($)|([0-9]))))
y un ejemplo de su uso:
; 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)
Y aquí hay algunos ejemplos de cómo una cadena con números se transforma con esta expresión regular:
fuente
(^[a-z]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$))
para el primero y(^[a-z]+|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|$|[0-9]))
para el segundo. La mayoría externa también se puede eliminar, pero la sintaxis para referirse a la coincidencia completa no es portátil entre idiomas ($0
y$&
hay 2 posibilidades).([A-Z]?[a-z]+)|([A-Z]+(?=[A-Z][a-z]))
Para manejar más letras que solo
A-Z
:s.split("(?<=\\p{Ll})(?=\\p{Lu})|(?<=\\p{L})(?=\\p{Lu}\\p{Ll})");
Ya sea:
Por ejemplo
parseXML
->parse
,XML
.o
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")); } }
fuente
Breve
Ambas respuestas principales aquí proporcionan código usando búsquedas retrospectivas positivas, que no es compatible con todos los sabores de expresiones regulares. La expresión regular a continuación se captura tanto
PascalCase
ycamelCase
y se puede utilizar en múltiples idiomas.Nota: Me doy cuenta de que esta pregunta está relacionada con Java, sin embargo, también veo varias menciones de esta publicación en otras preguntas etiquetadas para diferentes idiomas, así como algunos comentarios sobre esta pregunta para el mismo.
Código
Vea esta expresión regular en uso aquí
Resultados
Entrada de muestra
Salida de muestra
Explicación
[A-Z]+
[A-Z]?
, seguido de uno o más caracteres alfabéticos en minúscula[a-z]+
[A-Z]
o un carácter de límite de palabra\b
fuente
Puede utilizar StringUtils. splitByCharacterTypeCamelCase ("loremIpsum") de Apache Commons Lang.
fuente
Puede utilizar la siguiente expresión para Java:
fuente
En lugar de buscar separadores que no están allí , también podría considerar buscar los componentes del nombre (ciertamente están allí):
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);
Esto salidas
[eclipse, 福福, RCP, Ext]
. La conversión a una matriz es, por supuesto, simple.fuente
Puedo confirmar que la cadena
([A-Z]+|[A-Z]?[a-z]+)(?=[A-Z]|\b)
de expresiones regulares dada por ctwheels, arriba, funciona con el sabor de expresiones regulares de Microsoft.También me gustaría sugerir la siguiente alternativa, basada en expresiones regulares ctwheels', que se ocupa de caracteres numéricos:
([A-Z0-9]+|[A-Z]?[a-z]+)(?=[A-Z0-9]|\b)
.Esta capaz de dividir cadenas como:
a
fuente
Una solución de JavaScript
/** * 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('!'); };
fuente