最近のトラックバック

2017年10月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Google AdSense2

« ASP.NET(C#)でJQuery+TableSorterを利用 | トップページ | ASP.NET(C#)でファイルダウンロード »

C#でCSV・TSVファイル作成

C#でDataTableからCSVファイルまたはTSVファイルを作成してみた。



「データをカンマで区切り、改行で1レコード」という条件ででDataTableからCSVファイルを作るサンプルコード。

実行結果及びASP.NETソースコードの確認

//-------------------------------------------------------------------------------------
// データ作成
//-------------------------------------------------------------------------------------
DataTable dtData = new DataTable();
// 列作成
dtData.Columns.Add("colInt32", typeof(Int32));
dtData.Columns.Add("colInt64", typeof(Int64));
dtData.Columns.Add("colDouble", typeof(Double));
dtData.Columns.Add("colDecimal", typeof(Decimal));
dtData.Columns.Add("colNull", typeof(Int32));
dtData.Columns.Add("colDateTime", typeof(DateTime));
dtData.Columns.Add("colBoolean", typeof(Boolean));
dtData.Columns.Add("colString", typeof(String));
// データ投入
for (int i = 1; i <= 5; i++)
{
    DataRow dr = dtData.NewRow();
    dr["colInt32"] = i;
    dr["colInt64"] = i * i;
    dr["colDouble"] = 1.1 * i;
    dr["colDecimal"] = 10.1 * i;
    dr["colNull"] = DBNull.Value;
    dr["colDateTime"] = new DateTime(2010, 1, i);
    dr["colBoolean"] = (i % 2 == 0) ? true : false;
    dr["colString"] = string.Format("文字列{0}", (char)('A' + i - 1));
    dtData.Rows.Add(dr);
}
//-----------------------------------------------------------------------------------------
// CSVファイル作成
//-----------------------------------------------------------------------------------------
// 一時ファイル作成
string sTempFilePath = Path.GetTempFileName();
// CSVファイル書込
using (StreamWriter sw = new StreamWriter(sTempFilePath, false, Encoding.GetEncoding("Shift-JIS")))
{
    // 行列ループ
    for (int iRow = 0; iRow < dtData.Rows.Count; iRow++)
    {
        string sRecord = "";  // レコード
        // 1レコード作成
        for (int iCol = 0; iCol < dtData.Columns.Count; iCol++)
        {
            sRecord += string.Format("{0},", dtData.Rows[iRow][iCol]);
        }
        // 行末のカンマを削除
        sRecord = sRecord.Remove(sRecord.Length - 1, 1);
        // ファイル書込
        sw.WriteLine(sRecord);
    }
    // ファイルクローズ
    sw.Close();
}

上記ソースで作成されるCSVファイルの内容


大概は上記サンプルでも通用することが多いが、CSVとしては以下の問題がある。
  • データにカンマ、改行を含む文字列がある場合に正しく処理できない。
  • 空文字とnull文字の判定ができない。
  • Byte配列、Byte型、SByte型等の文字列変換が困難なデータ型を文字変換すると予期せぬ文字列を返す。

PS. DataTableに格納できる基本データ型
  • Boolean
  • Byte
  • Char
  • DateTime
  • Decimal
  • Double
  • Int16
  • Int32
  • Int64
  • SByte
  • Single
  • String
  • TimeSpan
  • UInt16
  • UInt32
  • UInt64
  • Byte[]
参考:DataColumn.DataType プロパティ

また使い勝手として以下が考えられる。
  • CSV(カンマ区切り)でなく、TSV(タブ区切り)を指定したい。
  • 文字コードはShift-JIS以外を指定したい。
  • 改行文字はCRLF・CR・LFのいずれかを指定したい。
  • ヘッダ行を出力有無を指定したい。
  • 最終行に改行文字が出力有無を指定したい。
  • Boolean型の出力書式は、True/Falseでなく1/0を指定したい。。
  • DateTime型の出力書式は「YYYY/MM/DD HH:mm:ss」「YYYYMMDD」等の時間書式を指定したい。


CSVファイルはRFC 4180で国際規格化されている。
しかしRFC 4180は現行仕様の追認形式をとっているため仕様が厳格な規格ではない。

Comma-Separated Values - Wikipediaを参考にRFC 4180をまとめると以下の通り。
  • ファイルは1つ以上のレコードからなる。レコードは改行で区切られる。最後のレコードの後には改行はあってもなくてもいい。
  • レコードは1つ以上の同じ個数のフィールドからなる。フィールドはコンマ「,」で区切られる。最後のフィールドの後にはコンマは付けない。
  • 1,2,となっている場合は3列目はnull文字を表す
  • ファイルの先頭には、オプションとして、通常のレコードと同一の書式の「ヘッダ行」があってもいい。ヘッダ行は、他のレコードと同じ個数のフィールドを持ち、フィールドの名称が書かれている。
  • フィールドは、ダブルクォート「"」で囲んでも囲まなくてもよい。
  • フィールドがコンマ、ダブルクォート、改行を含む場合は、かならずダブルクォートで囲む。また、フィールドに含まれるダブルクォートは2つ並べてエスケープする。


これまでの条件を満たし規格や使い勝手を考慮したDataTableからCSV/TSVファイルを作成するサンプルソース(DataTable2CharacterSeparatedValuesクラス)を記述する。

サンプルソースのpublicメソッドは以下の通り。
メソッド名 シグニチャ 概要
テキストデータファイル作成 DataTable2CharacterSeparatedValues.CreateFile
(
  DataTable dtData,
  string sDustFilePath,
  Option opt
)
CSV/TSVファイルの作成。
引数は以下の通り
dtData 出力対象のDataTable
sDustFilePath 出力ファイルパス
opt オプション(後述)

上記メソッドの第3引数のOptionクラスのフィールドは以下の通り。
フィールド シグニチャ 既定値 概要
文字コード Encoding enc Shift-JIS ファイルの文字コードを指定。
フィールド区切り文字 string sFiledSeparator カンマ(,) フィールド区切り文字を指定。
行区切り文字 string sLineBreak CRLF 行区切り文字を指定。
フィールド囲み enumFieldEnclosed enFieldEnclosed 特殊文字列を含む文字列フィールドだけ囲む フィールドをダブルクォーテーション(")で囲むか指定。
ヘッダ行の出力有無 bool bOutputHeader false ヘッダ行の出力するか指定。
ヘッダ行の列名は引数dtDataのDataColumns[n].ColumnName。
最終レコードに行区切り文字挿入 bool bLastRecordLineBreak true 最終レコードに行区切り文字を挿入するか指定。
Boolean型の出力書式 bool bBooleanTypeFormat true Boolean型の出力書式を指定。
trueの場合、True/Falseで出力。
falseの場合、1/0で出力。
DateTime型の出力書式 string sDateTimeTypeFormat yyyyMMdd HH:mm:ss DateTime型の出力書式を指定。
出力書式はDateTime.ToString()と同じ書式。

実行結果及びASP.NETソースコードの確認

using System;
using System.IO;
using System.Data;
using System.Text;

/// <summary>
/// DataTableからテキストファイル(CSV/TSV)作成クラス
/// </summary>
public class DataTable2CharacterSeparatedValues
{
    /// <summary>
    /// フィールド囲み
    /// </summary>
    public enum enumFieldEnclosed
    {
        None,                               // フィールドを囲まない
        AllField,                           // 全フィールドを囲む
        StringField,                        // 文字列フィールドだけ囲む
        ContainingSpecialCharactersField    // 特殊文字列を含む文字列フィールドだけ囲む
    }
    /// <summary>
    /// オプション
    /// </summary>
    public class Option
    {
        /// <summary>
        /// 文字コード
        /// </summary>
        public Encoding enc = Encoding.GetEncoding("Shift-JIS");
        /// <summary>
        /// フィールド区切り文字
        /// </summary>
        public string sFiledSeparator = ",";
        /// <summary>
        /// 行区切り文字
        /// </summary>
        public string sLineBreak = "\r\n";
        /// <summary>
        /// フィールド囲み
        /// </summary>
        public enumFieldEnclosed enFieldEnclosed = enumFieldEnclosed.ContainingSpecialCharactersField;
        /// <summary>
        /// ヘッダ行の出力有無
        /// </summary>
        public bool bOutputHeader = true;
        /// <summary>
        /// 最終レコードに行区切り文字挿入
        /// </summary>
        public bool bLastRecordLineBreak = true;
        /// <summary>
        /// Boolean型の出力書式(trueでTrue/False、falseで'1'/'0'と出力)
        /// </summary>
        public bool bBooleanTypeFormat = true;
        /// <summary>
        /// DateTime型の出力書式
        /// </summary>
        public string sDateTimeTypeFormat = "yyyyMMdd HH:mm:ss";
    }
    /// <summary>
    /// テキストデータファイル作成
    /// </summary>
    /// <param name="dtData">出力対象のDataTable</param>
    /// <param name="sDustFilePath">出力ファイルパス</param>
    /// <param name="opt">
    /// オプション(DataTable2TextFile.Optionクラス)
    /// enc                 : 文字コード            既定:Shift-JIS
    /// sFiledSeparator     : フィールド区切り      既定:カンマ(,)
    /// sLineBreak          : 行区切り              既定:CRLF
    /// enFieldEnclosed     : フィールド囲み        既定:特殊文字列を含む文字列フィールドだけ囲む
    /// bOutputHeader       : ヘッダ行の出力有無    既定:false
    /// bBooleanTypeFormat  : DateTime型の出力書式  既定:true(yyyyMMdd HH:mm:ss)
    /// sDateTimeTypeFormat : DateTime型の出力書式  既定:true(yyyyMMdd HH:mm:ss)
    /// </param>
    public static void CreateFile(DataTable dtData, string sDustFilePath, Option opt)
    {
        StringBuilder sbContents = new StringBuilder(1024); // ファイルコンテンツ
        string sFieldEnclosed;                              // フィールド囲み文字
        //-----------------------------------------------------------------------------------------
        // データ型チェック
        //-----------------------------------------------------------------------------------------
        foreach (DataColumn col in dtData.Columns)
        {
            // データ型取得
            Type type = col.DataType;
            // 配列型チェック(Byte[]向け)
            // ※バイト配列型は印字不可能な文字になるかもしれないので削除
            if (type.IsArray)
            {
                throw new ApplicationException("配列はフィールドに設定できません");
            }
            // バイト型(Byte/SByte)チェック
            // ※バイト型は印字不可能な文字になるかもしれないので削除
            if (type == typeof(Byte) || type == typeof(SByte))
            {
                throw new ApplicationException("バイト型はフィールドに設定できません");
            }
        }
        //-----------------------------------------------------------------------------------------
        // フィールド囲み文字
        //-----------------------------------------------------------------------------------------
        // 「全フィールドを囲む」の場合
        if (opt.enFieldEnclosed == enumFieldEnclosed.AllField)
        {
            sFieldEnclosed = "\"";
        }
        // 「フィールドを囲まない」
        // 「文字列フィールドだけ囲む」
        // 「特殊文字列を含む文字列フィールドだけ囲む」の場合
        else
        {
            sFieldEnclosed = "";
        }
        //-----------------------------------------------------------------------------------------
        // ヘッダ行作成
        //-----------------------------------------------------------------------------------------
        if (opt.bOutputHeader)
        {
            // 列名作成
            foreach (DataColumn col in dtData.Columns)
            {
                sbContents.Append(col.ColumnName);
                sbContents.Append(opt.sFiledSeparator);
            }
            // 最後のフィールド区切りを削除
            if (sbContents.Length > 0)
            {
                sbContents.Remove(sbContents.Length - opt.sFiledSeparator.Length, opt.sFiledSeparator.Length);
            }
            // 行区切り挿入
            sbContents.Append(opt.sLineBreak);
        }
        //-----------------------------------------------------------------------------------------
        // コンテンツ行作成
        //-----------------------------------------------------------------------------------------
        for (int iRow = 0; iRow < dtData.Rows.Count; iRow++)
        {
            // 列データ作成
            for (int iCol = 0; iCol < dtData.Columns.Count; iCol++)
            {
                // フィールドデータ取得
                object objField = dtData.Rows[iRow][iCol];
                // NULLの場合
                if (objField is DBNull)
                {
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 文字(String/Char)の場合
                else if (objField is String || objField is Char)
                {
                    string sField = objField.ToString();
                    switch(opt.enFieldEnclosed)
                    {
                        //「フィールドを囲まない」
                        case enumFieldEnclosed.None:
                            sbContents.Append(sField);
                            break;
                        //「全フィールドを囲む」の場合
                        case enumFieldEnclosed.AllField:
                            sField = sField.Replace("\"", "\"\"");
                            sbContents.AppendFormat("\"{0}\"", sField);
                            break;
                        //「文字列フィールドだけ囲む」の場合
                        case enumFieldEnclosed.StringField:
                            sField = sField.Replace("\"", "\"\"");
                            sbContents.AppendFormat("\"{0}\"", sField);
                            break;
                        //「特殊文字列を含む文字列フィールドだけ囲む」の場合
                        case enumFieldEnclosed.ContainingSpecialCharactersField:
                            sField = sField.Replace("\"", "\"\"");
                            if (sField.IndexOf(opt.sFiledSeparator) != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else if (sField.IndexOf(opt.sLineBreak) != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else if (sField.IndexOf("\"") != -1)
                            {
                                sbContents.AppendFormat("\"{0}\"", sField);
                            }
                            else
                            {
                                sbContents.AppendFormat("{0}", sField);
                            }
                            break;
                    }
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 真偽型(Boolean)の場合
                else if (objField is Boolean)
                {
                    string sValue;
                    // True/Falseで出力
                    if (opt.bBooleanTypeFormat)
                    {
                        sValue = ((bool)objField).ToString();
                    }
                    // 1/0で出力
                    else
                    {
                        sValue = ((bool)objField) ? "1" : "0";
                    }
                    sbContents.AppendFormat("{1}{0}{1}", sValue, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 日時(DateTime)
                else if (objField is DateTime)
                {
                    string sFormat = "{1}{0:" + opt.sDateTimeTypeFormat + "}{1}";
                    sbContents.AppendFormat(sFormat, objField, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
                // 数値型(Decimal/Double/Int16/Int32/Int64/Single/UInt16/UInt32/UInt64)
                // 日時(TimeSpan)
                else
                {
                    sbContents.AppendFormat("{1}{0}{1}", objField, sFieldEnclosed);
                    sbContents.Append(opt.sFiledSeparator);
                }
            }
            // 最後のフィールド区切りを削除
            sbContents.Remove(sbContents.Length - opt.sFiledSeparator.Length, opt.sFiledSeparator.Length);
            // 行区切り挿入
            sbContents.Append(opt.sLineBreak);
        }
        // 最終レコードの行区切り文字を削除
        if (!opt.bLastRecordLineBreak && sbContents.Length > 0)
        {
            sbContents.Remove(sbContents.Length - opt.sLineBreak.Length, opt.sLineBreak.Length);
        }
        //-----------------------------------------------------------------------------------------
        // ファイル書込
        //-----------------------------------------------------------------------------------------
        using (StreamWriter sw = new StreamWriter(sDustFilePath, false, opt.enc))
        {
            sw.Write(sbContents.ToString());
            sw.Close();
        }
    }
}




using System;
using System.Data;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        //-------------------------------------------------------------------------------------
        // 表示用データ作成
        //-------------------------------------------------------------------------------------
        DataTable dtData = new DataTable();
        // 列作成
        dtData.Columns.Add("colInt32", typeof(Int32));
        dtData.Columns.Add("colInt64", typeof(Int64));
        dtData.Columns.Add("colDouble", typeof(Double));
        dtData.Columns.Add("colDecimal", typeof(Decimal));
        dtData.Columns.Add("colNull", typeof(Int32));
        dtData.Columns.Add("colDateTime", typeof(DateTime));
        dtData.Columns.Add("colBoolean", typeof(Boolean));
        dtData.Columns.Add("colString", typeof(String));
        dtData.Columns.Add("colSpecialString", typeof(String));
        // データ投入
        for (int i = 1; i <= 5; i++)
        {
            DataRow dr = dtData.NewRow();
            dr["colInt32"] = i;
            dr["colInt64"] = i * i;
            dr["colDouble"] = 1.1 * i;
            dr["colDecimal"] = 10.1 * i;
            dr["colNull"] = DBNull.Value;
            dr["colDateTime"] = new DateTime(2010, 1, i, 23, 59, 59);
            dr["colBoolean"] = (i % 2 == 0) ? true : false;
            dr["colString"] = string.Format("文字列{0}", (char)('A' + i - 1));
            if (i % 5 == 1)
            {
                dr["colSpecialString"] = ",";
            }
            else if (i % 5 == 2)
            {
                dr["colSpecialString"] = "\t";
            }
            else if (i % 5 == 3)
            {
                dr["colSpecialString"] = "\r\n";
            }
            else if (i % 5 == 4)
            {
                dr["colSpecialString"] = "\"";
            }
            else
            {
                dr["colSpecialString"] = "";
            }
            dtData.Rows.Add(dr);
        }
        //-------------------------------------------------
        // CSV/TSVファイル作成
        //-------------------------------------------------
        DataTable2CharacterSeparatedValues.Option opt = new DataTable2CharacterSeparatedValues.Option();
        // 文字コード
        opt.enc = Encoding.GetEncoding("Shift-JIS");
        // フィールド区切り(","・"\t"のいずれか)
        opt.sFiledSeparator = ",";
        // 行区切り("\r\n"・"\r"・"\n"のいずれか)
        opt.sLineBreak = "\r\n";
        // フィールド囲み(ダブルクォーテーション(")の囲み方を指定)
        opt.enFieldEnclosed = DataTable2CharacterSeparatedValues.enumFieldEnclosed.AllField;
        // ヘッダ行を作成
        opt.bOutputHeader = true;
        // 最終レコードの後ろを改行
        opt.bLastRecordLineBreak = true;
        // Boolean型の出力形式(true:True/False or false:1/0)
        opt.bBooleanTypeFormat = false;
        // DateTime型の出力書式
        opt.sDateTimeTypeFormat = "yyyy/MM/dd hh:mm:ss";
        // ファイル作成
        string sTempFilePath = @"c:\temp\test.csv";
        DataTable2CharacterSeparatedValues.CreateFile(dtData, sTempFilePath, opt);
    }
}


上記ソースで作成されるCSVファイルの内容


  • RFC 4180
  • Comma-Separated Values - Wikipe
  • DataColumn.DataType プロパティ

  • « ASP.NET(C#)でJQuery+TableSorterを利用 | トップページ | ASP.NET(C#)でファイルダウンロード »

    ASP.NETサンプル」カテゴリの記事

    C#サンプル」カテゴリの記事

    コメント

    コメントを書く

    (ウェブ上には掲載しません)

    トラックバック

    この記事のトラックバックURL:
    http://app.cocolog-nifty.com/t/trackback/532391/45475513

    この記事へのトラックバック一覧です: C#でCSV・TSVファイル作成:

    « ASP.NET(C#)でJQuery+TableSorterを利用 | トップページ | ASP.NET(C#)でファイルダウンロード »

    Google AdSense


    • ---

    Amazon ウィジェット

    • ウィジェット

    @niyo_naのツイート

    無料ブログはココログ

    Google Analytics