20

I am looking for a way to split PascalCase strings, e.g. "MyString", into separate words - "My", "String". Another user posed the question for bash, but I want to know how to do it with general regular expressions or at least in .NET.

Bonus if you can find a way to also split (and optionally capitalize) camelCase strings: e.g. "myString" becomes "my" and "String", with the option to capitalize/lowercase either or both of the strings.

3
  • possible duplicate of is there a elegant way to parse a word and add spaces before capital letters Commented Jul 9, 2010 at 20:20
  • This question is specific to .NET, but the regex answers could be applied elsewhere. Commented Jul 9, 2010 at 22:30
  • Check out the dupe question: the accepted answer has the regex to split AnXMLAndXSLT2.0Tool to [An][XML][And][XSLT][2.0][Tool]. It uses lookarounds that one can argue is quite readable. Commented Jul 10, 2010 at 3:44

10 Answers 10

29

See this question: Is there a elegant way to parse a word and add spaces before capital letters? Its accepted answer covers what you want, including numbers and several uppercase letters in a row. While this sample has words starting in uppercase, it it equally valid when the first word is in lowercase.

string[] tests = {
   "AutomaticTrackingSystem",
   "XMLEditor",
   "AnXMLAndXSLT2.0Tool",
};


Regex r = new Regex(
    @"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])"
  );

foreach (string s in tests)
  r.Replace(s, " ");

The above will output:

[Automatic][Tracking][System]
[XML][Editor]
[An][XML][And][XSLT][2.0][Tool]
Sign up to request clarification or add additional context in comments.

8 Comments

@Steven Sudit: Yes. RegEx is one of the best tools for this type of problem. The other question is just got flushed out with a larger set of sample use cases.
@chilltemp, do you know of a built-in function for it?
@Shimmy: No. I'd recommend that you use the information in the linked question to create a reusable library.
I made my own function that doesn't use regex.
@Shimmy: Performance varies greatly depending upon many factors including the how complex the RegEx is and if it is compiled. Just like the performance of C# varies depending upon how you use it. That being said, I've always found RegEx in .NET to be fast enough for my needs (real-time transactional system with high throughput). The only ways to really compare is to look at the generated IL and/or do timed test runs.
|
14

Just to provide an alternative to the RegEx and looping solutions all ready provided here is an answer using LINQ which also handles camel case and acronyms:

    string[] testCollection = new string[] { "AutomaticTrackingSystem", "XSLT", "aCamelCaseWord" };
    foreach (string test in testCollection)
    {
        // if it is not the first character and it is uppercase
        //  and the previous character is not uppercase then insert a space
        var result = test.SelectMany((c, i) => i != 0 && char.IsUpper(c) && !char.IsUpper(test[i - 1]) ? new char[] { ' ', c } : new char[] { c });
        Console.WriteLine(new String(result.ToArray()));
    }

The output from this is:

Automatic Tracking System  
XSLT  
a Camel Case Word 

2 Comments

This is my absolute favorite :)
Worth noting that this doesn't work for acronyms mixed with other words, if the expectation is to treat the acronym as its own word. For example, HTTPResponseException converts to HTTPResponse Exception.
9

Answered in a different question:

void Main()
{
    "aCamelCaseWord".ToFriendlyCase().Dump();
}

public static class Extensions
{
    public static string ToFriendlyCase(this string PascalString)
    {
        return Regex.Replace(PascalString, "(?!^)([A-Z])", " $1");
    }
}

Outputs a Camel Case Word (.Dump() just prints to the console).

4 Comments

What must happen for the strings like this: aCamelCaseXML? Reading the question, I would expect a Camel Case XML. Instead, it gives a Camel Case X M L.
@MainMa That's true. Following .NET naming standards, any acronyms three letters or longer (e.g. XML) would be in proper case (i.e. Xml), but two-letter acronyms (e.g. IP for IPAddress) would still cause a problem. It would be better to have the algorithm handle this case.
Is there any out-the-box funtion that does this?
I'd suggest: new Regex( @" (?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace as stackoverflow.com/questions/3103730/… says
5

How about:

static IEnumerable<string> SplitPascalCase(this string text)
{
    var sb = new StringBuilder();
    using (var reader = new StringReader(text))
    {
        while (reader.Peek() != -1)
        {
            char c = (char)reader.Read();
            if (char.IsUpper(c) && sb.Length > 0)
            {
                yield return sb.ToString();
                sb.Length = 0;
            }

            sb.Append(c);
        }
    }

    if (sb.Length > 0)
        yield return sb.ToString();
}

3 Comments

This would be a "by hand" solution.
@Steven Sudit: Yeah... was that forbidden or something?
No, no, not at all. There was just some confusion about what "by hand" meant, when I suggested that to Pat as an alternative to RegExp. In fact, I think that RegExp, for all its power, is overused. For many jobs, it's a bad fit, leading to cryptic code and poor performance.
3

with the aims of

  • a) Creating a function which optimised performance
  • b) Have my own take on CamelCase in which capitalised acronyms were not separated (I fully accept this is not the standard definition of camel or pascal case, but it is not an uncommon usage) : "TestTLAContainingCamelCase" becomes "Test TLA Containing Camel Case" (TLA = Three Letter Acronym)

I therefore created the following (non regex, verbose, but performance oriented) function

public static string ToSeparateWords(this string value)
{
    if (value==null){return null;}
    if(value.Length <=1){return value;}
    char[] inChars = value.ToCharArray();
    List<int> uCWithAnyLC = new List<int>();
    int i = 0;
    while (i < inChars.Length && char.IsUpper(inChars[i])) { ++i; }
    for (; i < inChars.Length; i++)
    {
        if (char.IsUpper(inChars[i]))
        {
            uCWithAnyLC.Add(i);
            if (++i < inChars.Length && char.IsUpper(inChars[i]))
            {
                while (++i < inChars.Length) 
                {
                    if (!char.IsUpper(inChars[i]))
                    {
                        uCWithAnyLC.Add(i - 1);
                        break;
                    }
                }
            }
        }
    }
    char[] outChars = new char[inChars.Length + uCWithAnyLC.Count];
    int lastIndex = 0;
    for (i=0;i<uCWithAnyLC.Count;i++)
    {
        int currentIndex = uCWithAnyLC[i];
        Array.Copy(inChars, lastIndex, outChars, lastIndex + i, currentIndex - lastIndex);
        outChars[currentIndex + i] = ' ';
        lastIndex = currentIndex;
    }
    int lastPos = lastIndex + uCWithAnyLC.Count;
    Array.Copy(inChars, lastIndex, outChars, lastPos, outChars.Length - lastPos);
    return new string(outChars);
}

What was most surprising was the performance tests. using 1 000 000 iterations per function

regex pattern used = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))"
test string = "TestTLAContainingCamelCase":
static regex:      13 302ms
Regex instance:    12 398ms
compiled regex:    12 663ms
brent(above):         345ms
AndyRose:           1 764ms
DanTao:               995ms

the Regex instance method was only slightly faster than the static method, even over a million iterations (and I can't see the benefit of using the RegexOptions.Compiled flag), and Dan Tao's very succinct code was almost as fast as my much less clear code!

Comments

1
var regex = new Regex("([A-Z]+[^A-Z]+)");
var matches = regex.Matches("aCamelCaseWord")
    .Cast<Match>()
    .Select(match => match.Value);
foreach (var element in matches)
{
    Console.WriteLine(element);
}

Prints

Camel
Case
Word

(As you can see, it doesn't handle camelCase - it dropped the leading "a".)

5 Comments

1) Compile the regexp for some speed. 2) It'll still be slower than doing it by hand.
@Steven I agree that it should be compiled for speed, but it's the functionality I'm going after for now. What do you mean it will be "slower than doing it by hand"? If I reflect over an object with a bunch of public properties and convert the names from PascalCase to separate words, it will be much faster (development and maintenance time) doing it programmatically than by hand.
I didn't see speed mentioned as a requirement. Also I think "doing it by hand" refers to writing your own string parsing code which may be faster but will be significantly more code and more testing.
@Ken This method doesn't handle camelCase, so the "a" was dropped (see edit to the answer).
@Pat: what Ron said is correct: "by hand" means writing your own code to loop over the string, character by character, building up each word into a StringBuilder and outputting as needed.
1
string.Concat(str.Select(x => Char.IsUpper(x) ? " " + x : x.ToString())).TrimStart(' ').Dump();

This is far better approach then using Regex, Dump is just to print to console

Comments

0

In Ruby:

"aCamelCaseWord".split /(?=[[:upper:]])/
=> ["a", "Camel", "Case", "Word"]

I'm using positive lookahead here, so that I can split the string right before each uppercase letter. This lets me save any initial lowercase part as well.

3 Comments

That's a positive lookahead, isn't it? I can't get an equivalent to work for .NET, even when I replace [[:upper:]] with [A-Z] (en.wikipedia.org/wiki/Regular_expression).
.NET regex doesn't support the POSIX character class syntax. You could use \p{Lu} instead, but [A-Z] will probably suffice. Anyway, this approach is way too simplistic. Check out the other question, especially the split regex @poly came up with. It really is that complicated.
@Pat: that Wikipedia article is not meant to be used as a reference; too general and too theoretical. This site is much more useful: regular-expressions.info
0

Check that a non-word character comes at the beginning of your regex with \W and keep the individual strings together, then split the words.

Something like: \W([A-Z][A-Za-z]+)+

For: sdcsds sd aCamelCaseWord as dasd as aSscdcacdcdc PascelCase DfsadSsdd sd Outputs:

48: PascelCase
59: DfsadSsdd

2 Comments

Hmmm. That doesn't work straight-up for .NET's regex, but maybe with a little documentation digging...
You should use \b (word boundary) to match the beginning of the word, not \W.
0
    public static string PascalCaseToSentence(string input)
    {
        if (input == null) return "";

        string output = Regex.Replace(input, @"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])", m => " " + m.Value);
        return output;
    }

Based on Shimmy's answer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.