Dividiendo CamelCase

80

Todo esto es asp.net c #.

Tengo una enumeración

public enum ControlSelectionType 
{
    NotApplicable = 1,
    SingleSelectRadioButtons = 2,
    SingleSelectDropDownList = 3,
    MultiSelectCheckBox = 4,
    MultiSelectListBox = 5
}

El valor numérico de esto se almacena en mi base de datos. Muestro este valor en una cuadrícula de datos.

<asp:boundcolumn datafield="ControlSelectionTypeId" headertext="Control Type"></asp:boundcolumn>

La ID no significa nada para un usuario, así que cambié la columna vinculada a una columna de plantilla con lo siguiente.

<asp:TemplateColumn>
    <ItemTemplate>
        <%# Enum.Parse(typeof(ControlSelectionType), DataBinder.Eval(Container.DataItem, "ControlSelectionTypeId").ToString()).ToString()%>
    </ItemTemplate>
</asp:TemplateColumn>

Esto es mucho mejor ... Sin embargo, sería genial si hubiera una función simple que pudiera poner alrededor del Enum para dividirlo por el caso de Camel para que las palabras se ajusten bien en la cuadrícula de datos.

Nota: Soy plenamente consciente de que existen mejores formas de hacer todo esto. Esta pantalla se usa exclusivamente internamente y solo quiero un truco rápido para mostrarla un poco mejor.

Día de Robin
fuente

Respuestas:

76

De hecho, una expresión regular / reemplazo es el camino a seguir como se describe en la otra respuesta, sin embargo, esto también podría ser útil para usted si quisiera ir en una dirección diferente

    using System.ComponentModel;
    using System.Reflection;

...

    public static string GetDescription(System.Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());
        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Length > 0)
            return attributes[0].Description;
        else
            return value.ToString();
    }

esto le permitirá definir sus enumeraciones como

public enum ControlSelectionType 
{
    [Description("Not Applicable")]
    NotApplicable = 1,
    [Description("Single Select Radio Buttons")]
    SingleSelectRadioButtons = 2,
    [Description("Completely Different Display Text")]
    SingleSelectDropDownList = 3,
}

Tomado de

http://www.codeguru.com/forum/archive/index.php/t-412868.html

Eoin Campbell
fuente
+1 Excelente respuesta, probablemente usaré la respuesta de expresión regular ya que es más rápida y fácil, sin embargo, esta es una solución mucho mejor y, por lo tanto, se acepta.
Día de Robin
He visto muchas respuestas sobre los atributos de enumeración, ¡pero esta parece la más limpia!
nawfal
2
inteligente, pero es mucho más trabajo que una simple función de expresión regular estática. No estoy seguro de estar de acuerdo con "más limpio", "más rápido" o "más fácil". Cleverist? sin lugar a duda.
Todd Painton
1
Esto solo funciona si tiene control sobre esa enumeración, pero preferiría tener un control total sobre el código de visualización en lugar de asumir que los valores de la enumeración se escribirán correctamente.
Berin Loritsch
131

Solía:

    public static string SplitCamelCase(string input)
    {
        return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
    }

Tomado de http://weblogs.asp.net/jgalloway/archive/2005/09/27/426087.aspx

vb.net:

Public Shared Function SplitCamelCase(ByVal input As String) As String
    Return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim()
End Function
Tillito
fuente
Una forma sencilla de realizar la parte de la caja camel ... el otro enfoque es mejor para una mayor personalización. Gracias @Tillito
rolivares
62
Modifiqué un poco la expresión regular a "(? <= [Az]) ([AZ])". Esto da como resultado que ProductID se convierta en Product ID en lugar de Product I D. Especifica que la letra mayúscula debe ir precedida de una letra minúscula (observe el operador de búsqueda posterior). También elimina la necesidad de recortar.
Ben Mills
7
Hola Ben, ¿por qué no pones eso como respuesta? ¡Tener una expresión regular diferente (y más sofisticada) constituye un nuevo compañero de respuesta!
Nicholas Petersen
3
Como complemento al útil comentario de Ben, debo mencionar que también puedes dividir algo como "HELLOWorld" en "HELLO World" usando la expresión regular: (? <= [AZ]) ([AZ]) (? = [Az] )
giangurgolo
6
Combiné las expresiones de Ben Mills y giangurgolo: Regex.Replace (input, @ "((? <= [AZ]) ([AZ]) (? = [Az])) | ((? <= [Az] +) ([AZ])) ", @" $ 0 ", RegexOptions.Compiled) .Trim ();
IceWarrior353
23

Esta expresión regular (^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)se puede utilizar para extraer todas las palabras del nombre camelCase o PascalCase. También funciona con abreviaturas en cualquier lugar dentro del nombre.

  • MyHTTPServercontendrá exactamente 3 partidos: My, HTTP,Server
  • myNewXMLFilecontendrá 4 partidos: my, New, XML,File

Luego puede unirlos en una sola cadena usando string.Join.

string name = "myNewUIControl";
string[] words = Regex.Matches(name, "(^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)")
    .OfType<Match>()
    .Select(m => m.Value)
    .ToArray();
string result = string.Join(" ", words);
Ghost4Man
fuente
2
Me gusta. Sin embargo, vivimos en tiempos modernos. Por lo tanto: @"(^\p{Ll}+|\p{Lu}+(?!\p{Ll})|\p{Lu}\p{Ll}+)"también es importante tener en cuenta que esto no hará números en absoluto, aunque sean válidos en los identificadores.
Daniel B
¡Sencillo pero perfecto!
JC Raja
1
Necesitaba un ligero cambio a "(^ [az] + | [AZ] + (?! [Az]) | [AZ] [az] + | [0-9 \. *] + | [Az] +)" "ITPortfolio12v2.0.13BMS" da como resultado "IT Portfolio 12 v 2.0.13 BMS" para alguien
Joe Johnston
15

Si C # 3.0 es una opción, puede usar el siguiente resumen para hacer el trabajo:


Regex.Matches(YOUR_ENUM_VALUE_NAME, "[A-Z][a-z]+").OfType<Match>().Select(match => match.Value).Aggregate((acc, b) => acc + " " + b).TrimStart(' ');
em70
fuente
1
Esto no maneja Acroynms en el texto como AMACharter, devuelve 'Charter' no 'AMA Charter'.
Adam Mills
Aunque las modificaciones para manejar tal caso serían fáciles (piense en anteponer algo como ([AZ] *) y modificar ligeramente el código), por lo que recuerdo de las pautas de codificación de Microsoft, se desaconseja el uso de tales acrónimos en mayúsculas, y en acrónimos en mayúscula Se deben evitar los acrónimos generales si tienen más de 2 letras.
em70
1
No me funciona. "CamelCase" se convierte en "Camel" y no en "Camel Case".
Tillito
15

La respuesta de Tillito no maneja bien las cadenas que ya contienen espacios, o acrónimos. Esto lo arregla:

public static string SplitCamelCase(string input)
{
    return Regex.Replace(input, "(?<=[a-z])([A-Z])", " $1", RegexOptions.Compiled);
}
Petrucio
fuente
Descargo de responsabilidad: el crédito es para Tillito, proveedor de la respuesta original, y Ben Mills, quien sugirió la mejora en un comentario. Dado que es una respuesta mejorada y ninguno de ellos la publicó o editó, merece una respuesta por separado. Me habría ahorrado media hora de depuración si no estuviera enterrado debajo de los comentarios para empezar.
Petrucio
2
Falla con el caso de prueba simple "SMSMessage" (esperado: "Mensaje SMS", real: "SMSMessage").
Ian Kemp
10

Aquí hay un método de extensión que maneja números y múltiples caracteres en mayúscula con cordura, y también permite acrónimos específicos en mayúsculas en la cadena final:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Web.Configuration;

namespace System
{
    /// <summary>
    /// Extension methods for the string data type
    /// </summary>
    public static class ConventionBasedFormattingExtensions
    {
        /// <summary>
        /// Turn CamelCaseText into Camel Case Text.
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <remarks>Use AppSettings["SplitCamelCase_AllCapsWords"] to specify a comma-delimited list of words that should be ALL CAPS after split</remarks>
        /// <example>
        /// wordWordIDWord1WordWORDWord32Word2
        /// Word Word ID Word 1 Word WORD Word 32 Word 2
        /// 
        /// wordWordIDWord1WordWORDWord32WordID2ID
        /// Word Word ID Word 1 Word WORD Word 32 Word ID 2 ID
        /// 
        /// WordWordIDWord1WordWORDWord32Word2Aa
        /// Word Word ID Word 1 Word WORD Word 32 Word 2 Aa
        /// 
        /// wordWordIDWord1WordWORDWord32Word2A
        /// Word Word ID Word 1 Word WORD Word 32 Word 2 A
        /// </example>
        public static string SplitCamelCase(this string input)
        {
            if (input == null) return null;
            if (string.IsNullOrWhiteSpace(input)) return "";

            var separated = input;

            separated = SplitCamelCaseRegex.Replace(separated, @" $1").Trim();

            //Set ALL CAPS words
            if (_SplitCamelCase_AllCapsWords.Any())
                foreach (var word in _SplitCamelCase_AllCapsWords)
                    separated = SplitCamelCase_AllCapsWords_Regexes[word].Replace(separated, word.ToUpper());

            //Capitalize first letter
            var firstChar = separated.First(); //NullOrWhiteSpace handled earlier
            if (char.IsLower(firstChar))
                separated = char.ToUpper(firstChar) + separated.Substring(1);

            return separated;
        }

        private static readonly Regex SplitCamelCaseRegex = new Regex(@"
            (
                (?<=[a-z])[A-Z0-9] (?# lower-to-other boundaries )
                |
                (?<=[0-9])[a-zA-Z] (?# number-to-other boundaries )
                |
                (?<=[A-Z])[0-9] (?# cap-to-number boundaries; handles a specific issue with the next condition )
                |
                (?<=[A-Z])[A-Z](?=[a-z]) (?# handles longer strings of caps like ID or CMS by splitting off the last capital )
            )"
            , RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace
        );

        private static readonly string[] _SplitCamelCase_AllCapsWords =
            (WebConfigurationManager.AppSettings["SplitCamelCase_AllCapsWords"] ?? "")
                .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(a => a.ToLowerInvariant().Trim())
                .ToArray()
                ;

        private static Dictionary<string, Regex> _SplitCamelCase_AllCapsWords_Regexes;
        private static Dictionary<string, Regex> SplitCamelCase_AllCapsWords_Regexes
        {
            get
            {
                if (_SplitCamelCase_AllCapsWords_Regexes == null)
                {
                    _SplitCamelCase_AllCapsWords_Regexes = new Dictionary<string,Regex>();
                    foreach(var word in _SplitCamelCase_AllCapsWords)
                        _SplitCamelCase_AllCapsWords_Regexes.Add(word, new Regex(@"\b" + word + @"\b", RegexOptions.Compiled | RegexOptions.IgnoreCase));
                }

                return _SplitCamelCase_AllCapsWords_Regexes;
            }
        }
    }
}
Jerph
fuente
6

Puede usar métodos de extensión de C #

        public static string SpacesFromCamel(this string value)
        {
            if (value.Length > 0)
            {
                var result = new List<char>();
                char[] array = value.ToCharArray();
                foreach (var item in array)
                {
                    if (char.IsUpper(item) && result.Count > 0)
                    {
                        result.Add(' ');
                    }
                    result.Add(item);
                }

                return new string(result.ToArray());
            }
            return value;
        }

Entonces puedes usarlo como

var result = "TestString".SpacesFromCamel();

El resultado será

Cadena de prueba

Sameera R.
fuente
1
Esto en realidad crea un espacio al principio, corrigió el código
Martin Zikmund
3

También tengo una enumque tuve que separar. En mi caso, este método resolvió el problema-

string SeparateCamelCase(string str)
{
    for (int i = 1; i < str.Length; i++)
    {
        if (char.IsUpper(str[i]))
        {
            str = str.Insert(i, " ");
            i++;
        }
    }
    return str;
}
Islam ariful
fuente
2

Usando LINQ:

var chars = ControlSelectionType.NotApplicable.ToString().SelectMany((x, i) => i > 0 && char.IsUpper(x) ? new char[] { ' ', x } : new char[] { x });

Console.WriteLine(new string(chars.ToArray()));
Andy Rose
fuente
1
Debería volver a la codificación con C \ C ++: D - demasiado sucio para C #
datos
1
Bueno, dije que fue un truco rápido y sucio. Aquí hay una versión de LINQ más limpia.
Andy Rose
Esto no maneja Acroynms en el texto como AMACharter, devuelve 'AMA Charter' no 'AMA Charter
Adam Mills
2
public enum ControlSelectionType    
{   
    NotApplicable = 1,   
    SingleSelectRadioButtons = 2,   
    SingleSelectDropDownList = 3,   
    MultiSelectCheckBox = 4,   
    MultiSelectListBox = 5   
} 
public class NameValue
{
    public string Name { get; set; }
    public object Value { get; set; }
}    
public static List<NameValue> EnumToList<T>(bool camelcase)
        {
            var array = (T[])(Enum.GetValues(typeof(T)).Cast<T>()); 
            var array2 = Enum.GetNames(typeof(T)).ToArray<string>(); 
            List<NameValue> lst = null;
            for (int i = 0; i < array.Length; i++)
            {
                if (lst == null)
                    lst = new List<NameValue>();
                string name = "";
                if (camelcase)
                {
                    name = array2[i].CamelCaseFriendly();
                }
                else
                    name = array2[i];
                T value = array[i];
                lst.Add(new NameValue { Name = name, Value = value });
            }
            return lst;
        }
        public static string CamelCaseFriendly(this string pascalCaseString)
        {
            Regex r = new Regex("(?<=[a-z])(?<x>[A-Z])|(?<=.)(?<x>[A-Z])(?=[a-z])");
            return r.Replace(pascalCaseString, " ${x}");
        }

//In  your form 
protected void Button1_Click1(object sender, EventArgs e)
        {
            DropDownList1.DataSource = GeneralClass.EnumToList<ControlSelectionType  >(true); ;
            DropDownList1.DataTextField = "Name";
            DropDownList1.DataValueField = "Value";

            DropDownList1.DataBind();
        }
Kiarash
fuente
2

La solución de Eoin Campbell funciona bien, excepto si tiene un servicio web.

Debería hacer lo siguiente ya que el atributo de descripción no es serializable.

[DataContract]
public enum ControlSelectionType
{
    [EnumMember(Value = "Not Applicable")]
    NotApplicable = 1,
    [EnumMember(Value = "Single Select Radio Buttons")]
    SingleSelectRadioButtons = 2,
    [EnumMember(Value = "Completely Different Display Text")]
    SingleSelectDropDownList = 3,
}


public static string GetDescriptionFromEnumValue(Enum value)
{
    EnumMemberAttribute attribute = value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(typeof(EnumMemberAttribute), false)
        .SingleOrDefault() as EnumMemberAttribute;
    return attribute == null ? value.ToString() : attribute.Value;
}
Theo Koekemoer
fuente
2

Y si no te apetece usar expresiones regulares, prueba esto:

public static string SeperateByCamelCase(this string text, char splitChar = ' ') {

        var output = new StringBuilder();

        for (int i = 0; i < text.Length; i++)
        {
            var c = text[i];

            //if not the first and the char is upper
            if (i > 0 && char.IsUpper(c)) {

                var wasLastLower = char.IsLower(text[i - 1]);

                if (i + 1 < text.Length) //is there a next
                {
                    var isNextUpper = char.IsUpper(text[i + 1]);

                    if (!isNextUpper) //if next is not upper (start of a word).
                    {
                        output.Append(splitChar);
                    }
                    else if (wasLastLower) //last was lower but i'm upper and my next is an upper (start of an achromin). 'abcdHTTP' 'abcd HTTP'
                    {
                        output.Append(splitChar);
                    }
                }
                else
                {
                    //last letter - if its upper and the last letter was lower 'abcd' to 'abcd A'
                    if (wasLastLower)
                    {
                        output.Append(splitChar);
                    }
                }
            }

            output.Append(c);
        }


        return output.ToString();

    }

Pasa estas pruebas, no le gustan los números pero no los necesitaba.

    [TestMethod()]
    public void ToCamelCaseTest()
    {

        var testData = new string[] { "AAACamel", "AAA", "SplitThisByCamel", "AnA", "doesnothing", "a", "A", "aasdasdAAA" };
        var expectedData = new string[] { "AAA Camel", "AAA", "Split This By Camel", "An A", "doesnothing", "a", "A", "aasdasd AAA" };

        for (int i = 0; i < testData.Length; i++)
        {
            var actual = testData[i].SeperateByCamelCase();
            var expected = expectedData[i];
            Assert.AreEqual(actual, expected);
        }

    }
Tiene
fuente
2

#JustSayNoToRegex

Toma un identificador de C #, con uderscores y números, y lo convierte en una cadena separada por espacios.

public static class StringExtensions
{
    public static string SplitOnCase(this string identifier)
    {
        if (identifier == null || identifier.Length == 0) return string.Empty;
        var sb = new StringBuilder();

        if (identifier.Length == 1) sb.Append(char.ToUpperInvariant(identifier[0]));

        else if (identifier.Length == 2) sb.Append(char.ToUpperInvariant(identifier[0])).Append(identifier[1]);

        else {
            if (identifier[0] != '_') sb.Append(char.ToUpperInvariant(identifier[0]));
            for (int i = 1; i < identifier.Length; i++) {
                var current = identifier[i];
                var previous = identifier[i - 1];

                if (current == '_' && previous == '_') continue;

                else if (current == '_') {
                    sb.Append(' ');
                }

                else if (char.IsLetter(current) && previous == '_') {
                    sb.Append(char.ToUpperInvariant(current));
                }

                else if (char.IsDigit(current) && char.IsLetter(previous)) {
                    sb.Append(' ').Append(current);
                }

                else if (char.IsLetter(current) && char.IsDigit(previous)) {
                    sb.Append(' ').Append(char.ToUpperInvariant(current));
                }

                else if (char.IsUpper(current) && char.IsLower(previous) 
                    && (i < identifier.Length - 1 && char.IsUpper(identifier[i + 1]) || i == identifier.Length - 1)) {
                        sb.Append(' ').Append(current);
                }

                else if (char.IsUpper(current) && i < identifier.Length - 1 && char.IsLower(identifier[i + 1])) {
                    sb.Append(' ').Append(current);
                }

                else {
                    sb.Append(current);
                }
            }
        }
        return sb.ToString();
    }

}

Pruebas:

[TestFixture]
static class HelpersTests
{
    [Test]
    public static void Basic()
    {
        Assert.AreEqual("Foo", "foo".SplitOnCase());
        Assert.AreEqual("Foo", "_foo".SplitOnCase());
        Assert.AreEqual("Foo", "__foo".SplitOnCase());
        Assert.AreEqual("Foo", "___foo".SplitOnCase());
        Assert.AreEqual("Foo 2", "foo2".SplitOnCase());
        Assert.AreEqual("Foo 23", "foo23".SplitOnCase());
        Assert.AreEqual("Foo 23 A", "foo23A".SplitOnCase());
        Assert.AreEqual("Foo 23 Ab", "foo23Ab".SplitOnCase());
        Assert.AreEqual("Foo 23 Ab", "foo23_ab".SplitOnCase());
        Assert.AreEqual("Foo 23 Ab", "foo23___ab".SplitOnCase());
        Assert.AreEqual("Foo 23", "foo__23".SplitOnCase());
        Assert.AreEqual("Foo Bar", "Foo_bar".SplitOnCase());
        Assert.AreEqual("Foo Bar", "Foo____bar".SplitOnCase());
        Assert.AreEqual("AAA", "AAA".SplitOnCase());
        Assert.AreEqual("Foo A Aa", "fooAAa".SplitOnCase());
        Assert.AreEqual("Foo AAA", "fooAAA".SplitOnCase());
        Assert.AreEqual("Foo Bar", "FooBar".SplitOnCase());
        Assert.AreEqual("Mn M", "MnM".SplitOnCase());
        Assert.AreEqual("AS", "aS".SplitOnCase());
        Assert.AreEqual("As", "as".SplitOnCase());
        Assert.AreEqual("A", "a".SplitOnCase());
        Assert.AreEqual("_", "_".SplitOnCase());

    }
}
Gru
fuente
1

Versión simple similar a algunas de las anteriores, pero con lógica para no insertar automáticamente el separador (que por defecto es un espacio, pero puede ser cualquier carácter) si ya hay uno en la posición actual.

Utiliza StringBuildercadenas en lugar de "mutantes".

public static string SeparateCamelCase(this string value, char separator = ' ') {

    var sb = new StringBuilder();
    var lastChar = separator;

    foreach (var currentChar in value) {

        if (char.IsUpper(currentChar) && lastChar != separator)
            sb.Append(separator);

        sb.Append(currentChar);

        lastChar = currentChar;
    }

    return sb.ToString();
}

Ejemplo:

Input  : 'ThisIsATest'
Output : 'This Is A Test'

Input  : 'This IsATest'
Output : 'This Is A Test' (Note: Still only one space between 'This' and 'Is')

Input  : 'ThisIsATest' (with separator '_')
Output : 'This_Is_A_Test'
Mark A. Donohoe
fuente
0

Prueba esto:

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        Console
            .WriteLine(
                SeparateByCamelCase("TestString") == "Test String" // True
            );
    }

    public static string SeparateByCamelCase(string str)
    {
        return String.Join(" ", SplitByCamelCase(str));
    }

    public static IEnumerable<string> SplitByCamelCase(string str) 
    {
        if (str.Length == 0) 
            return new List<string>();

        return 
            new List<string> 
            { 
                Head(str) 
            }
            .Concat(
                SplitByCamelCase(
                    Tail(str)
                )
            );
    }

    public static string Head(string str)
    {
        return new String(
                    str
                        .Take(1)
                        .Concat(
                            str
                                .Skip(1)
                                .TakeWhile(IsLower)
                        )
                        .ToArray()
                );
    }

    public static string Tail(string str)
    {
        return new String(
                    str
                        .Skip(
                            Head(str).Length
                        )
                        .ToArray()
                );
    }

    public static bool IsLower(char ch) 
    {
        return ch >= 'a' && ch <= 'z';
    }
}

Ver muestra en línea

kogoia
fuente