¿Hay alguna manera elegante de analizar una palabra y agregar espacios antes de letras mayúsculas?

necesito analizar algunos datos y quiero convertir

AutomaticTrackingSystem 

a

 Automatic Tracking System 

esencialmente colocando un espacio antes de cualquier letra mayúscula (además del primero, por supuesto)

Sin regex puedes hacer algo como (o tal vez algo más conciso usando LINQ):

(Nota: no hay verificación de errores, debe agregarlo)

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SO { class Program { static void Main(string[] args) { String test = "AStringInCamelCase"; StringBuilder sb = new StringBuilder(); foreach (char c in test) { if (Char.IsUpper(c)) { sb.Append(" "); } sb.Append(c); } if (test != null && test.Length > 0 && Char.IsUpper(test[0])) { sb.Remove(0, 1); } String result = sb.ToString(); Console.WriteLine(result); } } } 

esto da la salida

 A String In Camel Case 

Puede usar miradas, por ejemplo:

 string[] tests = { "AutomaticTrackingSystem", "XMLEditor", }; Regex r = new Regex(@"(?!^)(?=[AZ])"); foreach (string test in tests) { Console.WriteLine(r.Replace(test, " ")); } 

Esto imprime ( como se ve en ideone.com ):

 Automatic Tracking System XML Editor 

La expresión regular (?!^)(?=[AZ]) consta de dos afirmaciones:

  • (?!^) – es decir, no estamos al comienzo de la cadena
  • (?=[AZ]) – es decir, estamos justo antes de una letra mayúscula

Preguntas relacionadas

  • ¿Cómo convierto CamelCase en nombres legibles para humanos en Java?
  • ¿Cómo funciona la expresión regular (?<=#)[^#]+(?=#) ?

Referencias

  • regular-expressions.info/Lookarounds

Dividir la diferencia

Aquí es donde el uso de aserciones realmente hace una diferencia, cuando tiene varias reglas diferentes, y / o desea Split lugar de Replace . Este ejemplo combina ambos:

 string[] tests = { "AutomaticTrackingSystem", "XMLEditor", "AnXMLAndXSLT2.0Tool", }; Regex r = new Regex( @" (?<=[AZ])(?=[AZ][az]) # UC before me, UC lc after me | (?<=[^AZ])(?=[AZ]) # Not UC before me, UC after me | (?<=[A-Za-z])(?=[^A-Za-z]) # Letter before me, non letter after me ", RegexOptions.IgnorePatternWhitespace ); foreach (string test in tests) { foreach (string part in r.Split(test)) { Console.Write("[" + part + "]"); } Console.WriteLine(); } 

Esto imprime ( como se ve en ideone.com ):

 [Automatic][Tracking][System] [XML][Editor] [An][XML][And][XSLT][2.0][Tool] 

Preguntas relacionadas

  • La división de Java está comiendo mis personajes.
    • Tiene muchos ejemplos de división en aserciones de coincidencia de ancho cero

Acabo de escribir una función para hacer exactamente esto. 🙂

Reemplace ([az])([AZ]) con $1 $2 (o \1 \2 en otros idiomas).

También tengo un reemplazo para ([AZ]+)([AZ][az]) también – esto convierte cosas como “NumberOfABCDThings” en “Number Of ABCD Things”

Entonces en C # esto se vería así:

 Regex r1 = new Regex(@"([az])([AZ])"); Regex r2 = new Regex(@"([AZ]+)([AZ][az])"); NewString = r1.Replace( InputString , "$1 $2"); NewString = r2.Replace( NewString , "$1 $2"); 

(Aunque posiblemente haya una forma más precisa de escribir eso)

Si pudiera tener signos de puntuación o números, supongo que podría intentar ([^AZ])([AZ]) para la primera coincidencia.

Hmmm, otra forma de escribir esas expresiones regulares, utilizando lookbehind y lookahead, es simplemente hacer coincidir la posición e insertar un espacio, es decir (?<=[az])(?=[AZ]) y (?<=[AZ]+)(?=[AZ][az]) y en ambos casos sustitúyalo por "" - no estoy seguro de si puede haber ventajas en ese método, pero es una forma interesante. :)

Aparentemente, hay una opción para regex inverso 🙂 Ahora podemos eliminar la inversión de cadena, aquí hay otra forma de hacerlo:

 using System; using System.Linq; using System.Text.RegularExpressions; class MainClass { public static void Main (string[] args) { Regex ry = new Regex (@"([AZ][az]+|[AZ]+[AZ]|[AZ]|[^A-Za-z]+[^A-Za-z])", RegexOptions.RightToLeft); string[] tests = { "AutomaticTrackingSystem", "XMLEditor", "AnXMLAndXSLT2.0Tool", "NumberOfABCDThings", "AGoodMan", "CodeOfAGoodMan" }; foreach(string t in tests) { Console.WriteLine("\n\n{0} -- {1}", t, ry.Replace(t, " $1")); } } } 

Salida:

 AutomaticTrackingSystem -- Automatic Tracking System XMLEditor -- XML Editor AnXMLAndXSLT2.0Tool -- An XML And XSLT 2.0 Tool NumberOfABCDThings -- Number Of ABCD Things AGoodMan -- A Good Man CodeOfAGoodMan -- Code Of A Good Man 

Si intenta mantener acrónimos intactos, reemplace “([^ AZ]) ([AZ])” con “\ 1 \ 2”, de lo contrario reemplace “(.) ([AZ])” con “\ 1 \ 2”.

Prueba esto:

 using System; using System.Linq; using System.Text.RegularExpressions; class MainClass { public static void Main (string[] args) { var rx = new Regex (@"([az]+[AZ]|[AZ][AZ]+|[AZ]|[^A-Za-z][^A-Za-z]+)"); string[] tests = { "AutomaticTrackingSystem", "XMLEditor", "AnXMLAndXSLT2.0Tool", "NumberOfABCDThings", "AGoodMan", "CodeOfAGoodMan" }; foreach(string t in tests) { string y = Reverse(t); string x = Reverse( rx.Replace(y, @" $1") ); Console.WriteLine("\n\n{0} -- {1}",y,x); } } static string Reverse(string s) { var ca = s.ToCharArray(); Array.Reverse(ca); string t = new string(ca); return t; } } 

Salida:

 metsySgnikcarTcitamotuA -- Automatic Tracking System rotidELMX -- XML Editor looT0.2TLSXdnALMXnA -- An XML And XSLT 2.0 Tool sgnihTDCBAfOrebmuN -- Number Of ABCD Things naMdooGA -- A Good Man naMdooGAfOedoC -- Code Of A Good Man 

Funciona escaneando la cadena hacia atrás y convirtiendo la letra mayúscula en el terminador. Deseando que haya un parámetro para RegEx para escanear la cadena hacia atrás, por lo que la inversión de cadena por separado anterior ya no será necesaria 🙂

Solo use este linq one-liner: (funciona perfectamente para mí)

 public static string SpaceCamelCase(string input) { return input.Aggregate(string.Empty, (old, x) => $"{old}{(char.IsUpper(x) ? " " : "")}{x}").TrimStart(' '); }