Apa cara terbaik untuk mengimpor file CSV ke dalam struktur data yang diketik dengan kuat?
Apa cara terbaik untuk mengimpor file CSV ke dalam struktur data yang diketik dengan kuat?
Jawaban:
TextFieldParser Microsoft stabil dan mengikuti RFC 4180 untuk file CSV. Jangan menunda Microsoft.VisualBasic
namespace; itu adalah komponen standar dalam .NETFramework, cukup tambahkan referensi ke Microsoft.VisualBasic
rakitan global .
Jika Anda mengompilasi untuk Windows (bukan Mono) dan tidak mengantisipasi keharusan mengurai file CSV yang "rusak" (tidak sesuai RFC), maka ini akan menjadi pilihan yang jelas, karena gratis, tidak terbatas, stabil, dan didukung secara aktif, sebagian besar tidak dapat dikatakan untuk FileHelpers.
Lihat juga: Cara: Membaca Dari File Teks Berbatas Koma dalam Visual Basic untuk contoh kode VB.
TextFieldParser
akan bekerja untuk tab-delimited dan cruft aneh lain yang dihasilkan Excel juga. Saya menyadari bahwa jawaban Anda sebelumnya tidak mengklaim bahwa pustaka itu khusus VB, itu baru saja menurut saya menyiratkan bahwa itu benar-benar dimaksudkan untuk VB, dan tidak dimaksudkan untuk digunakan dari C #, yang menurut saya tidak kasus - ada beberapa kelas yang sangat berguna di MSVB.
Gunakan koneksi 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();
Jika Anda mengharapkan skenario yang cukup rumit untuk penguraian CSV, jangan pernah berpikir untuk menggulung parser kami sendiri . Ada banyak alat luar biasa di luar sana, seperti FileHelpers , atau bahkan dari CodeProject .
Intinya adalah ini adalah masalah yang cukup umum dan Anda dapat bertaruh bahwa banyak pengembang perangkat lunak telah memikirkan dan memecahkan masalah ini.
Brian memberikan solusi yang bagus untuk mengubahnya menjadi koleksi yang diketik dengan kuat.
Sebagian besar metode penguraian CSV yang diberikan tidak memperhitungkan bidang pelolosan atau beberapa seluk-beluk file CSV lainnya (seperti bidang pemangkasan). Berikut adalah kode yang saya gunakan secara pribadi. Ini agak kasar di sekitar tepinya dan hampir tidak ada pelaporan kesalahan.
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;
}
Perhatikan bahwa ini tidak menangani kasus tepi bidang yang tidak dihilangkan oleh tanda kutip ganda, tetapi meerley memiliki string yang dikutip di dalamnya. Lihat posting ini untuk sedikit penjelasan yang lebih baik serta beberapa tautan ke beberapa perpustakaan yang tepat.
Saya setuju dengan @ NotMyself . FileHelpers telah teruji dengan baik dan menangani semua jenis kasus tepi yang pada akhirnya harus Anda tangani jika Anda melakukannya sendiri. Lihatlah apa yang FileHelpers lakukan dan hanya tulis sendiri jika Anda benar-benar yakin bahwa (1) Anda tidak akan pernah perlu menangani kasus edge yang dilakukan FileHelpers, atau (2) Anda suka menulis hal semacam ini dan akan melakukannya sangat senang ketika Anda harus mengurai hal-hal seperti ini:
1, "Bill", "Smith", "Supervisor", "No Comment"
2, 'Drake,', 'O'Malley', "Petugas kebersihan,
Ups, saya tidak dikutip dan saya berada di jalur baru!
Saya bosan jadi saya memodifikasi beberapa hal yang saya tulis. Ini mencoba untuk merangkum parsing dengan cara OO sementara mengurangi jumlah iterasi melalui file, itu hanya iterasi sekali di bagian depan atas.
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]),
};
}
}
}
}
Ada dua artikel di CodeProject yang menyediakan kode untuk solusi, satu yang menggunakan StreamReader dan satu lagi yang mengimpor data CSV menggunakan Microsoft Text Driver .
Cara sederhana yang baik untuk melakukannya adalah dengan membuka file, dan membaca setiap baris ke dalam array, daftar tertaut, struktur data pilihan Anda. Berhati-hatilah saat menangani baris pertama.
Ini mungkin tidak masuk akal, tetapi tampaknya ada cara langsung untuk mengaksesnya juga dengan menggunakan string koneksi .
Mengapa tidak mencoba menggunakan Python, bukan C # atau VB? Ini memiliki modul CSV yang bagus untuk diimpor yang melakukan semua pekerjaan berat untuk Anda.
Saya harus menggunakan parser CSV di .NET untuk proyek musim panas ini dan memilih Microsoft Jet Text Driver. Anda menentukan folder menggunakan string koneksi, lalu membuat kueri file menggunakan pernyataan SQL Select. Anda dapat menentukan tipe yang kuat menggunakan file schema.ini. Saya tidak melakukan ini pada awalnya, tetapi kemudian saya mendapatkan hasil yang buruk di mana jenis datanya tidak langsung terlihat, seperti nomor IP atau entri seperti "XYQ 3.9 SP1".
Satu batasan yang saya temui adalah tidak dapat menangani nama kolom di atas 64 karakter; itu memotong. Ini seharusnya tidak menjadi masalah, kecuali saya berurusan dengan data masukan yang dirancang dengan sangat buruk. Ia mengembalikan ADO.NET DataSet.
Ini adalah solusi terbaik yang saya temukan. Saya akan berhati-hati dalam menjalankan pengurai CSV saya sendiri, karena saya mungkin akan melewatkan beberapa kasus akhir, dan saya tidak menemukan paket penguraian CSV gratis lainnya untuk .NET di luar sana.
EDIT: Juga, hanya ada satu file schema.ini per direktori, jadi saya secara dinamis menambahkannya untuk mengetik kolom yang dibutuhkan. Ini hanya akan mengetik kuat kolom yang ditentukan, dan menyimpulkan untuk setiap bidang yang tidak ditentukan. Saya sangat menghargai ini, karena saya berurusan dengan mengimpor CSV kolom 70+ yang lancar dan tidak ingin menentukan setiap kolom, hanya kolom yang berperilaku tidak semestinya.
Saya mengetik beberapa kode. Hasil di datagridviewer terlihat bagus. Ini mem-parsing satu baris teks ke daftar objek.
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;
}
Jika Anda dapat menjamin bahwa tidak ada koma dalam data, cara termudah mungkin adalah dengan menggunakan String.split .
Sebagai contoh:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
Mungkin ada perpustakaan yang bisa Anda gunakan untuk membantu, tapi itu mungkin sesederhana yang bisa Anda dapatkan. Pastikan Anda tidak memiliki koma dalam data, jika tidak, Anda perlu menguraikannya dengan lebih baik.