Dzielenie CamelCase


80

To wszystko jest c # asp.net.

Mam wyliczenie

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

Wartość liczbowa tego jest przechowywana w mojej bazie danych. Wyświetlam tę wartość w datagridzie.

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

Identyfikator nic nie znaczy dla użytkownika, więc zmieniłem kolumnę boundcolumn na kolumnę szablonu z następującym.

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

To jest o wiele lepsze ... Jednak byłoby wspaniale, gdyby istniała prosta funkcja, którą mogę umieścić wokół Enum, aby podzielić ją według wielkości Camel, aby słowa ładnie zawijały się w datagrid.

Uwaga: mam pełną świadomość, że są lepsze sposoby na zrobienie tego wszystkiego. Ten ekran jest używany wyłącznie wewnętrznie i chcę tylko szybkiego włamania, aby wyświetlić go trochę lepiej.

Odpowiedzi:


76

Rzeczywiście, regex / replace to sposób postępowania zgodnie z opisem w drugiej odpowiedzi, jednak może to być również przydatne, jeśli chcesz pójść w innym kierunku

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

pozwoli to zdefiniować wyliczenia jako

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

Pochodzą z

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


+1 Świetna odpowiedź, prawdopodobnie użyję odpowiedzi wyrażenia regularnego, ponieważ jest to szybsze i łatwiejsze rozwiązanie, jednak jest to znacznie lepsze rozwiązanie i dlatego jest akceptowane.
Robin Day

Widziałem wiele odpowiedzi dotyczących atrybutów wyliczenia, ale ten wygląda najczystszy!
nawfal

2
sprytne, ale wymaga dużo więcej pracy niż prosta statyczna funkcja regex. Nie jestem pewien, czy zgadzam się ze słowami „najczystszy”, „szybszy” czy „łatwiejszy”. Cleverist? na pewno.
Todd Painton

1
Działa to tylko wtedy, gdy masz kontrolę nad tym wyliczeniem, ale wolałbym mieć pełną kontrolę nad kodem wyświetlania, zamiast zakładać, że wartości wyliczenia będą pisane normalnie.
Berin Loritsch

131

Użyłem:

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

Zaczerpnięto z 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

Prosty sposób na wykonanie części obudowy wielbłąda ... drugie podejście jest lepsze dla większej personalizacji. Dzięki @Tillito
rolivares

62
Poprawiłem trochę wyrażenie regularne na „(? <= [Az]) ([AZ])”. Powoduje to, że ProductID jest konwertowane na identyfikator produktu zamiast na produkt I D. Określa, że ​​duża litera musi być poprzedzona małą literą (zwróć uwagę na operator lookbehind). Eliminuje również potrzebę przycinania.
Ben Mills

7
Hej Ben, dlaczego nie podasz tego jako odpowiedzi. Posiadanie innego (i bardziej wyrafinowanego) wyrażenia regularnego stanowi nową odpowiedź!
Nicholas Petersen

3
Jako dodatek do pomocnego komentarza Bena, powinienem wspomnieć, że możesz również podzielić coś takiego jak „HELLOWorld” na „HELLO World”, używając wyrażenia regularnego: (? <= [AZ]) ([AZ]) (? = [Az] )
giangurgolo

6
Połączyłem wyrażenia Bena Millsa i giangurgolo: Regex.Replace (input, @ "((? <= [AZ]) ([AZ]) (? = [Az])) | ((? <= [Az] +) ([AZ])) ", @" $ 0 ", RegexOptions.Compiled) .Trim ();
IceWarrior353

23

Tego wyrażenia regularnego (^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)można użyć do wyodrębnienia wszystkich słów z nazwy camelCase lub PascalCase. Działa również ze skrótami w dowolnym miejscu nazwy.

  • MyHTTPServer będzie zawierać dokładnie 3 dopasowania: My , HTTP,Server
  • myNewXMLFilebędzie zawierać 4 mecze: my, New,XML ,File

Możesz następnie połączyć je w jeden ciąg używając 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);

2
Lubię to. Jednak żyjemy w czasach współczesnych. W związku z tym: @"(^\p{Ll}+|\p{Lu}+(?!\p{Ll})|\p{Lu}\p{Ll}+)"ważne jest również, aby pamiętać, że to w ogóle nie wpłynie na liczby, mimo że są one prawidłowe w identyfikatorach.
Daniel B

Proste, ale doskonałe!
JC Raja

1
Potrzebowałem niewielkiej zmiany na „(^ [az] + | [AZ] + (?! [Az]) | [AZ] [az] + | [0-9 \. *] + | [Az] +)” „ITPortfolio12v2.0.13BMS” daje w wyniku „IT Portfolio 12 v 2.0.13 BMS” hth ktoś
Joe Johnston

15

Jeśli opcja C # 3.0 jest opcją, do wykonania zadania możesz użyć następującego linijki:


Regex.Matches(YOUR_ENUM_VALUE_NAME, "[A-Z][a-z]+").OfType<Match>().Select(match => match.Value).Aggregate((acc, b) => acc + " " + b).TrimStart(' ');

1
To nie obsługuje Acroynms w tekście, takich jak AMACharter, zwraca „Karta”, a nie „Karta AMA”.
Adam Mills,

Chociaż modyfikacje do obsługi takiego przypadku byłyby łatwe (pomyśl o dodaniu czegoś takiego jak ([AZ] *) i nieznacznej modyfikacji kodu), z tego, co pamiętam z wytycznych kodowania Microsoftu, odradza się stosowanie takich akronimów pisanych wielkimi literami, a akronimy pisane wielkimi literami Należy unikać akronimów ogólnych, jeśli są dłuższe niż 2 litery.
em70

1
Nie działa na mnie. „CamelCase” staje się „Camel”, a nie „Camel Case”.
Tillito

15

Odpowiedź Tillito nie obsługuje dobrze ciągów znaków już zawierających spacje ani akronimów. To rozwiązuje problem:

public static string SplitCamelCase(string input)
{
    return Regex.Replace(input, "(?<=[a-z])([A-Z])", " $1", RegexOptions.Compiled);
}

Zastrzeżenie: uznanie należy do Tillito, dostawcy oryginalnej odpowiedzi, i Bena Millsa, który zasugerował poprawę w komentarzu. Ponieważ jest to ulepszona odpowiedź i żaden z nich nie opublikował ani nie edytował jej, zasługuje na oddzielną odpowiedź. Oszczędziłoby mi to pół godziny na debugowaniu, gdyby nie było to zakopane pod komentarzami.
Petrucio,

2
Niepowodzenie z prostym przypadkiem testowym „SMSMessage” (oczekiwany: „SMS Message”, aktualny: „SMSMessage”).
Ian Kemp

10

Oto metoda rozszerzenia, która rozsądnie obsługuje liczby i wiele wielkich liter, a także pozwala na pisanie dużych liter akronimów w końcowym ciągu:

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

6

Możesz użyć metod rozszerzających 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;
        }

Następnie możesz go używać jak

var result = "TestString".SpacesFromCamel();

Wynik będzie

Ciąg testowy


1
To faktycznie tworzy spację na początku, naprawiono kod
Martin Zikmund

3

Mam też taki, enumktóry musiałem oddzielić. W moim przypadku ta metoda rozwiązała problem-

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

2

Korzystanie z 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()));

1
Powinieneś wrócić do kodowania w C \ C ++: D - zbyt brudny dla C #
dane

1
Cóż, stwierdziłem, że był to szybki i brudny hack. Oto bardziej przejrzysta wersja LINQ.
Andy Rose

Nie dotyczy to Acroynms w tekście, takich jak AMACharter, zwraca „Karta AMA”, a nie „Karta AMA
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();
        }

2

Rozwiązanie firmy Eoin Campbell działa dobrze, chyba że masz usługę internetową.

Należy wykonać następujące czynności, ponieważ atrybut opisu nie jest możliwy do serializacji.

[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;
}

2

A jeśli nie masz ochoty używać wyrażenia regularnego - spróbuj tego:

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

    }

Zdał te testy, nie lubi liczb, ale ja tego nie potrzebowałem.

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

    }

2

#JustSayNoToRegex

Pobiera identyfikator C # z wynikami uderscores i liczbami i konwertuje go na ciąg rozdzielany spacjami.

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

}

Testy:

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

    }
}

1

Prosta wersja podobna do niektórych z powyższych, ale z logiką, aby nie wstawiać automatycznie separatora (który domyślnie jest spacją, ale może być dowolnym znakiem), jeśli istnieje już jeden na bieżącej pozycji.

Używa StringBuilderzamiast „mutowania” ciągów znaków.

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

Przykład:

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'

0

Spróbuj tego:

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';
    }
}

Zobacz próbkę online

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.