C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[2]: CSVファイルの列数カウントについて


(過去ログ 138 を表示中)

[トピック内 8 記事 (1 - 8 表示)]  << 0 >>

■81427 / inTopicNo.1)  CSVファイルの列数カウントについて
  
□投稿者/ おすぎどん (1回)-(2016/09/21(Wed) 11:44:52)

分類:[C#] 

現在、C#でCSVファイルを読込し、列数のカウントを行うプログラムを作成しています。
最終的にはヘッダーの数とデータの数が不一致の場合は-1を返すよう考えています。

読込するCSVファイルは以下の前提条件があります。
1.データはカンマ区切り
2.ダブルコーテーションは付ける場合と付けない場合がある
3.文字コードはUTF-8かSJISかは引数で渡す
4.列数と行数は不定
5.データの中にカンマと改行を使う可能性がある
6.データはNull(空白)もある

今はA Fast CSV Reader を使い読み込ませているのですが、ヘッダーの数よりもデータの数が多い場合に
ヘッダーの数までしかデータが取得できない状況です。
上記の前提条件をクリアし、CSVを1行毎に配列へ格納する手段はないでしょうか。

現在のソースは以下へ記載いたします。
-------------------------------------------------------------------
public int GetColumns(string csvfilepath, string hasHeader, string UseUtf8)
{
    int ret = 0;
    int ColFlag = 0;

    // utf8のCSVであるかどうか
    Encoding enc = UseUtf8.Equals("1") ? new System.Text.UTF8Encoding(false) : Encoding.GetEncoding("Shift-JIS");

    using (StreamReader file = new StreamReader(File.OpenRead(csvfilepath), enc))
    {
        // CsvReaderの場合
        LumenWorks.Framework.IO.Csv.CsvReader csv = new LumenWorks.Framework.IO.Csv.CsvReader(file, false);

        //ヘッダーの列数
        int fieldCount = csv.FieldCount;

        //追加ロジック
        string info = "";
        string[] test = new string[fieldCount];

        while (csv.ReadNextRecord())
        {
            // ヘッダの要素数の配列を定義(リサイズ)
            Array.Resize(ref test, fieldCount);
            // 現在行を配列へ格納
            csv.CopyCurrentRecordTo(test);

            // 配列をリストへ展開
            List<string> vlist = new List<string>(test);
        }

        if (vlist.Count != fieldCount) {
                ColFlag = -1;
        }
    }

    return ret;
-------------------------------------------------------------------

引用返信 編集キー/
■81428 / inTopicNo.2)  Re[1]: CSVファイルの列数カウントについて
□投稿者/ Hongliang (458回)-(2016/09/21(Wed) 11:59:37)
// ちらっとソース読んだだけですけど
ええと、処理だけ見るとstring[]だのList<string>だのの出番はなさそうなのですが。
var csv = new LumenWorks.Framework.IO.Csv.CsvReader(file, false);
var columnCount = csv.FieldCount;
while (csv.ReadNextRecord())
{
    if (columnCount != csv.FieldCount)
    {
        return -1;
    }
}
return columnCount;

引用返信 編集キー/
■81429 / inTopicNo.3)  Re[2]: CSVファイルの列数カウントについて
□投稿者/ おすぎどん (2回)-(2016/09/21(Wed) 13:02:26)
No81428 (Hongliang さん) に返信
Hongliang さん
ありがとうございます。

記載いただいたロジックを試したのですが、
FieldCountはどうやらヘッダーの列数しか取れない仕様のようです。
そのため、データの要素数を各行毎に取る方法を探しています。
引用返信 編集キー/
■81430 / inTopicNo.4)  Re[3]: CSVファイルの列数カウントについて
□投稿者/ WebSurfer (1041回)-(2016/09/21(Wed) 13:25:00)
No81429 (おすぎどん さん) に返信

TextFieldParser(Microsoft が提供している Visual Basic .NET 用のクラス
ライブラリ)を使うという選択肢はないのでしょうか?

> FieldCountはどうやらヘッダーの列数しか取れない仕様のようです。
> そのため、データの要素数を各行毎に取る方法を探しています。

TextFieldParser.ReadFields メソッド(現在行のすべてのフィールドを読み込
んで文字列の配列として返し、次のデータが格納されている行にカーソルを進め
ます)というのがあって、その目的に使えそうな気がします。

昔使ったことがありますが ReadFields で各行の文字列の配列を取得できるのは
間違いなかったです。

CSV パーサー
http://surferonwww.info/BlogEngine/post/2010/10/28/CSV-parser.aspx

C# でも Microsoft.VisualBasic.dll を参照に追加してやれば使えます。何と
いっても Microsoft のライブラリなので、質問者さんの目的が果たせればこれ
を使うのが一番無難そうな気がします。

引用返信 編集キー/
■81431 / inTopicNo.5)  Re[1]: CSVファイルの列数カウントについて
□投稿者/ 魔界の仮面弁士 (904回)-(2016/09/21(Wed) 13:43:35)
> 今はA Fast CSV Reader を使い読み込ませているのですが、
ざっと見てみましたが、コメント行入り CSV にも対応した実装に
なっていますね。ソースも公開されているようで。

CSV 仕様にもよりますが、たとえばこんな感じでどうでしょう。

// CsvReader version 3.9.1.0
using (var csv = new LumenWorks.Framework.IO.Csv.CsvReader(file, true)) {
  bool hasError = false;
  csv.MissingFieldAction = LumenWorks.Framework.IO.Csv.MissingFieldAction.ReplaceByNull;
  csv.ParseError += delegate { hasError = true; }; // 列数オーバー等
  int fieldCount = csv.FieldCount;
  string[] cols = new string[fieldCount + 1];
  while (csv.ReadNextRecord() && !hasError) {
    csv.CopyCurrentRecordTo(cols);
    if(Array.IndexOf(cols, null) != fieldCount) {
     return -1; // 列数不足
    }
  }
  return hasError ? -1 : fieldCount;
}


> 最終的にはヘッダーの数とデータの数が不一致の場合は-1を返すよう考えています。
先頭行を常にヘッダーとみなす実装にするのでしょうか。
それとも、ヘッダーの有無を引数で指定するのでしょうか。

GetColumns メソッドに用意した hasHeader という
引数が使われている様子が無いですし、かといって
CsvReader の hasHeaders 引数は false 固定に
なっているようで。


> 読込するCSVファイルは以下の前提条件があります。
CSV ファイルは、
 (1) レコードの末尾に改行を伴うもの
 (2) レコードとレコードの間に改行を伴うもの
とがありますね。

前者は「aaa,bbb,ccc\r\nxxx,yyy,zzz\r\n」形式で
後者は「aaa,bbb,ccc\r\nxxx,yyy,zzz」形式です。


後者の前提で前者のCSVを受け取ってしまうと、
最後の改行の後に列数1の空文字列があるように
見えてしまいます。

かといって「空の行」を読み飛ばす仕様だと、
末尾行以外に空行を含むデータがあった時に
「データの数が不一致の場合は-1を返す仕様」を
満たせなくなってしまう可能性が生じます。


> 2.ダブルコーテーションは付ける場合と付けない場合がある
> 5.データの中にカンマと改行を使う可能性がある
「ダブルコーテーションを含んだデータ」はどのような CSV にする予定でしょうか?
たとえば『ab"cd』というデータを含めるようなケースです。

また、『「xyz」なデータと「"xyz"」なデータを区別する』必要が
ある場合に、どのような CSV を求めるのかも重要です。

その上で、ダブルコーテーションの対応が不適切な CSV データが
渡されてきた場合に、列数として何を return させるのか(-2 とか 0 とか)
あるいは何らかの例外を throw させるのかも考えておく必要がありそうです。


> 3.文字コードはUTF-8かSJISかは引数で渡す
改行コードにもご注意下さい。Shift_JIS だと CrLf 改行が多いですが、
UTF-8 の場合、CrLf だけでなく Lf 改行なデータもしばしば見かけます。

ちなみに Excel で生成されるデータの場合、
レコード間の区切りは CrLf ですが
セル内改行は Lf になりますね。


> 4.列数と行数は不定
その上で、「行によって列数の異なる CSV」が渡された場合は、
戻り値として -1 を返せば良いのですね。


> 6.データはNull(空白)もある
これは '\0' な文字が含まれるという意味でしょうか。
// Char nullChar = default(Char);

それとも、「col1,col2,,col4」のように、
空のフィールドが存在するという意味でしょうか。


> CSVを1行毎に配列へ格納する手段はないでしょうか。
提示されたコードだと、変数 vlist のスコープが間違っていませんか?
引用返信 編集キー/
■81432 / inTopicNo.6)  Re[2]: CSVファイルの列数カウントについて
□投稿者/ 魔界の仮面弁士 (905回)-(2016/09/21(Wed) 14:16:44)
No81431 (魔界の仮面弁士) に追記
> using (var csv = new LumenWorks.Framework.IO.Csv.CsvReader(file, true)) {

「ヘッダーの数とデータの数」を比較する処理ということだったので、
第二引数 hasHeaders に true をセットしていましたが…。

この CSV ライブラリだと、ヘッダー行が
VB,VB,vb,vb
のようになっていた場合に、正しく扱えないようです。


とはいえ、今回の目的である「列数のカウント」という点だけに限れば
ヘッダー行の解析は必ずしも必要では無いと思いますので、
今回はあえて、false を指定しておいた方が良さそうです。
引用返信 編集キー/
■81433 / inTopicNo.7)  Re[4]: CSVファイルの列数カウントについて
□投稿者/ おすぎどん (3回)-(2016/09/21(Wed) 14:46:44)
No81430 (WebSurfer さん) に返信

TextFieldParser で目的が達成できそうです。
まだ全ての試験が出来ていませんが、データ行の要素取得ができました。
試験を進めていきますが、一先ず解決済みにさせていただきます。

皆さま、ありがとうございました。
解決済み
引用返信 編集キー/
■81438 / inTopicNo.8)  Re[5]: CSVファイルの列数カウントについて
□投稿者/ 魔界の仮面弁士 (909回)-(2016/09/21(Wed) 18:54:13)
No81433 (おすぎどん さん) に返信
> TextFieldParser で目的が達成できそうです。

TextFieldParser.ReadFields() を使って解析する場合、
空行が圧縮される特性がありますので御注意下さい。



テスト用 CSV の作成コード
 System.IO.File.WriteAllText(csvfilepath, "Col1,Col2,Col3,Col4\r\n1-1,\"1-2a\r\n1-2b\r\n\r\n1-2d\r\n\r\n\r\n1-2g\",\"\r\n\",1-4", enc);

※以下、CrLf改行(\r\n) を星印で表記しています。


期待する解析結果/CsvReader の解析結果
 「Col1」「Col2」「Col3」「Col4」
 「1-1」「1-2a★1-2b★★1-2d★★★1-2g」「★」「1-4」


TextFieldParser の解析結果 (TrimWhiteSpace = false の場合)
 「Col1」「Col2」「Col3」「Col4」
 「1-1」「1-2a★1-2b★1-2d★1-2g」「★」「1-4」


TextFieldParser の解析結果(TrimWhiteSpace = true の場合)
 「Col1」「Col2」「Col3」「Col4」
 「1-1」「1-2a★1-2b★1-2d★1-2g」「」「1-4」



> まだ全ての試験が出来ていませんが、データ行の要素取得ができました。

解決済みチェックは付けたままにしておきます。

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -