Logo wizaman's blog (legacy)

キャメルケースを分割する

October 13, 2017
3 min read
Table of Contents

タイトルの通り、キャメルケースの分割(split)をします。実装はC#。

正規表現で頑張る方法もあるようですが、ちょっとややこしいのとパフォーマンス的に嬉しくないことは容易に想像できるので、愚直な方法で実装します。

厳密なキャメルケースであれば、IDのような単語は、idやIdとなると思いますが、そのままIDと書くこともあるため、そのパターンにも対応します。スネークケースとの混用も対応します。先頭と末尾のアンダースコアは無視します。

例えば、_abcFOOBar123_456hoge__HOGE_ならばabcFOOBar123456hogeHOGEに分割します。

実装

識別子として有効な文字種は、アルファベット大/小、数字、アンダースコアに限られるとして、これら文字種をenumで定義します。charの拡張メソッドで文字種判定を実装し、キャメルケース分割はstringの拡張メソッドとして実装します。

文字種判定はIsDigit()やIsUpper()、IsLower()が標準機能としてあるものの、ここではASCIIだけ見たいのにUnicodeでチェックされるのがイマイチなので自作処理で判定します。

想定外の文字が入力された時の動作は不定です。デバッグ時はとりあえずアサートにかかるようにしました(Unityのアサートなので都合悪ければいいように置き換えてください)。

public static class CharExtension
{
    public static bool isNumber(this char c)
    {
        return '0' <= c && c <= '9';
    }
 
    public static bool isAlphabet(this char c)
    {
        return isLowerAlphabet(c) || isUpperAlphabet(c);
    }
    public static bool isLowerAlphabet(this char c)
    {
        return 'a' <= c && c <= 'z';
    }
    public static bool isUpperAlphabet(this char c)
    {
        return 'A' <= c && c <= 'Z';
    }
}
 
public static class StringExtension
{
    private enum IdCharType
    {
        Invalid,
        LowerAlphabet,
        UpperAlphabet,
        Number,
        Underscore,
    }
 
    public static List<string> splitCamelCase(this string str)
    {
        var length = str.Length;
        var texts = new List<string>();
 
        var last = IdCharType.Invalid;
        var start = 0;
 
        bool upperSequence = false;
 
        for(var i = 0; i < length; ++i) {
            var c = str[i];
            var current = c.toCharType();
 
            Assert.IsTrue(current != IdCharType.Invalid, "Invalid character.");
 
            if(i == 0) {
                last = current;
                continue;
            }
 
            if(last == IdCharType.UpperAlphabet) {
                if(current == IdCharType.UpperAlphabet) {
                    upperSequence = true;
                    continue;
                }
                if(upperSequence) {
                    upperSequence = false;
                    if(current == IdCharType.Underscore) {
                        texts.Add(str.Substring(start, i - start));
                        start = i;
                    }
                    else {
                        texts.Add(str.Substring(start, i - start - 1));
                        start = i - 1;
                    }
                    last = current;
                    continue;
                }
                if(current == IdCharType.LowerAlphabet) {
                    last = current;
                    continue;
                }
            }
            upperSequence = false;
 
            if(last == IdCharType.Underscore) {
                start = i;
                last = current;
                continue;
            }
 
            if(last != current) {
                texts.Add(str.Substring(start, i - start));
                start = i;
            }
 
            last = current;
        }
 
        if(start < length && last != IdCharType.Underscore) {
            texts.Add(str.Substring(start, length - start));
        }
 
        return texts;
    }
 
    private static IdCharType toCharType(this char c)
    {
        if(c.isLowerAlphabet()) return IdCharType.LowerAlphabet;
        if(c.isUpperAlphabet()) return IdCharType.UpperAlphabet;
        if(c.isNumber()) return IdCharType.Number;
        if(c == '_') return IdCharType.Underscore;
        return IdCharType.Invalid;
    }
}