C#でCSVファイルを読み込む

C#でCSVファイルを読み込む

C#でCSVファイルを読み込む

皆さんはCSVファイルを読み込んでデータ処理をするとき、どのようにファイル読み込みを行っていますでしょうか。
通常は下記のようなサンプルを流用し、ファイルを1行ずつ読み込み、Split関数などで分割することが多いかと思います。

 private static List ReadFile(string path)
 {
    List list = new List();
    using (StreamReader sr = new StreamReader(path))
    {
        string line = "";
        while ((line = sr.ReadLine()) != null)
        {
            list.Add(line);
        }
    }
    return list;
 }
しかし、CSVファイルはカンマ区切りだからといって、Split関数で分割してもうまく行かない場合があるのです。
CSVファイルは下記のようにデータ1つ1つをダブルクォーテーションで囲い、この中にカンマが合った場合でもそれを区切り文字としては扱わず1つのデータ(文字列)として扱う場合があります。
 "りんご","ぶどう","みかん,もも","かき"
こういった場合に、カンマでSplit関数を使用してしまうと、意図しない場所でデータが区切られ、データの整合性が合わなくなることがあります。

なので今回は、1つのデータをダブルクォーテーションで囲ったケースでも正常に読み取れるように自作のクラスを作成していきたいと思います。
独学故、「もっとこうするべき」といった意見があれば是非ご連絡ください!


自作メソッドの作成

さっそくコーディングをしていきます。
今回の方針としては、先程のサンプルを用いてファイルデータをListに格納。そのListsから1行ずつ取り出し、さらにそこから1文字ずつ判断をしていく動きにします。
その中で、区切り文字ダブルクォーテーションを判定し、挙動を変えていきます。

いきなりダブルクォーテーションを考慮すると難しくなるので、まずはSplit関数を使わない方法で作成します。

 public static List GetCSV(string path, char separate=',', string encoding="utf-8") 
 {
    var returnList = new List();
    var list = ReadFile(path, encoding);
    foreach(var line in list)
    {
        var separateList = new List();
        StringBuilder sb = new StringBuilder();

        foreach(char c in line)
        {
            if(c == separate)
            {  
                separateList.Add(sb.ToString());
                sb = new StringBuilder();
            }
            else
            {
                sb.Append(c);
            }
        }

        if(sb.Length != 0)
        {
            separateList.Add(sb.ToString());
        }

        var strArray = new string[separateList.Count];
        foreach (var item in separateList.Select((value, index) => new { value, index }))
        {
            strArray[item.index] = item.value;
        }
        returnList.Add(strArray);
    }
    return returnList;
 }
取り出した文字がカンマ出ない場合は、StringBuilderを使って文字列に結合していきます。このStringBuilderで作成した文字列が1つのデータになります。
区切り文字であるカンマを検知した際には、StringBuilder文字列をListに格納していきます。このListは1行文のデータを貯めています。
1行分の処理が終わった際に、データを配列に格納しなおし、戻り値用のListに追加。この処理を全ての行分処理をさせます。

これでSplit関数を使用しないで、カンマ区切りを処理できるようになりました。

続いては、ダブルクォーテーションを1項目とする処理を追加していきます。
反映したメソッドは下記になります。

 public static List GetCSV(string path, char separate=',', string encoding="utf-8")
 {
    var returnList = new List();
    var list = ReadFile(path, encoding);

    var separateList = new List();
    StringBuilder sb = new StringBuilder();
    bool isDoubleQuotation = false;
    foreach(var line in list)
    {
        foreach(char c in line)
        {
            if(c == separate && !isDoubleQuotation)
            {  
                separateList.Add(sb.ToString());
                sb = new StringBuilder();
            }
            else
            {
                sb.Append(c);
                if(c == '"')
                {
                    isDoubleQuotation = !isDoubleQuotation;
                }
            }
        }

        if(sb.Length != 0 && !isDoubleQuotation)
        {
            separateList.Add(sb.ToString());
            sb = new StringBuilder();

            var strArray = new string[separateList.Count];
            foreach (var item in separateList.Select((value, index) => new { value, index }))
            {
                strArray[item.index] = item.value;
            }
            separateList = new List();
            returnList.Add(strArray);
        }
    }
    return returnList;
 }    
先程カンマを検知していたif文の条件にダブルクォーテーション内かの判断を追加します。
このisDoubleQuotationは、ダブルクォーテーション内の文字列を扱っているかを判断する変数となっており、文字がカンマでない際に判定をさせます。
また、カンマ内で改行が入った際も処理を終わらせず1つのデータを処理しているようにするため、今までStringBuilder文字列をList追加していた処理をダブルクォーテーション内判定をする変数で制御させます。
こうすることで改行が入って次の行の処理に入った際にも、StringBuilderを継続させることが可能になります。


動作確認

作成した自作メソッドを検証するため、下記csvファイルを用意しておき動作確認を実施します。

 "A","B","C","D"
 "あいうえお","かきくけこ","さしすせそ","たちつ
 てと"
 "なにぬねの","はひふへほ","まみむめも","やゆ,よ"
 "あいうえお","かきくけこ","さしすせそ","たちつ
 てと"
動作確認用のプログラムは下記で、実行した際に出力される内容を確認します。
データは空白文字で分けて表示させます。
 using CSV4net;

 var list = ReadingFile.GetCSV("test.csv");
 foreach (var item in list.Select((value, index) => new { value, index }))
 {
    Console.Write(item.index + ":");
    foreach(var str in item.value)
    {
        Console.Write(str + " ");
    }
    Console.WriteLine("");
 } 
このプログラムの出力は下記のようになり、ダブルクォーテーション内のカンマや改行も1つのデータ内として判定されるようになります。
※ダブルクォーテーション内の改行はなくなる仕様です。
 0:"A" "B" "C" "D" 
 1:"あいうえお" "かきくけこ" "さしすせそ" "たちつてと" 
 2:"なにぬねの" "はひふへほ" "まみむめも" "やゆ,よ" 
 3:"あいうえお" "かきくけこ" "さしすせそ" "たちつてと"        
正常に処理できていますね!


さいごに

今回はC#でCSVファイルのデータを処理するプログラムを作成しました。
通常はSplit関数で処理をさせますが、ダブルクォーテーションで囲っているCSVも存在するため、その場合であっても処理できるように組んでいます。
プログラムはGitに公開していますので、もしアドバイスあればコメント下さい!!
今回はこの辺で、ではまた!!

改修版はこちらで紹介しています。

コメント

このブログの人気の投稿

PowerAppsで座席表を作成する

Power AutomateでTeamsのキーワードをトリガーにする

Power Automateで文字列抽出