厳密に型指定されたデータ構造にCSVファイルをインポートする最良の方法は何ですか?
厳密に型指定されたデータ構造にCSVファイルをインポートする最良の方法は何ですか?
回答:
MicrosoftのTextFieldParserは安定しており、CSVファイルはRFC 4180に準拠しています。Microsoft.VisualBasic
名前空間にうんざりしないでください。これは.NET Frameworkの標準コンポーネントMicrosoft.VisualBasic
です。グローバルアセンブリへの参照を追加するだけです。
(Monoとは対照的に)Windows向けにコンパイルしていて、「壊れた」(RFCに準拠していない)CSVファイルを解析する必要がない場合は、これが明らかな選択です。積極的にサポートされており、そのほとんどはFileHelpersには言えません。
関連項目:方法: VBコード例については、Visual Basicでコンマ区切りのテキストファイルから読み取る。
TextFieldParser
は、タブ区切りやその他の奇妙なExcelで生成された残骸でも機能します。私はあなたの前の答えは、ライブラリは、VB-特異的であったと主張していなかった、それだけで、それが本当にされたことを意味しているように私に出くわしたことを実感意味 VBのためではなく、意図した C#から使用する、私はあると思うしませんケース-MSVBには本当に便利なクラスがいくつかあります。
OleDB接続を使用します。
String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
CSV解析のかなり複雑なシナリオを想定している場合は、独自のパーサーをロールアップすることさえ考えないでください。優れたツールの多くは次のように、そこにありますFileHelpersから、あるいはものCodeProjectの。
ポイントは、これはかなり一般的な問題であり、多くのソフトウェア開発者がすでにこの問題を考えて解決していることでしょう。
ブライアンは、それを強く型付けされたコレクションに変換するための素晴らしいソリューションを提供します。
指定されたCSV解析メソッドのほとんどは、エスケープフィールドや、CSVファイルの他の微妙な要素(トリミングフィールドなど)を考慮していません。これが私が個人的に使用するコードです。端が粗く、エラー報告はほとんどありません。
public static IList<IList<string>> Parse(string content)
{
IList<IList<string>> records = new List<IList<string>>();
StringReader stringReader = new StringReader(content);
bool inQoutedString = false;
IList<string> record = new List<string>();
StringBuilder fieldBuilder = new StringBuilder();
while (stringReader.Peek() != -1)
{
char readChar = (char)stringReader.Read();
if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
{
// If it's a \r\n combo consume the \n part and throw it away.
if (readChar == '\r')
{
stringReader.Read();
}
if (inQoutedString)
{
if (readChar == '\r')
{
fieldBuilder.Append('\r');
}
fieldBuilder.Append('\n');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
records.Add(record);
record = new List<string>();
inQoutedString = false;
}
}
else if (fieldBuilder.Length == 0 && !inQoutedString)
{
if (char.IsWhiteSpace(readChar))
{
// Ignore leading whitespace
}
else if (readChar == '"')
{
inQoutedString = true;
}
else if (readChar == ',')
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
else
{
fieldBuilder.Append(readChar);
}
}
else if (readChar == ',')
{
if (inQoutedString)
{
fieldBuilder.Append(',');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
}
else if (readChar == '"')
{
if (inQoutedString)
{
if (stringReader.Peek() == '"')
{
stringReader.Read();
fieldBuilder.Append('"');
}
else
{
inQoutedString = false;
}
}
else
{
fieldBuilder.Append(readChar);
}
}
else
{
fieldBuilder.Append(readChar);
}
}
record.Add(fieldBuilder.ToString().TrimEnd());
records.Add(record);
return records;
}
これは、二重引用符で区切られていないフィールドのエッジケースを処理しないことに注意してください。少し良い説明といくつかの適切なライブラリへのリンクについては、この投稿を参照してください。
@NotMyselfに同意します。FileHelpersは十分にテストされており、自分で行う場合には最終的に対処しなければならないあらゆる種類のエッジケースを処理します。FileHelpersが何をするかを見て、(1)FileHelpersが行うエッジケースを処理する必要がまったくないか、(2)この種のものを書くのが大好きで、このようなものを解析しなければならないときは大喜びしてください:
1、「ビル」、「スミス」、「スーパーバイザー」、「コメントなし」
2、「ドレイク」、「オマレー」、「用務員、
おっと、私は引用されておらず、新しい行にいます!
退屈だったので、書いたものをいくつか修正しました。ファイル全体の反復回数を削減しながら、解析をOO方式でカプセル化しようとします。これは、最上位のforeachで1回だけ繰り返されます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// usage:
// note this wont run as getting streams is not Implemented
// but will get you started
CSVFileParser fileParser = new CSVFileParser();
// TO Do: configure fileparser
PersonParser personParser = new PersonParser(fileParser);
List<Person> persons = new List<Person>();
// if the file is large and there is a good way to limit
// without having to reparse the whole file you can use a
// linq query if you desire
foreach (Person person in personParser.GetPersons())
{
persons.Add(person);
}
// now we have a list of Person objects
}
}
public abstract class CSVParser
{
protected String[] deliniators = { "," };
protected internal IEnumerable<String[]> GetRecords()
{
Stream stream = GetStream();
StreamReader reader = new StreamReader(stream);
String[] aRecord;
while (!reader.EndOfStream)
{
aRecord = reader.ReadLine().Split(deliniators,
StringSplitOptions.None);
yield return aRecord;
}
}
protected abstract Stream GetStream();
}
public class CSVFileParser : CSVParser
{
// to do: add logic to get a stream from a file
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class CSVWebParser : CSVParser
{
// to do: add logic to get a stream from a web request
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class Person
{
public String Name { get; set; }
public String Address { get; set; }
public DateTime DOB { get; set; }
}
public class PersonParser
{
public PersonParser(CSVParser parser)
{
this.Parser = parser;
}
public CSVParser Parser { get; set; }
public IEnumerable<Person> GetPersons()
{
foreach (String[] record in this.Parser.GetRecords())
{
yield return new Person()
{
Name = record[0],
Address = record[1],
DOB = DateTime.Parse(record[2]),
};
}
}
}
}
解決のためのコードを提供CodeProjectの上の2件の記事、使用するものがあるのStreamReaderを一つその輸入はCSVデータ使用してMicrosoftテキストドライバーが。
これを行う簡単な方法は、ファイルを開いて、各行を配列、リンクリスト、選択したデータ構造に読み込むことです。ただし、最初の行の取り扱いには注意してください。
これはあなたの頭の上のかもしれませんが、接続文字列を使用してそれらにアクセスするための直接的な方法があるようです。
C#やVBの代わりにPythonを使用してみませんか?それはあなたのためにすべての重労働を行うインポートする素晴らしいCSVモジュールを持っています。
この夏のプロジェクトでは、.NETでCSVパーサーを使用する必要があり、Microsoft Jet Text Driverで解決しました。接続文字列を使用してフォルダーを指定し、SQL Selectステートメントを使用してファイルをクエリします。schema.iniファイルを使用して、強い型を指定できます。最初はこれを実行しませんでしたが、IP番号や「XYQ 3.9 SP1」のようなエントリなど、データのタイプがすぐに明らかにならないという悪い結果が出ていました。
私が遭遇した1つの制限は、64文字を超える列名を処理できないことです。切り捨てられます。これは問題になりません。ただし、設計が非常に不十分な入力データを扱っていた場合を除きます。ADO.NET DataSetを返します。
これは私が見つけた最良の解決策でした。私はおそらく自分のCSVパーサーをロールすることに警戒しているでしょう。おそらく、いくつかの最終ケースを見逃してしまい、.NET用の他の無料のCSV解析パッケージがそこにないためです。
編集:また、必要な列を強く入力するために、ディレクトリごとにschema.iniファイルは1つしか存在できないため、動的に追加します。指定された列のみを厳密に型指定し、指定されていないフィールドを推測します。70以上の流動的なCSVのインポートを扱っていて、各列を指定するのではなく、誤動作している列のみをインポートしたので、私はこれに本当に感謝しています。
コードを入力しました。datagridviewerの結果は良好に見えました。1行のテキストを解析して、オブジェクトのarraylistを作成します。
enum quotestatus
{
none,
firstquote,
secondquote
}
public static System.Collections.ArrayList Parse(string line,string delimiter)
{
System.Collections.ArrayList ar = new System.Collections.ArrayList();
StringBuilder field = new StringBuilder();
quotestatus status = quotestatus.none;
foreach (char ch in line.ToCharArray())
{
string chOmsch = "char";
if (ch == Convert.ToChar(delimiter))
{
if (status== quotestatus.firstquote)
{
chOmsch = "char";
}
else
{
chOmsch = "delimiter";
}
}
if (ch == Convert.ToChar(34))
{
chOmsch = "quotes";
if (status == quotestatus.firstquote)
{
status = quotestatus.secondquote;
}
if (status == quotestatus.none )
{
status = quotestatus.firstquote;
}
}
switch (chOmsch)
{
case "char":
field.Append(ch);
break;
case "delimiter":
ar.Add(field.ToString());
field.Clear();
break;
case "quotes":
if (status==quotestatus.firstquote)
{
field.Clear();
}
if (status== quotestatus.secondquote)
{
status =quotestatus.none;
}
break;
}
}
if (field.Length != 0)
{
ar.Add(field.ToString());
}
return ar;
}
データにコンマがないことが保証できる場合、おそらく最も簡単な方法はString.splitを使用することです。
例えば:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
あなたが助けるために使用できるライブラリがあるかもしれませんが、それはおそらくあなたが得ることができるのと同じくらい簡単です。データにカンマが含まれていないことを確認してください。そうでない場合は、データをより適切に解析する必要があります。