82

What is the best way to convert from Pascal Case (upper Camel Case) to a sentence.

For example starting with

"AwaitingFeedback"

and converting that to

"Awaiting feedback"

C# preferable but I could convert it from Java or similar.

Mike Cole
  • 14,474
  • 28
  • 114
  • 194
Garry Shutler
  • 32,260
  • 12
  • 84
  • 119
  • 2
    Camel case is awaitingFeedback and not AwaitingFeedback (Pascal Case). Also, what you want to do is not completely possible. How about disableGPS? Is there a solution general enough to handle these cases? – kgiannakakis Nov 27 '08 at 10:08
  • @kgiannakakis altered the question accordingly. I always get forget which way round the names are, especially with there being upper and lower Camel Case. – Garry Shutler Nov 27 '08 at 10:49

17 Answers17

81
public static string ToSentenceCase(this string str)
{
    return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}

In versions of visual studio after 2015, you can do

public static string ToSentenceCase(this string str)
{
    return Regex.Replace(str, "[a-z][A-Z]", m => $"{m.Value[0]} {char.ToLower(m.Value[1])}");
}

Based on: Converting Pascal case to sentences using regular expression

RoadieRich
  • 6,330
  • 3
  • 35
  • 52
21

I will prefer to use Humanizer for this. Humanizer is a Portable Class Library that meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities.

Short Answer

"AwaitingFeedback".Humanize() => Awaiting feedback

Long and Descriptive Answer

Humanizer can do a lot more work other examples are:

"PascalCaseInputStringIsTurnedIntoSentence".Humanize() => "Pascal case input string is turned into sentence"
"Underscored_input_string_is_turned_into_sentence".Humanize() => "Underscored input string is turned into sentence"
"Can_return_title_Case".Humanize(LetterCasing.Title) => "Can Return Title Case"
"CanReturnLowerCase".Humanize(LetterCasing.LowerCase) => "can return lower case"

Complete code is :

using Humanizer;
using static System.Console;

namespace HumanizerConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine("AwaitingFeedback".Humanize());
            WriteLine("PascalCaseInputStringIsTurnedIntoSentence".Humanize());
            WriteLine("Underscored_input_string_is_turned_into_sentence".Humanize());
            WriteLine("Can_return_title_Case".Humanize(LetterCasing.Title));
            WriteLine("CanReturnLowerCase".Humanize(LetterCasing.LowerCase));
        }
    }
}

Output

Awaiting feedback

Pascal case input string is turned into sentence

Underscored input string is turned into sentence Can Return Title Case

can return lower case

If you prefer to write your own C# code you can achieve this by writing some C# code stuff as answered by others already.

Community
  • 1
  • 1
Banketeshvar Narayan
  • 3,799
  • 4
  • 38
  • 46
16

This works for me:

Regex.Replace(strIn, "([A-Z]{1,2}|[0-9]+)", " $1").TrimStart()
SSTA
  • 169
  • 1
  • 2
  • 6
    How does this change the case of the letter after the space? – Drew Noakes Mar 08 '11 at 18:48
  • 1
    This may not return what you intend for cases like `AwaitingTFeedback` or `Awaiting9Feedback`. [Jef's answer](https://stackoverflow.com/a/11432809/661933) is better for me (which returns `Awaiting T Feedback` and `Awaiting9 Feedback` respectively). – nawfal Dec 07 '17 at 06:18
16

Here you go...

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

namespace CamelCaseToString
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(CamelCaseToString("ThisIsYourMasterCallingYou"));   
        }

        private static string CamelCaseToString(string str)
        {
            if (str == null || str.Length == 0)
                return null;

            StringBuilder retVal = new StringBuilder(32);

            retVal.Append(char.ToUpper(str[0]));
            for (int i = 1; i < str.Length; i++ )
            {
                if (char.IsLower(str[i]))
                {
                    retVal.Append(str[i]);
                }
                else
                {
                    retVal.Append(" ");
                    retVal.Append(char.ToLower(str[i]));
                }
            }

            return retVal.ToString();
        }
    }
}
Sandeep Datta
  • 28,607
  • 15
  • 70
  • 90
  • 1
    You should ToUpper() the first character, otherwise your routine doesn't work with true camelCase, only PascalCase – David Wengier Nov 27 '08 at 10:07
  • Yup nice catch, I knew something was amiss (but couldn't put my finger on it) when he said "AwaitingFeedback" is camel case! – Sandeep Datta Nov 27 '08 at 10:18
11

This is just like @SSTA, but is more efficient than calling TrimStart.

Regex.Replace("ThisIsMyCapsDelimitedString", "(\\B[A-Z])", " $1")
Bryan Legend
  • 6,790
  • 1
  • 59
  • 60
10

Found this in the MvcContrib source, doesn't seem to be mentioned here yet.

return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim();
JefClaes
  • 3,275
  • 21
  • 23
5

Just because everyone has been using Regex (except this guy), here's an implementation with StringBuilder that was about 5x faster in my tests. Includes checking for numbers too.

"SomeBunchOfCamelCase2".FromCamelCaseToSentence == "Some Bunch Of Camel Case 2"

public static string FromCamelCaseToSentence(this string input) {
    if(string.IsNullOrEmpty(input)) return input;

    var sb = new StringBuilder();
    // start with the first character -- consistent camelcase and pascal case
    sb.Append(char.ToUpper(input[0]));

    // march through the rest of it
    for(var i = 1; i < input.Length; i++) {
        // any time we hit an uppercase OR number, it's a new word
        if(char.IsUpper(input[i]) || char.IsDigit(input[i])) sb.Append(' ');
        // add regularly
        sb.Append(input[i]);
    }

    return sb.ToString();
}
drzaus
  • 24,171
  • 16
  • 142
  • 201
  • In your benchmarks did you cache the regex object? There are "compiled regexes" but I'm not sure if .net uses them by default. https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices – Bryan Legend Aug 17 '22 at 17:14
4

Here's a basic way of doing it that I came up with using Regex

public static string CamelCaseToSentence(this string value)
{
    var sb = new StringBuilder();
    var firstWord = true;

    foreach (var match in Regex.Matches(value, "([A-Z][a-z]+)|[0-9]+"))
    {
        if (firstWord)
        {
            sb.Append(match.ToString());
            firstWord = false;
        }
        else
        {
            sb.Append(" ");
            sb.Append(match.ToString().ToLower());
        }
    }

    return sb.ToString();
}

It will also split off numbers which I didn't specify but would be useful.

Garry Shutler
  • 32,260
  • 12
  • 84
  • 119
3
string camel = "MyCamelCaseString";
string s = Regex.Replace(camel, "([A-Z])", " $1").ToLower().Trim();
Console.WriteLine(s.Substring(0,1).ToUpper() + s.Substring(1));

Edit: didn't notice your casing requirements, modifed accordingly. You could use a matchevaluator to do the casing, but I think a substring is easier. You could also wrap it in a 2nd regex replace where you change the first character

"^\w"

to upper

\U (i think)
Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231
2

I'd use a regex, inserting a space before each upper case character, then lowering all the string.

    string spacedString = System.Text.RegularExpressions.Regex.Replace(yourString, "\B([A-Z])", " \k");
    spacedString = spacedString.ToLower();
Antoine
  • 5,055
  • 11
  • 54
  • 82
  • I don't know C# but I don't think escapes like \s are legal in the replace section: how the language will know if it must insert space, tab or something else? :-) – PhiLho Nov 27 '08 at 09:46
  • You're right, should be simpler to replace it with a clear " ". – Antoine Nov 27 '08 at 09:49
  • The only thing I'd say is that will produce "awaiting feedback" – Garry Shutler Nov 27 '08 at 09:59
  • Well you sure have to remove the first space and upper the first character. Maybe add a "\B" before the pattern so as not to match the first char. – Antoine Nov 27 '08 at 10:09
2

It is easy to do in JavaScript (or PHP, etc.) where you can define a function in the replace call:

var camel = "AwaitingFeedbackDearMaster";
var sentence = camel.replace(/([A-Z].)/g, function (c) { return ' ' + c.toLowerCase(); });
alert(sentence);

Although I haven't solved the initial cap problem... :-)

Now, for the Java solution:

String ToSentence(String camel)
{
  if (camel == null) return ""; // Or null...
  String[] words = camel.split("(?=[A-Z])");
  if (words == null) return "";
  if (words.length == 1) return words[0];
  StringBuilder sentence = new StringBuilder(camel.length());
  if (words[0].length() > 0) // Just in case of camelCase instead of CamelCase
  {
    sentence.append(words[0] + " " + words[1].toLowerCase());
  }
  else
  {
    sentence.append(words[1]);
  }
  for (int i = 2; i < words.length; i++)
  {
    sentence.append(" " + words[i].toLowerCase());
  }
  return sentence.toString();
}

System.out.println(ToSentence("AwaitingAFeedbackDearMaster"));
System.out.println(ToSentence(null));
System.out.println(ToSentence(""));
System.out.println(ToSentence("A"));
System.out.println(ToSentence("Aaagh!"));
System.out.println(ToSentence("stackoverflow"));
System.out.println(ToSentence("disableGPS"));
System.out.println(ToSentence("Ahh89Boo"));
System.out.println(ToSentence("ABC"));

Note the trick to split the sentence without loosing any character...

PhiLho
  • 40,535
  • 6
  • 96
  • 134
1

An xquery solution that works for both UpperCamel and lowerCamel case:

To output sentence case (only the first character of the first word is capitalized):

declare function content:sentenceCase($string)
{
let $firstCharacter := substring($string, 1, 1)
let $remainingCharacters := substring-after($string, $firstCharacter)
return
concat(upper-case($firstCharacter),lower-case(replace($remainingCharacters, '([A-Z])', ' $1')))
};

To output title case (first character of each word capitalized):

declare function content:titleCase($string)
{
let $firstCharacter := substring($string, 1, 1)
let $remainingCharacters := substring-after($string, $firstCharacter)
return
concat(upper-case($firstCharacter),replace($remainingCharacters, '([A-Z])', ' $1'))
};
Lorenzo
  • 29,081
  • 49
  • 125
  • 222
Fraser
  • 11
  • 2
1

Found myself doing something similar, and I appreciate having a point-of-departure with this discussion. This is my solution, placed as an extension method to the string class in the context of a console application.

using System;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string piratese = "avastTharMatey";
            string ivyese = "CheerioPipPip";

            Console.WriteLine("{0}\n{1}\n", piratese.CamelCaseToString(), ivyese.CamelCaseToString());
            Console.WriteLine("For Pete\'s sake, man, hit ENTER!");
            string strExit = Console.ReadLine();
        }

    }

    public static class StringExtension
    {
        public static string CamelCaseToString(this string str)
        {
            StringBuilder retVal = new StringBuilder(32);

            if (!string.IsNullOrEmpty(str))
            {
                string strTrimmed = str.Trim();

                if (!string.IsNullOrEmpty(strTrimmed))
                {
                    retVal.Append(char.ToUpper(strTrimmed[0]));

                    if (strTrimmed.Length > 1)
                    {
                        for (int i = 1; i < strTrimmed.Length; i++)
                        {
                            if (char.IsUpper(strTrimmed[i])) retVal.Append(" ");

                            retVal.Append(char.ToLower(strTrimmed[i]));
                        }
                    }
                }
            }
            return retVal.ToString();
        }
    }
}
sscheider
  • 522
  • 5
  • 14
1

Pseudo-code:

NewString = "";
Loop through every char of the string (skip the first one)
   If char is upper-case ('A'-'Z')
     NewString = NewString + ' ' + lowercase(char)
   Else
     NewString = NewString + char

Better ways can perhaps be done by using regex or by string replacement routines (replace 'X' with ' x')

schnaader
  • 49,103
  • 10
  • 104
  • 136
1

Most of the preceding answers split acronyms and numbers, adding a space in front of each character. I wanted acronyms and numbers to be kept together so I have a simple state machine that emits a space every time the input transitions from one state to the other.

    /// <summary>
    /// Add a space before any capitalized letter (but not for a run of capitals or numbers)
    /// </summary>
    internal static string FromCamelCaseToSentence(string input)
    {
        if (string.IsNullOrEmpty(input)) return String.Empty;

        var sb = new StringBuilder();
        bool upper = true;

        for (var i = 0; i < input.Length; i++)
        {
            bool isUpperOrDigit = char.IsUpper(input[i]) || char.IsDigit(input[i]);
            // any time we transition to upper or digits, it's a new word
            if (!upper && isUpperOrDigit)
            {
                sb.Append(' ');
            }
            sb.Append(input[i]);
            upper = isUpperOrDigit;
        }

        return sb.ToString();
    }

And here's some tests:

    [TestCase(null, ExpectedResult = "")]
    [TestCase("", ExpectedResult = "")]
    [TestCase("ABC", ExpectedResult = "ABC")]
    [TestCase("abc", ExpectedResult = "abc")]
    [TestCase("camelCase", ExpectedResult = "camel Case")]
    [TestCase("PascalCase", ExpectedResult = "Pascal Case")]
    [TestCase("Pascal123", ExpectedResult = "Pascal 123")]
    [TestCase("CustomerID", ExpectedResult = "Customer ID")]
    [TestCase("CustomABC123", ExpectedResult = "Custom ABC123")]
    public string CanSplitCamelCase(string input)
    {
        return FromCamelCaseToSentence(input);
    }
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • 1
    Nice answer. In my project I added `bool nextIsLower = i > 0 && i + 1 < source.Length && char.IsLower(source[i + 1]);` and changed the if expression to `if ((!upper || nextIsLower) && isUpperOrDigit)`. This separates acronyms from words, so **CustomABCWith123** becomes **Custom ABC With 123** instead of **Custom ABCWith 123**. There may be cases I haven't handled, and, of course, A and I don't work. – stritch000 May 22 '20 at 06:30
0

Mostly already answered here

Small chage to the accepted answer, to convert the second and subsequent Capitalised letters to lower case, so change

if (char.IsUpper(text[i]))                
    newText.Append(' ');            
newText.Append(text[i]);

to

if (char.IsUpper(text[i]))                
{
    newText.Append(' ');            
    newText.Append(char.ToLower(text[i]));
}
else
   newText.Append(text[i]);
Community
  • 1
  • 1
Binary Worrier
  • 50,774
  • 20
  • 136
  • 184
0

Here is my implementation. This is the fastest that I got while avoiding creating spaces for abbreviations.

public static string PascalCaseToSentence(string input)
    {
        if (string.IsNullOrEmpty(input) || input.Length < 2)
            return input;

        var sb = new char[input.Length + ((input.Length + 1) / 2)];
        var len = 0;
        var lastIsLower = false;
        for (int i = 0; i < input.Length; i++)
        {
            var current = input[i];
            if (current < 97)
            {
                if (lastIsLower)
                {
                    sb[len] = ' ';
                    len++;
                }
                lastIsLower = false;
            }
            else
            {
                lastIsLower = true;
            }
            sb[len] = current;

            len++;
        }

        return new string(sb, 0, len);
    }
juanora
  • 542
  • 10
  • 21