Regex para convertir CamelCase a camel_case en java

86

Entiendo por qué la salida deseada no se le da el uso de expresiones regulares para convertir una cadena como FooBara Foo_Barque da lugar Foo_Bar_. Podría haber hecho algo con String.substring substring(0, string.length() - 2)o simplemente reemplazar el último carácter, pero creo que hay una mejor solución para tal escenario.

Aquí está el código:

String regex = "([A-Z][a-z]+)";
String replacement = "$1_";

"CamelCaseToSomethingElse".replaceAll(regex, replacement); 

/*
outputs: Camel_Case_To_Something_Else_
desired output: Camel_Case_To_Something_Else
*/

Pregunta: ¿Busca una forma más ordenada de obtener el resultado deseado?

ajmartin
fuente
Esta pregunta es similar a stackoverflow.com/questions/4886091/…
Paul Vargas

Respuestas:

168

Ver esta pregunta y CaseFormatde guayaba

en tu caso, algo como:

CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "SomeInput");
mkobit
fuente
@eliocs la pregunta no fue etiquetada como androide y "forma más ordenada" .. Gracias por el
2
El enlace de CaseFormat está desconectado. El reemplazo está aquí
Anticom
66

enlazar la minúscula y la mayúscula como dos grupos, estará bien

public  class Main
{
    public static void main(String args[])
    {
        String regex = "([a-z])([A-Z]+)";
        String replacement = "$1_$2";
        System.out.println("CamelCaseToSomethingElse"
                           .replaceAll(regex, replacement)
                           .toLowerCase());
    }
}
clevertension
fuente
2
Nota: Si se permiten palabras de una sola letra en la cadena de entrada, por ejemplo, "thisIsATest", el código anterior imprimirá "this_is_atest". Guayaba, en la respuesta aceptada, da como resultado "this_is_a_test".
DtotheK
Éste no va a funcionar en un nombre de comenzar con las tapas, por ejemplo: IBMIsMyCompany.
User3301
37

Puede utilizar el siguiente fragmento de código:

String replaceAll = key.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase();
Sandeep Vaid
fuente
¿Qué pasa si mi cadena contiene un número? Mode3 termina como mode3, mientras que yo querría mode_3.
Mike Stoddart
No convierte el caso de camello como MyUUIDsubrayado correctamente, lo tengo my_uu_id.
User3301
6

No puedo proporcionar RegEx, sería increíblemente complejo de todos modos.

Pruebe esta función con reconocimiento automático de siglas.

Desafortunadamente, Guava lib no detecta automáticamente los acrónimos en mayúsculas, por lo que "bigCAT" se convertiría en "BIG_C_A_T"

/**
 * Convert to UPPER_UNDERSCORE format detecting upper case acronyms
 */
private String upperUnderscoreWithAcronyms(String name) {
    StringBuffer result = new StringBuffer();
    boolean begin = true;
    boolean lastUppercase = false;
    for( int i=0; i < name.length(); i++ ) {
        char ch = name.charAt(i);
        if( Character.isUpperCase(ch) ) {
            // is start?
            if( begin ) {
                result.append(ch);
            } else {
                if( lastUppercase ) {
                    // test if end of acronym
                    if( i+1<name.length() ) {
                        char next = name.charAt(i+1);
                        if( Character.isUpperCase(next) ) {
                            // acronym continues
                            result.append(ch);
                        } else {
                            // end of acronym
                            result.append('_').append(ch);
                        }
                    } else {
                        // acronym continues
                        result.append(ch);
                    }
                } else {
                    // last was lowercase, insert _
                    result.append('_').append(ch);
                }
            }
            lastUppercase=true;
        } else {
            result.append(Character.toUpperCase(ch));
            lastUppercase=false;
        }
        begin=false;
    }
    return result.toString();
}
radzimir
fuente
4

¿Por qué no simplemente hacer coincidir el carácter anterior como un no comienzo de línea $?

String text = "CamelCaseToSomethingElse";
System.out.println(text.replaceAll("([^_A-Z])([A-Z])", "$1_$2"));

Tenga en cuenta que esta versión es segura para realizar en algo que ya está en caja de camello.

Brett Ryan
fuente
¿Está intentando utilizar ^y $como anclajes? Porque sus significados cambian cuando los colocas en una clase de personaje. [^$_A-Z]coincide con cualquier carácter que no sea $, _o una letra mayúscula, y no creo que eso sea lo que quisiste decir.
Alan Moore
Sin la intención de ser anclas, estoy intentando no coincidir con el carácter superior, $se agregó por error ya que es una técnica que uso en los nombres de clase.
Brett Ryan
3

Agregue una aserción de anticipación de ancho cero.

http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html

Lea la documentación para (?=X) etc.

Personalmente, dividiría la cadena y luego la recombinaría. Esto puede ser incluso más rápido cuando se hace correctamente, y hace que el código sea mucho más fácil de entender que la magia de las expresiones regulares. No me malinterpretes: me encantan las expresiones regulares. Pero esta no es realmente una expresión regular ordenada, ni esta transformación una tarea clásica de . ¿Después de todo parece que también quieres hacerlo en minúsculas?

Un truco feo, pero rápida sería la de sustituir (.)([A-Z]+)con $1_$2y minúsculas toda la cadena después (a menos que puede hacer las expresiones regulares de Perl estilo extrended, donde se puede minúsculas la sustitución directa!). Aún así, considero dividir en la transición de abajo hacia arriba, luego transformar y luego unir como la forma adecuada y más legible de hacer esto.

Ha QUIT - Anony-Mousse
fuente
Sí, eventualmente también me gustaría que estuviera en minúsculas.
ajmartin
Así que lo dividiría en trozos que coincidieran [A-Z][a-z]*, en minúscula la primera letra y volver a unirlos. O el truco de reemplazo + minúsculas que acabo de agregar a la respuesta principal.
Ha QUIT - Anony-Mousse
2
public class ReplaceFromCameltoSnake {
    public static void main(String args[]){
        String s1=" totalAmountWithoutDiscount";  
        String replaceString=s1.replaceAll("([A-Z]+)","\\_$1").toLowerCase(); 
        System.out.println(replaceString);  
    }
}
abinash sahu
fuente
$ 1-se usa para hacer grupo
abinash sahu
2

No estoy seguro de que sea posible tener algo realmente sólido con expresiones regulares puras. Especialmente para apoyar acrónimos.

Hice una pequeña función, inspirada en la respuesta de @radzimir, que admite acrónimos y no caracteres alfabéticos:

De https://gist.github.com/ebuildy/cf46a09b1ac43eea17c7621b7617ebcd :

private static String snakeCaseFormat(String name) {
    final StringBuilder result = new StringBuilder();

    boolean lastUppercase = false;

    for (int i = 0; i < name.length(); i++) {
        char ch = name.charAt(i);
        char lastEntry = i == 0 ? 'X' : result.charAt(result.length() - 1);
        if (ch == ' ' || ch == '_' || ch == '-' || ch == '.') {
            lastUppercase = false;

            if (lastEntry == '_') {
                continue;
            } else {
                ch = '_';
            }
        } else if (Character.isUpperCase(ch)) {
            ch = Character.toLowerCase(ch);
            // is start?
            if (i > 0) {
                if (lastUppercase) {
                    // test if end of acronym
                    if (i + 1 < name.length()) {
                        char next = name.charAt(i + 1);
                        if (!Character.isUpperCase(next) && Character.isAlphabetic(next)) {
                            // end of acronym
                            if (lastEntry != '_') {
                                result.append('_');
                            }
                        }
                    }
                } else {
                    // last was lowercase, insert _
                    if (lastEntry != '_') {
                        result.append('_');
                    }
                }
            }
            lastUppercase = true;
        } else {
            lastUppercase = false;
        }

        result.append(ch);
    }
    return result.toString();
}
Thomas Decaux
fuente
1
Esta es una respuesta de calidad, maneja la mayoría de casos extremos.
User3301
1
([A-Z][a-z\d]+)(?=([A-Z][a-z\d]+))

Debe buscar una letra mayúscula seguida de letras minúsculas. La búsqueda anticipada positiva buscará otra palabra que comience con una letra mayúscula seguida de letras minúsculas, pero NO la incluirá en la coincidencia.

Mira aquí: http://regexr.com?30ooo

Jack
fuente
0

Tuve que implementar esto para convertir algunas claves en formato camel a minúsculas con guiones bajos. La expresión regular que se me ocurrió es:

(?<!^|_|[A-Z])([A-Z])

En inglés, significa letra mayúscula que no está precedida por el comienzo de la cadena, un guión bajo u otra letra mayúscula .

En los ejemplos a continuación, los caracteres en negrita son los que deberían producir una coincidencia usando la expresión regular mencionada anteriormente:

  • Camel C ase T O S omething E lse
  • camello C ase T O S omething E lse
  • camel_case_to_something_else
  • Camel_Case_To_Something_Else
  • CAMEL_CASE_TO_SOMETHING_ELSE

Observe que la expresión no afecta a las cadenas que ya están en formato de minúsculas + subrayado.

El patrón de reemplazo sería:

_l$1

Lo que significa minúscula del primer grupo de captura, siendo el primer grupo de captura la letra mayúscula. También puede minúscula toda la cadena después para normalizar las dos últimas muestras de la lista anterior.

Argenkiwi
fuente