DOBON.NET DOBON.NETプログラミング掲示板過去ログ

FileIO.TextFieldParserでCSV読込時エラーになる

環境/言語:[Windows7 VB.net]
分類:[.NET]

いつもお世話になっております。

CSVの読み込み時における、あるエラーについて質問いたします。
以下のCSVに対し、以下のVB.netのコードを実行すると、
「エラー箇所」の所でエラーが発生します。

A列, B列, C列
aaa, b, ccc
aaa, b, ccc
aaa, b, ccc
aaa, b, ccc
aaa,"b", ccc  ・・・エラー箇所
aaa, b, ccc


Dim parser as New FileIO.TextFieldParser("C:\MYCSV.csv",system.Text.Encoding.GetEncoding("Shift_JIS"))
parser.TextFieldType = FileIO.FieldType.Delimited
parser.SetDelimiters(",")
While Not parser.EndOfData
Dim row As String() = parser.ReadFields() '5行目でエラー
'処理
End While


このエラーは、CSV内に  ," が含まれる場合に、その行への読み込みがおこなわ
れる時に発生します。また、 ,d"e"f などとなっている場合は発生しません。つまり , と "  が
行内に連続する場合にのみ発生するようです。 

この現象を回避する方法は無いでしょうか? 

ちなみに以前はCSVの読込をSystem.Data.Odbc.OdbcConnection で行っていましたが、
こちらでやると各CSVに対し個別に列定義のパラメータファイルを作成しないと、
読込間違い(例えば10億以上の数値が勝手に0に変換されるなどの現象)が起こるため、
FileIO.TextFieldParserに全面的に切り替えて処理を行おうという方向性です。

宜しくお願いいたします。
■No31030に返信(kane123さんの記事)
> aaa,    b,    ccc
「aaa,b,ccc」ではなく、
「aaa,    b,    ccc」なのですね。

parser.TrimWhiteSpace は True での運用でしょうか?


> aaa,"b",    ccc  ・・・エラー箇所
何と言うエラーが発生していますか?

同じデータにしたつもりでしたが、当方ではエラーが再現しませんでした。
検証環境は .NET 4 (WOW64) です。

試しに、データを修正して
--------------
A列, B列, C列
aaa,"b",ccc
aaa,"b,ccc
aaa,b",ccc
aaa,b"c"d,efg
--------------
としてみましたが、やはりエラーにはなりませんでした。
(最終行の末尾に、改行を付けた場合も付けなかった場合も同様)

なお、上記のファイルからは下記の配列が得られました。

「A列」「B列」「C列」
「aaa」「b」「ccc」
「aaa」「b,ccc(改行)aaa,b」「ccc」
「aaa」「b"c"d」「efg」


そして、HasFieldsEnclosedInQuotes = False の場合はこうなりました。

「A列」「B列」「C列」
「aaa」「"b"」「ccc」
「aaa」「"b」「ccc」
「aaa」「b"」「ccc」
「aaa」「b"c"d」「efg」



> このエラーは、CSV内に  ,"  が含まれる場合に、その行への読み込みがおこなわ
「  ,"」ですか?(全角スペース2つ+半角カンマ1つ+ダブルクォーテーション)
先のサンプルデータには、「  」や「,"」はありましたが、
「  ,"」というデータは見当たらないようです。


> また、 ,d"e"f などとなっている場合は発生しません。
> つまり , と "  が行内に連続する場合にのみ発生するようです。 
「aaa,"b,"ccc」のようなデータになっているわけでは無いのですよね?



> こちらでやると各CSVに対し個別に列定義のパラメータファイルを作成しないと、
> 読込間違い(例えば10億以上の数値が勝手に0に変換されるなどの現象)が起こるため、

Microsoft Text Driver バージョン 6.01.7601.17632 での ODBC 接続の他、
JET 4.0 や ACE 12.0 の Text I-ISAM を OleDb 接続で試してみましたが、
10億以上が 0 変換されるパターンは再現できませんでした。


> 列定義のパラメータファイルを作成しないと、

Schema.ini を使わないパターンとしては、ImportMixedTypes 指定で
MajorityType ではなく、Text を指定しておくという手法がありますが、
それが「10億以上の数値が0になる」現象の解決になるかどうかは分かりません。
http://hanatyan.sakura.ne.jp/vb60bbs/wforum.cgi?mode=allread&no=15003&page=0



> FileIO.TextFieldParserに全面的に切り替えて処理を行おうという方向性です。
どうしても駄目なら、自前で切り出して処理するとか。
※頂いた質問にひとつずつ答えると、箇条書きの羅列になり、読みづらくなるので、
長文で答えさせて下さい。

parser.TrimWhiteSpaceは特に指定していません。
>「aaa, b, ccc」なのですね。
私がベタ書きしたCSVの部分を、自分で.txtファイルに張り付けると
(なぜかは分かりませんが)余計なスペースやTABが間に入っている
ことが分かりました。大変失礼しました。
一行目のデータはあくまで「aaa,b,ccc」です。(つまり間にスペース等無し。)
そして、エラーの起こる行のデータは「aaa,"b",ccc」です。

私の検証環境は .NET 3.5です。 
このCSVの問題については、例えば例として挙げた「10億ケタの数値が0に変換される現象」というのは、他にも文字(日本語・英字)や数値を多量に含む、4,000kb くらいのファイルで行った場合にしか再現しない、という厄介なものです。(この点を書き漏らしていてすみませんでした。)

同じようなことが、今回のファイルの「aaa,"b",ccc」でのエラーが再現しないことにも言えるのではないかと危惧しています。

上記のCSVは現象をシンプル化して伝えるために私が書いたものですが、
確かにエラーは起こりました。この点は間違いありません。エラーメッセージは
「現在の行を解析できません。」(現在、本番環境を離れているので一字一句は正確ではありませんが) です。

ですが、時間を置いて、上記CSVを実行したら、エラーが起こらないという
ことも一度だけありました。そこで、エラーを100%再現するようなCSVを作ろうとするのですが、
@大量件数である A文字長にバラツキがある B値がランダム(日本語や英数字が混在)など条件を満たさないと、(実際起こっている)本番データのエラーは再現されないような感があり、苦戦しています。 明日、職場PCにて、確実にエラーの再現しそうなCSVの作成に成功したら、このスレッドに張り付けようと思います。 

再現性の低い現象で、余計な手間をかけさせてしまって、申し訳ありません。
きちんと手がかりを提示しようと思うので、また宜しくお願いします。


>「  ,"」ですか?(全角スペース2つ+半角カンマ1つ+ダブルクォーテーション)
すみません。「,"」です。

>「aaa,"b,"ccc」のようなデータになっているわけでは無いのですよね?
そうなっているわけでは無いです。

本番環境にて、再現性のあるCSVの作成に成功しました。
以下の文字列です。
(.txtファイル(メモ帳)からブラウザへそのままコピーペーストと致します。
おそらく、これをそのままコピーペストして頂き、 「.csv」拡張子保存で、現象は再現されると思います。今度は何度も検証を行ったので間違い無いはずです。)

@エラーが起こらないパターン
COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
hoge,12456789,01,0003,,,0003,001,hoge,"HOGE",

Aエラーが起こるパターン
COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
hoge,12456789,01,0003,,,0003,001,hoge,"HOGE,

Bもうひとつ、エラーが起こるパターン
COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
hoge,123456789,01,0003,,,0003,001,hoge,"HOGE" hoge,


以上です。これから言えることは、
「,」と「,」との間に、「"」が最左端と最右端にある場合はエラーに
ならない。が、そうでない位置に「"」があり、かつ「,"」と連続している
場合はエラーが起こる。 と言ったところでしょうか?

ちなみに、昨日のレスで一語一句正確に書けませんでしたが、エラーメッセージは
「現在の区切り記号を使用して、行 2 を解析できません。」
です。


魔界の仮面弁士さんから頂いた指摘
>どうしても駄目なら、自前で切り出して処理するとか。
を参考にして考えた結果、現在、以下の方法で固めようという方向でいます。

Dim ct As Long
Dim row As String()
Dim SR As New System.IO.StreamReader([PASS], System.Text.Encoding.GetEncoding("Shift_JIS"))

Do Until SR.EndOfStream
ct += 1
If ct > 0 Then
row = SR.ReadLine.Split(",")
'処理
End If
Loop
SR.Close()
SR = Nothing

今回の質問では、再現性の悪いデータを検証不足なまま載せてしまい、
魔界の仮面弁士さんに余分な検証作業の労力をお掛けしたことを申し訳
なく思います。こちらで事前に本当に100%再現するデータになるまでサンプルを
煮詰めるべきだったと反省しています。
本当に申し訳ありませんでした。

最終的はやはり
>どうしても駄目なら、自前で切り出して処理するとか。
とのアドバイスが決め手となりました。
深謝です。
解決済みとさせて頂きます。
解決済み!
■No31039に返信(kane123さんの記事)
> 本番環境にて、再現性のあるCSVの作成に成功しました。
> 以下の文字列です。

下記のコードで読み取りましたが、エラーなく読み込めました。
.NET バージョンの違いでしょうか。

Using parser As New FileIO.TextFieldParser(csvPath, System.Text.Encoding.GetEncoding("Shift_JIS"))
    parser.TextFieldType = FileIO.FieldType.Delimited
    parser.TrimWhiteSpace = False
    parser.HasFieldsEnclosedInQuotes = False
    parser.SetDelimiters(",")
    Do Until parser.EndOfData
        Dim row() As String = parser.ReadFields()
        Dim line As String = Join(row, "|").Replace(vbCr, "{Cr}").Replace(vbLf, "{Lf}").Replace(vbTab, "{tab}")
        Console.WriteLine("[" & line & "]")
    End While
    parser.Close()
End Using



TextFieldParser を使うかどうかは別として、CSV の内容を読み解くと:

> COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
> hoge,12456789,01,0003,,,0003,001,hoge,"HOGE", 
引用符をデータの区切りとする場合は
「hoge」「12456789」「01」「0003」「」「」「0003」「001」「hoge」「HOGE」「 」
と解釈できますね。引用符もデータの一部と見なす場合はこうなるでしょう。
「hoge」「12456789」「01」「0003」「」「」「0003」「001」「hoge」「"HOGE"」「 」


> COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
> hoge,12456789,01,0003,,,0003,001,hoge,"HOGE, 
引用符をデータの区切りとする場合、引用符が奇数個なのでエラーになって欲しいところ。
引用符もデータの一部と見なす場合は、こうかな。
「hoge」「12456789」「01」「0003」「」「」「0003」「001」「hoge」「"HOGE」「 」


> COL1,COL2,COL3,COL4,COL5,COL6,COL7,COL8,COL9,COL10,COL11
> hoge,123456789,01,0003,,,0003,001,hoge,"HOGE" hoge, 
引用符をデータの区切りとする場合はエラーもしくは警告。
引用符もデータの一部と見なす場合はこんな感じで。
「hoge」「12456789」「01」「0003」「」「」「0003」「001」「hoge」「"HOGE" hoge」「 」
解決済み!
すみません、 
>.NET バージョンの違いでしょうか。

と書かれているのでそちらにばかり目が行ってしまってましたが
もう一度頂いた回答を全て精読して比較検証しましたが、
 HasFieldsEnclosedInQuotes  がキモだと判りました!

つまり、
「現在の区切り記号を使用して、行 2 を解析できません。」
が起こるのは HasFieldsEnclosedInQuotes を明示的にFalse指定
しなかった場合だと判明しました。 (.NET のバージョンは無関係です)

魔界の仮面弁士さんの初回レスを良く見てこちらで
徹底的に比較検証すれば良かったものの、見落としをしてしまって
すみませんでした。

これで、全て根本的に解決しました。
ありがとうございました。
(解決済みにします)
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板