2014年4月6日日曜日

[C#]CSVファイル読み込み

.NET で CSV ファイルを読み込むにはいくつかの方法があります。それぞれに癖があるので、ちょっと纏めてみました。

TextReader を使う
単純にファイルを行単位で読み込んで、カンマで分割します。

    using (TextReader reader = new StreamReader(fileStream, Encoding.UTF8))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            string[] data = line.Split(',');
        }
    }



問題はダブルクォーテーションでくくられた文字列内に改行文字やカンマが入っているようなデータを扱う場合は崩れてしまいます。

TextFieldParser を使う
Microsoft.VisualBasic.FileIO を参照設定に追加する必要はありますが、これを使えば TextReader の問題は回避出来ます。


    using (TextFieldParser parser = new TextFieldParser(filename, Encoding.GetEncoding("shift-jis")))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");

        // ヘッダがある場合は下記コメントを解除
        //if (!parser.EndOfData)
        //{
        //    // 無視
        //    string[] data = parser.ReadFields();
        //}

        // 行ごとにデータを読み込む
        while (!parser.EndOfData)
        {
            string[] data = parser.ReadFields();
        }

    }

ただ、これにも問題はあって、文字列の先頭に空白や 0 があると丸められてしまいます。Excel で開いた時と同じ感じです。

OLE DB Provider を使う
これを使えば SELECT 文でファイルを読み込めますが、列の型が不安定だし、今だと 64bit
版ではドライバが標準搭載されていないので、ちょっと論外ですねぇ。コードは割愛させてもらいます。

ゴリゴリ書く
適当にゴリゴリ書いてきました。
CSV文字列データを二次元のリストにしています。
空文字は NULL 扱いです。
先頭文字で改行なのか空文字なのか文字列なのか数値なのかを判定しています。
ちょっと長いです。

    /// <summary>
    /// CSVデータパース
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static List<List<string>> parse(string data)
    {
        var rows = new List<List<string>>();
        var cells = new List<string>();

        while (data.Length != 0)
        {
            if (data.StartsWith(Environment.NewLine))
            {
                // 改行位置の場合、行を追加
                rows.Add(cells);
                cells = new List<string>();
                data = data.Substring(2, data.Length - 2);
            }
            else if (data[0] == ',')
            {
                // 先頭がカンマの場合、NULL
                cells.Add(null);
                data = data.Substring(1, data.Length - 1);
            }
            else if (data[0] == '"')
            {
                // 先頭がダブルコーテーションの場合、次のダブルコーテーション+カンマまでを文字列と見なす
                var newLine = data.IndexOf("\"" + Environment.NewLine);
                var index = data.IndexOf("\",");
                if (index != -1)
                {
                    if (newLine != -1)
                    {
                        if (index < newLine)
                        {
                            cells.Add(data.Substring(1, index - 1));
                            index = index + 2;
                            data = data.Substring(index, data.Length - index);
                        }
                        else
                        {
                            cells.Add(data.Substring(1, newLine - 1));
                            newLine = newLine + 3;
                            data = data.Substring(newLine, data.Length - newLine);

                            // 改行
                            rows.Add(cells);
                            cells = new List<string>();
                        }
                    }
                    else
                    {
                        cells.Add(data.Substring(1, index - 1));
                        index = index + 2;
                        data = data.Substring(index, data.Length - index);
                    }
                }
                else
                {
                    if (newLine != -1)
                    {
                        cells.Add(data.Substring(1, newLine - 1));
                        newLine = newLine + 3;
                        data = data.Substring(newLine, data.Length - newLine);

                        // 改行
                        rows.Add(cells);
                        cells = new List<string>();
                    }
                    else
                    {
                        throw new ArgumentException("文字列の終端が特定できません");
                    }
                }
            }
            else
            {
                // 上記以外は次のカンマまでを文字列とする
                var newLine = data.IndexOf(Environment.NewLine);
                var index = data.IndexOf(",");
                if (index != -1)
                {
                    if (index < newLine)
                    {
                        cells.Add(data.Substring(0, index));
                        index = index + 1;
                        data = data.Substring(index, data.Length - index);
                    }
                    else
                    {
                        cells.Add(data.Substring(0, newLine));
                        newLine = newLine + 2;
                        data = data.Substring(newLine, data.Length - newLine);

                        // 改行
                        rows.Add(cells);
                        cells = new List<string>();
                    }
                }
                else
                {
                    // 最終データ
                    cells.Add(data.Substring(0, data.Length - 1));
                    data = "";
                    rows.Add(cells);
                }
            }
        }

        return rows;
    }

ごめんなさい、ちゃんと試験してないのでコピペは避けてください。
あと、きっと StringBuilder 使った方が早いです。

まとめ
微妙にかゆいところに手が届きません。入力されるデータの形式でどう読み込むかを使い分けるしか無いですねぇ。

関連

0 件のコメント:

コメントを投稿