您的位置:首页 > 编程语言 > VB

CSV文件解析(转)

2008-07-15 20:34 435 查看

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 DataTable
da.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 DataTable
da.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 ファイルを作成する方法」が参考になります。

参考:

ConnectionStrings.com

正規表現を使った方法

次に正規表現を使った方法を紹介します。「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 csvRecords
End 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 foundCount
End 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 csvRecords
End 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つが代表的なものです。)

A Fast CSV Reader

XmlCsvReader Implementation

A portable and efficient generic parser for flat files

ASC2XXX - Two classes for parsing delimited text files(正規表現を使用しているようです)

.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 = True

While 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にすればよいかもしれませんが。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息