Splitting CamelCase

C#asp.netString

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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

Solution 3 - C#

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

Solution 4 - C#

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

Solution 5 - C#

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

Solution 6 - C#

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

Solution 7 - C#

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

Solution 8 - C#

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

Solution 9 - C#

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

Solution 10 - C#

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

Solution 11 - C#

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

Solution 12 - C#

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

    }

Solution 13 - C#

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

	}
}

Solution 14 - C#

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'

Solution 15 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRobin DayView Question on Stackoverflow
Solution 1 - C#TillitoView Answer on Stackoverflow
Solution 2 - C#Eoin CampbellView Answer on Stackoverflow
Solution 3 - C#Ghost4ManView Answer on Stackoverflow
Solution 4 - C#PetrucioView Answer on Stackoverflow
Solution 5 - C#em70View Answer on Stackoverflow
Solution 6 - C#JerphView Answer on Stackoverflow
Solution 7 - C#Sameera R.View Answer on Stackoverflow
Solution 8 - C#Andy RoseView Answer on Stackoverflow
Solution 9 - C#Ariful IslamView Answer on Stackoverflow
Solution 10 - C#KiarashView Answer on Stackoverflow
Solution 11 - C#Theo KoekemoerView Answer on Stackoverflow
Solution 12 - C#HathView Answer on Stackoverflow
Solution 13 - C#GruView Answer on Stackoverflow
Solution 14 - C#Mark A. DonohoeView Answer on Stackoverflow
Solution 15 - C#giokoguashviliView Answer on Stackoverflow