Robin Day
Robin Day

Reputation: 102488

Splitting CamelCase

This is all asp.net c#.

I have an enum

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

The numerical value of this is stored in my database. I display this value in a datagrid.

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

The ID means nothing to a user so I have changed the boundcolumn to a template column with the following.

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

This is a lot better... However, it would be great if there was a simple function I can put around the Enum to split it by Camel case so that the words wrap nicely in the datagrid.

Note: I am fully aware that there are better ways of doing all this. This screen is purely used internally and I just want a quick hack in place to display it a little better.

Upvotes: 99

Views: 58798

Answers (15)

Tillito
Tillito

Reputation: 7868

I used:

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

Taken from 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

Here is a dotnet Fiddle for online execution of the c# code.

Upvotes: 167

Ghost4Man
Ghost4Man

Reputation: 1072

This regex (^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+) can be used to extract all words from the camelCase or PascalCase name. It also works with abbreviations anywhere inside the name.

  • MyHTTPServer will contain exactly 3 matches: My, HTTP, Server
  • myNewXMLFile will contain 4 matches: my, New, XML, File

You could then join them into a single string using 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);

As @DanielB noted in the comments, that regex won't work for numbers (and with underscores), so here is an improved version that supports any identifier with words, acronyms, numbers, underscores (slightly modified @JoeJohnston's version), see online demo (fiddle):

([A-Z]+(?![a-z])|[A-Z][a-z]+|[0-9]+|[a-z]+)

Extreme example: __snake_case12_camelCase_TLA1ABCsnake, case, 12, camel, Case, TLA, 1, ABC

Upvotes: 30

Gru
Gru

Reputation: 490

#JustSayNoToRegex

Takes a C# identifier, with uderscores and numbers, and converts it to space-separated string.

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

}

Tests:

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

    }
}

Upvotes: 2

Mark A. Donohoe
Mark A. Donohoe

Reputation: 30408

Simple version similar to some of the above, but with logic to not auto-insert the separator (which is by default, a space, but can be any char) if there's already one at the current position.

Uses a StringBuilder rather than 'mutating' strings.

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

Example:

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'

Upvotes: 2

Sameera R.
Sameera R.

Reputation: 4592

You can use C# extension methods

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

Then you can use it like

var result = "TestString".SpacesFromCamel();

Result will be

Test String

Upvotes: 8

giokoguashvili
giokoguashvili

Reputation: 2103

Try this:

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

See sample online

Upvotes: 0

Ariful Islam
Ariful Islam

Reputation: 674

I also have an enum which I had to separate. In my case this method solved the 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;
}

Upvotes: 3

Hath
Hath

Reputation: 12769

And if you don't fancy using regex - try this:

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

    }

Passes these tests, it doesn't like numbers but i didn't need it to.

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

    }

Upvotes: 2

Petrucio
Petrucio

Reputation: 5679

Tillito's answer does not handle strings already containing spaces well, or Acronyms. This fixes it:

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

Upvotes: 21

Jerph
Jerph

Reputation: 4580

Here's an extension method that handles numbers and multiple uppercase characters sanely, and also allows for upper-casing specific acronyms in the final string:

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

Upvotes: 11

Theo Koekemoer
Theo Koekemoer

Reputation: 228

The solution from Eoin Campbell works good except if you have a Web Service.

You would need to do the Following as the Description Attribute is not 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;
}

Upvotes: 2

Kiarash
Kiarash

Reputation: 1899

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

Upvotes: 2

Andy Rose
Andy Rose

Reputation: 16984

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

Upvotes: 3

em70
em70

Reputation: 6081

If C# 3.0 is an option you can use the following one-liner to do the job:


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

Upvotes: 15

Eoin Campbell
Eoin Campbell

Reputation: 44288

Indeed a regex/replace is the way to go as described in the other answer, however this might also be of use to you if you wanted to go a different direction

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

this will allow you define your Enums as

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

Taken from

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

Upvotes: 81

Related Questions