CSV文件解析(转)

来源:互联网 发布:kc网络电话软件 编辑:程序博客网 时间:2024/05/22 07:06

http://dobon.net/vb/dotnet/file/readcsvfile.html

Jet ProviderやODBC Providerを使う方法

さて、ここから本題に入ります。まずは、Jet ProviderやODBC Providerを使う方法を紹介します。これらを使って、CSVファイルを解析することができます。

以下にJet Providerを使った例を紹介します。ここでは解析されたCSVファイルの内容をDataTableに格納しています。CSVファイルの文字コードは、Shift JISである必要があります。なお、接続文字列の「HDR=No」を「HDR=Yes」とすることにより、一行目をヘッダとすることができます。

[VB.NET]'CSVファイルのあるフォルダDim csvDir As String = "C:/"'CSVファイルの名前Dim csvFileName As String = "test.csv"'接続文字列Dim conString As String = _    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" _    + csvDir + ";Extended Properties=""text;HDR=No;FMT=Delimited"""Dim con As New System.Data.OleDb.OleDbConnection(conString)Dim commText As String = "SELECT * FROM [" + csvFileName + "]"Dim da As New System.Data.OleDb.OleDbDataAdapter(commText, con)'DataTableに格納するDim dt As New DataTableda.Fill(dt)
[C#]//CSVファイルのあるフォルダstring csvDir = @"C:/";//CSVファイルの名前string csvFileName = "test.csv";//接続文字列string conString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="    + csvDir + ";Extended Properties=/"text;HDR=No;FMT=Delimited/"";System.Data.OleDb.OleDbConnection con =    new System.Data.OleDb.OleDbConnection(conString);string commText = "SELECT * FROM [" + csvFileName + "]";System.Data.OleDb.OleDbDataAdapter da =    new System.Data.OleDb.OleDbDataAdapter(commText, con);//DataTableに格納するDataTable dt = new DataTable();da.Fill(dt);

次はODBC Provider(Microsoft Text Driver)を使った例です。.NET Framework 1.1以降で使用できます。なおこの場合は、一行目がヘッダとして処理されます。

[VB.NET]'CSVファイルのあるフォルダDim csvDir As String = "C:/"'CSVファイルの名前Dim csvFileName As String = "test.csv"'接続文字列Dim conString As String = _    "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" _    + csvDir + ";Extensions=asc,csv,tab,txt;"Dim con As New System.Data.Odbc.OdbcConnection(conString)Dim commText As String = "SELECT * FROM [" + csvFileName + "]"Dim da As New System.Data.Odbc.OdbcDataAdapter(commText, con)'DataTableに格納するDim dt As New DataTableda.Fill(dt)
[C#]//CSVファイルのあるフォルダstring csvDir = @"C:/";//CSVファイルの名前string csvFileName = "test.csv";//接続文字列string conString = "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq="    + csvDir + ";Extensions=asc,csv,tab,txt;";System.Data.Odbc.OdbcConnection con =    new System.Data.Odbc.OdbcConnection(conString);string commText = "SELECT * FROM [" + csvFileName + "]";System.Data.Odbc.OdbcDataAdapter da =    new System.Data.Odbc.OdbcDataAdapter(commText, con);//DataTableに格納するDataTable dt = new DataTable();da.Fill(dt);

このような方法では、正しくCSVファイルが解析されるようにするには、Schema.iniを用意しておく必要があります。そうしないと、正しく解釈されない可能性が十分にあります。

Schema.iniファイルの作成法については、「Importing CSV Data and saving it in database」や「[AC97]VBAから Schema.ini ファイルを作成する方法」が参考になります。

正規表現を使った方法

次に正規表現を使った方法を紹介します。「Perlメモ」の「CSV形式の行から値のリストを取り出す」や「値に改行コードを含むCSV形式を扱う」が参考になります。 これによると、CSVの一つの行(一つのレコード)からフィールドを取り出すには、レコードの最後にカンマをつけてから、

("(?:[^"]|"")*"|[^,]*),

というパターンを使用します。

また、フィールドに改行コードを含む場合にCSV形式の文字列から一つのレコードを取り出すには、まず一行を取り出してから、その文字列内の「"」の数を数え、奇数であれば次の一行を追加します。これを「"」の数が偶数になるまで繰り返し、偶数になったところで、一つのレコードが取り出せたものとします。

フィールドを取り出すためのパターンは、他にもいろいろ考えられます。例えば、一つのレコードからフィールドを取り出すためのパターンとしては、

,(?=([^/"]*"[^"]*")*(?![^"]*"))

(?:^|,)(/"(?:[^/"]+|/"/")*/"|[^,]*)

などが「CSV Regex Pattern」や「RegEx for CSV」で紹介されています。

「Perlメモ」による方法を参考にしたサンプルを以下に紹介します。ここでは、先のCSV規則の3が適切に処理されるように、「Perlメモ」のパターンに手を加えています。また、一行取り出すためにも、正規表現を使っています。取り出したフィールドは、レコードごとにArrayListに格納し、これらをさらにArrayListに格納しています(実用的ではありませんが、あくまでCSVの解析のサンプルということで、ご理解ください)。CSVにはヘッダが無く、すべてのフィールドを文字列として取得します。CSV形式のファイルを解析する場合は、その内容をString型に読み込んでからメソッドを呼び出してください。

[VB.NET]''' <summary>''' CSVをArrayListに変換''' </summary>''' <param name="csvText">CSVの内容が入ったString</param>''' <returns>変換結果のArrayList</returns>Public Shared Function CsvToArrayList1(ByVal csvText As String) _    As System.Collections.ArrayList    Dim csvRecords As New System.Collections.ArrayList    '前後の改行を削除しておく    csvText = csvText.Trim( _        New Char() {ControlChars.Cr, ControlChars.Lf})    '一行取り出すための正規表現    Dim regLine As New System.Text.RegularExpressions.Regex( _        "^.*(?:/n|$)", _        System.Text.RegularExpressions.RegexOptions.Multiline)    '1行のCSVから各フィールドを取得するための正規表現    Dim regCsv As New System.Text.RegularExpressions.Regex( _        "/s*(""(?:[^""]|"""")*""|[^,]*)/s*,", _        System.Text.RegularExpressions.RegexOptions.None)    Dim mLine As System.Text.RegularExpressions.Match = _        regLine.Match(csvText)    While mLine.Success        '一行取り出す        Dim line As String = mLine.Value        '改行記号が"で囲まれているか調べる        While CountString(line, """") Mod 2 = 1            mLine = mLine.NextMatch()            If Not mLine.Success Then                Throw New ApplicationException("不正なCSV")            End If            line += mLine.Value        End While        '行の最後の改行記号を削除        line = line.TrimEnd( _            New Char() {ControlChars.Cr, ControlChars.Lf})        '最後に「,」をつける        line += ","        '1つの行からフィールドを取り出す        Dim csvFields As New System.Collections.ArrayList        Dim m As System.Text.RegularExpressions.Match = _            regCsv.Match(line)        While m.Success            Dim field As String = m.Groups(1).Value            '前後の空白を削除            field = field.Trim()            '"で囲まれている時            If field.StartsWith("""") And field.EndsWith("""") Then                '前後の"を取る                field = field.Substring(1, field.Length - 2)                '「""」を「"」にする                field = field.Replace("""""", """")            End If            csvFields.Add(field)            m = m.NextMatch()        End While        csvFields.TrimToSize()        csvRecords.Add(csvFields)        mLine = mLine.NextMatch()    End While    csvRecords.TrimToSize()    Return csvRecordsEnd Function''' <summary>''' 指定された文字列内にある文字列が幾つあるか数える''' </summary>''' <param name="strInput">strFindが幾つあるか数える文字列</param>''' <param name="strFind">数える文字列</param>''' <returns>strInput内にstrFindが幾つあったか</returns>Public Shared Function CountString( _    ByVal strInput As String, _    ByVal strFind As String) As Integer    Dim foundCount As Integer = 0    Dim sPos As Integer = strInput.IndexOf(strFind)    While sPos > -1        foundCount += 1        sPos = strInput.IndexOf(strFind, sPos + 1)    End While    Return foundCountEnd Function
[C#]/// <summary>/// CSVをArrayListに変換/// </summary>/// <param name="csvText">CSVの内容が入ったString</param>/// <returns>変換結果のArrayList</returns>public static System.Collections.ArrayList CsvToArrayList1(string csvText){    System.Collections.ArrayList csvRecords =        new System.Collections.ArrayList();    //前後の改行を削除しておく    csvText = csvText.Trim(new char[] {'/r', '/n'});    //一行取り出すための正規表現    System.Text.RegularExpressions.Regex regLine =        new System.Text.RegularExpressions.Regex(        "^.*(?://n|$)",        System.Text.RegularExpressions.RegexOptions.Multiline);    //1行のCSVから各フィールドを取得するための正規表現    System.Text.RegularExpressions.Regex regCsv =        new System.Text.RegularExpressions.Regex(        "//s*(/"(?:[^/"]|/"/")*/"|[^,]*)//s*,",        System.Text.RegularExpressions.RegexOptions.None);    System.Text.RegularExpressions.Match mLine = regLine.Match(csvText);    while (mLine.Success)    {        //一行取り出す        string line = mLine.Value;        //改行記号が"で囲まれているか調べる        while ((CountString(line, "/"") % 2) == 1)        {            mLine = mLine.NextMatch();            if (!mLine.Success)            {                throw new ApplicationException("不正なCSV");            }            line += mLine.Value;        }        //行の最後の改行記号を削除        line = line.TrimEnd(new char[] {'/r', '/n'});        //最後に「,」をつける        line += ",";        //1つの行からフィールドを取り出す        System.Collections.ArrayList csvFields =            new System.Collections.ArrayList();        System.Text.RegularExpressions.Match m = regCsv.Match(line);        while (m.Success)        {            string field = m.Groups[1].Value;            //前後の空白を削除            field = field.Trim();            //"で囲まれている時            if (field.StartsWith("/"") && field.EndsWith("/""))            {                //前後の"を取る                field = field.Substring(1, field.Length - 2);                //「""」を「"」にする                field = field.Replace("/"/"", "/"");            }            csvFields.Add(field);            m = m.NextMatch();        }        csvFields.TrimToSize();        csvRecords.Add(csvFields);        mLine = mLine.NextMatch();    }    csvRecords.TrimToSize();    return csvRecords;}/// <summary>/// 指定された文字列内にある文字列が幾つあるか数える/// </summary>/// <param name="strInput">strFindが幾つあるか数える文字列</param>/// <param name="strFind">数える文字列</param>/// <returns>strInput内にstrFindが幾つあったか</returns>public static int CountString(string strInput, string strFind){    int foundCount = 0;    int sPos = strInput.IndexOf(strFind);    while (sPos > -1)    {        foundCount++;        sPos = strInput.IndexOf(strFind, sPos + 1);    }    return foundCount;}

文字列を独自に解析する方法

最後に紹介するのは、文字列を独自に解析する方法です。面倒ですが、これが一番速いかもしれません。

以下にその例を示します。使い方は先のCsvToArrayList1メソッドと同じです。

補足:テストが十分でないため、間違いがあるかもしれません。不具合を発見された方は、ぜひご報告ください。

[VB.NET]''' <summary>''' CSVをArrayListに変換''' </summary>''' <param name="csvText">CSVの内容が入ったString</param>''' <returns>変換結果のArrayList</returns>Public Shared Function CsvToArrayList2(ByVal csvText As String) _    As System.Collections.ArrayList    '前後の改行を削除しておく    csvText = csvText.Trim( _        New Char() {ControlChars.Cr, ControlChars.Lf})    Dim csvRecords As New System.Collections.ArrayList    Dim csvFields As New System.Collections.ArrayList    Dim csvTextLength As Integer = csvText.Length    Dim startPos As Integer = 0    Dim endPos As Integer = 0    Dim field As String = ""    While True        '空白を飛ばす        While startPos < csvTextLength _            AndAlso (csvText.Chars(startPos) = " "c _            OrElse csvText.Chars(startPos) = ControlChars.Tab)            startPos += 1        End While        'データの最後の位置を取得        If startPos < csvTextLength _            AndAlso csvText.Chars(startPos) = ControlChars.Quote Then            '"で囲まれているとき            '最後の"を探す            endPos = startPos            While True                endPos = csvText.IndexOf(ControlChars.Quote, endPos + 1)                If endPos < 0 Then                    Throw New ApplicationException("""が不正")                End If                '"が2つ続かない時は終了                If endPos + 1 = csvTextLength OrElse _                    csvText.Chars((endPos + 1)) <> ControlChars.Quote Then                    Exit While                End If                '"が2つ続く                endPos += 1            End While            '一つのフィールドを取り出す            field = csvText.Substring(startPos, endPos - startPos + 1)            '""を"にする            field = field.Substring(1, field.Length - 2). _                Replace("""""", """")            endPos += 1            '空白を飛ばす            While endPos < csvTextLength AndAlso _                csvText.Chars(endPos) <> ","c AndAlso _                csvText.Chars(endPos) <> ControlChars.Lf                endPos += 1            End While        Else            '"で囲まれていない            'カンマか改行の位置            endPos = startPos            While endPos < csvTextLength AndAlso _                csvText.Chars(endPos) <> ","c AndAlso _                csvText.Chars(endPos) <> ControlChars.Lf                endPos += 1            End While            '一つのフィールドを取り出す            field = csvText.Substring(startPos, endPos - startPos)            '後の空白を削除            field = field.TrimEnd()        End If        'フィールドの追加        csvFields.Add(field)        '行の終了か調べる        If endPos >= csvTextLength OrElse _            csvText.Chars(endPos) = ControlChars.Lf Then            '行の終了            'レコードの追加            csvFields.TrimToSize()            csvRecords.Add(csvFields)            csvFields = New System.Collections.ArrayList( _                csvFields.Count)            If endPos >= csvTextLength Then                '終了                Exit While            End If        End If        '次のデータの開始位置        startPos = endPos + 1    End While    csvRecords.TrimToSize()    Return csvRecordsEnd Function
[C#]/// <summary>/// CSVをArrayListに変換/// </summary>/// <param name="csvText">CSVの内容が入ったString</param>/// <returns>変換結果のArrayList</returns>public static System.Collections.ArrayList CsvToArrayList2(string csvText){    //前後の改行を削除しておく    csvText = csvText.Trim(new char[] {'/r', '/n'});    System.Collections.ArrayList csvRecords =        new System.Collections.ArrayList();    System.Collections.ArrayList csvFields =        new System.Collections.ArrayList();    int csvTextLength = csvText.Length;    int startPos = 0, endPos = 0;    string field = "";    while (true)    {        //空白を飛ばす        while (startPos < csvTextLength &&            (csvText[startPos] == ' ' || csvText[startPos] == '/t'))        {            startPos++;        }        //データの最後の位置を取得        if (startPos < csvTextLength && csvText[startPos] == '"')        {            //"で囲まれているとき            //最後の"を探す            endPos = startPos;            while (true)            {                endPos = csvText.IndexOf('"', endPos + 1);                if (endPos < 0)                {                    throw new ApplicationException("/"が不正");                }                //"が2つ続かない時は終了                if (endPos + 1 == csvTextLength || csvText[endPos + 1] != '"')                {                    break;                }                //"が2つ続く                endPos++;            }            //一つのフィールドを取り出す            field = csvText.Substring(startPos, endPos - startPos + 1);            //""を"にする            field = field.Substring(1, field.Length - 2).Replace("/"/"", "/"");            endPos++;            //空白を飛ばす            while (endPos < csvTextLength &&                csvText[endPos] != ',' && csvText[endPos] != '/n')            {                endPos++;            }        }        else        {            //"で囲まれていない            //カンマか改行の位置            endPos = startPos;            while (endPos < csvTextLength &&                csvText[endPos] != ',' && csvText[endPos] != '/n')            {                endPos++;            }            //一つのフィールドを取り出す            field = csvText.Substring(startPos, endPos - startPos);            //後の空白を削除            field = field.TrimEnd();        }        //フィールドの追加        csvFields.Add(field);        //行の終了か調べる        if (endPos >= csvTextLength || csvText[endPos] == '/n')        {            //行の終了            //レコードの追加            csvFields.TrimToSize();            csvRecords.Add(csvFields);            csvFields = new System.Collections.ArrayList(                csvFields.Count);            if (endPos >= csvTextLength)            {                //終了                break;            }        }        //次のデータの開始位置        startPos = endPos + 1;    }    csvRecords.TrimToSize();    return csvRecords;}

第三者の作成したクラス

このようなコードを自分で書かなくても、すでに優秀なクラスが多く存在します。最後に、その内幾つかを以下に紹介しておきます。(上の2つが代表的なものです。)

.NET Framework 2.0以降で、TextFieldParserクラスを使用する方法

.NET Framework 2.0からは、VB.NET用のクラスとして、TextFieldParserクラスが追加されました。これを使えば、CSVファイルの解析も楽になります。

以下に例を示します。文字コードを指定しているEncodingに関しては、「文字コードを指定してテキストファイルを読み込む」を参考にしてください。C#では参照に「Microsoft.VisualBasic.dll」を追加する必要があります。

[VB.NET]Dim csvRecords As New System.Collections.ArrayList()'CSVファイル名Dim csvFileName As String = "C:/test.csv"'Shift JISで読み込むDim tfp As New FileIO.TextFieldParser(csvFileName, _    System.Text.Encoding.GetEncoding(932))'フィールドが文字で区切られているとする'デフォルトでDelimitedなので、必要なしtfp.TextFieldType = FileIO.FieldType.Delimited'区切り文字を,とするtfp.Delimiters = New String() {","}'フィールドを"で囲み、改行文字、区切り文字を含めることができるか'デフォルトでtrueなので、必要なしtfp.HasFieldsEnclosedInQuotes = True'フィールドの前後からスペースを削除する'デフォルトでtrueなので、必要なしtfp.TrimWhiteSpace = TrueWhile Not tfp.EndOfData    'フィールドを読み込む    Dim fields As String() = tfp.ReadFields()    '保存    csvRecords.Add(fields)End While'後始末tfp.Close()
[C#]System.Collections.ArrayList csvRecords =    new System.Collections.ArrayList();//CSVファイル名string csvFileName = "C://test.csv";//Shift JISで読み込むMicrosoft.VisualBasic.FileIO.TextFieldParser tfp =    new Microsoft.VisualBasic.FileIO.TextFieldParser(        csvFileName,        System.Text.Encoding.GetEncoding(932));//フィールドが文字で区切られているとする//デフォルトでDelimitedなので、必要なしtfp.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;//区切り文字を,とするtfp.Delimiters = new string[] { "," };//フィールドを"で囲み、改行文字、区切り文字を含めることができるか//デフォルトでtrueなので、必要なしtfp.HasFieldsEnclosedInQuotes = true;//フィールドの前後からスペースを削除する//デフォルトでtrueなので、必要なしtfp.TrimWhiteSpace = true;while (!tfp.EndOfData){    //フィールドを読み込む    string[] fields = tfp.ReadFields();    //保存    csvRecords.Add(fields);}//後始末tfp.Close();

TextFieldParser.TrimWhiteSpaceプロパティはフィールドの前後のスペースを削除するものですが、ダブルクォートで囲まれていた場合でも、前後のスペースを削除します。また、TrimWhiteSpaceの削除するスペースは、スペース文字の他に、タブや改行文字も含まれるようです。このような点から、TextFieldParserクラスを使って先に示したルールに基づいた解析は、無理のようです。(RFC4180に基づくならば、TrimWhiteSpaceをfalseにすればよいかもしれませんが。)

原创粉丝点击