Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

【CSV読み込み】TextFieldParserではなく、CsvParserを使用する

More than 1 year has passed since last update.

2020/02/19追記

CsvParserを使用せずとも、CsvReaderで可能でした。
CSVの解説と各プログラミング言語での実装例

CSVパース時の懸念点

CSVをパースする際、単純に1行ずつ読み込んで、カンマ「,」でSplitするだけで事足りるようなデータであればいいのですが、実際はそうともいかないので、以下のような考慮が必要になるかと思います。

※前提 区切り文字:カンマ「,」
    囲み文字:ダブルクオーテーション「"」
    各行の列数は同じ

  • データ中にカンマが存在する(区切り文字に指定した文字がある)
"a","b","c,d,e"
  • データ中にダブルクォーテーションが存在する(ダブルクォーテーションを二重化してエスケープされている)
"a","""b","c"
  • データ中に改行が存在する
"a","b
ccc","d"
  • 囲んだパターンと囲まないパターンが混在
"a",b,"c,d,e"

他には、データ中にタブ「\t」が存在する等・・・

まずTextFieldParserを使用してみましたが・・・

こういったデータを考慮しながら自前でパースしていくのはなかなか大変なので、というか私は考えることすらやめたので・・・
(いつか挑戦したい)
便利なクラスがないか昔ネットで探したところ、TextFieldParserが良さげのようでした。
TextFiledParser

.Netのクラスなので、他のオープンソースのパーサーと比べて、動作に信頼が持て、コンプライアンス的にみても良いと思っていました。

実際に上記データを読み込んでみると綺麗にパースできます。

使用するCSV

"ヘッダー1","ヘッダー2","ヘッダー3"
"a","b","c,d,e"
"a","""b","c"
"a","b
ccc","d"
"a",b,"c,d,e"

コード

using Microsoft.VisualBasic.FileIO;
using System;
using System.Linq;
using System.Text;

namespace TestProject.CSVParseTest
{
    public class CSVParseTest
    {
        public void ReadCsv()
        {
            using (var parser = new TextFieldParser(@".\test.csv", Encoding.UTF8))
            {
                // 区切り文字
                parser.Delimiters = new string[] { "," };

                // 囲み文字あり
                parser.HasFieldsEnclosedInQuotes = true;

                while (!parser.EndOfData)
                {
                    var values = parser.ReadFields();
                    values.ToList().ForEach(v =>
                    {
                        // わかりやすいようにパイプを入れる
                        Console.Write(v + "|");
                    });

                    // わかりやすいように改行
                    Console.WriteLine();
                }
            }
        }
    }
}
using System;

namespace TestProject.CSVParseTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                var pTest = new CSVParseTest();
                pTest.ReadCsv();
            }
            catch(Exception err)
            {
                Console.WriteLine(err.Message);
            }
            finally
            {
                Console.Read();
            }
        }
    }
}

image.png

上記の結果に満足していたのですが、次のようなデータの場合に、意図しない動きとなってしまいました。

使用するCSV ※データ中に空の改行が入る

"ヘッダー1","ヘッダー2","ヘッダー3"
"a","b","c,d,e"
"a","""b","c"
"a","b

gggg

ffff

ccc","d"
"a",b,"c,d,e"

結果
image.png
データ中であっても、空行が削除されてしまうようです。
掲示板などの備考欄等のテキストには、よく空行を入れてみた目を整えることがあるかとおもいます。
そういったデータを扱えなくなるのは困るので不採用としました。

CsvHelperのCsvParserクラスを使用する

他をあたることになり、人気のCsvHelperを検討していました。
CsvHelper

ただ、これを利用する場合、CSVのフォーマットに沿ったエンティティのクラスを用意する必要があり、
CSVの列数が後から変更になったりすると、都度プログラムソースも修正しなければならないと思ったので躊躇していました。

TextFiledParserのように1行分の文字を区切り文字でsplitして配列で返してくれたらなぁ・・・
かつ、空行も再現してくれたらなぁ・・・
なんて思っていたら、CsvHelper.CsvParserのReadメソッドがそれでした。

using CsvHelper;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

namespace TestProject.CSVParseTest
{
    public class CSVParseTest2
    {
        public void ReadCsv()
        {
            using(var stream = new StreamReader(@".\test.csv", Encoding.UTF8))
            using (var parser = new CsvParser(stream, CultureInfo.InvariantCulture))
            {
                string[] line;
                while ((line = parser.Read()) != null)
                {
                    line.ToList().ForEach(v =>
                    {
                        // わかりやすいようにパイプを入れる
                        Console.Write(v + "|");
                    });

                    // わかりやすいように改行
                    Console.WriteLine();
                }
            }
        }
    }
}

image.png

「TextFieldParser + 空行が削除されない」が実現できました。

おわりに

CsvHelperの中はおそらく1文字ずつ処理して、パースしているんだと思います。
いつか中身を見て勉強してみたいです。

誤認や、他の方法で解決できる等、ありましたら指摘いただけるとありがたいです。

GodPhwng
業界は7年程度です。 なーなーに理解していたところをしっかりと正しく理解したいです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away