Bir CSV dosyasını kesin olarak yazılmış bir veri yapısına aktarmanın en iyi yolu nedir?
Bir CSV dosyasını kesin olarak yazılmış bir veri yapısına aktarmanın en iyi yolu nedir?
Yanıtlar:
Microsoft'un TextFieldParser'ı kararlıdır ve CSV dosyaları için RFC 4180'i izler . Microsoft.VisualBasic
Ad alanı tarafından ertelenmeyin; .NET Framework'teki standart bir bileşendir, yalnızca genel Microsoft.VisualBasic
derlemeye bir referans ekleyin .
Windows için derliyorsanız (Mono'nun aksine) ve "bozuk" (RFC uyumlu olmayan) CSV dosyalarını ayrıştırmak zorunda kalacağınızı tahmin etmiyorsanız, ücretsiz, kısıtlamasız, kararlı olduğu için bu bariz bir seçim olacaktır. ve aktif olarak desteklenir, çoğu FileHelpers için söylenemez.
Ayrıca bkz: Nasıl yapılır: VB kodu örneği için Visual Basic'te Virgülle Ayrılmış Metin Dosyalarından Okuma .
TextFieldParser
Çok sekme ile sınırlandırılmış ve diğer garip Excel oluşturulan cruft için irade çalışması. Ben önceki cevabı kütüphanesi VB-özgü olduğunu iddia olmadığını fark, sadece gerçekten olduğunu ima olarak benim için rastladı demek VB için değil, amaçlanan ben sanmıyorum ki, C # kullanılacak durum - MSVB'de gerçekten kullanışlı bazı sınıflar var.
OleDB bağlantısı kullanın.
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 ayrıştırması için oldukça karmaşık senaryolar bekliyorsanız, kendi ayrıştırıcımızı yuvarlamayı düşünmeyin bile . FileHelpers gibi birçok mükemmel araç var, hatta CodeProject'ten olanlar .
Mesele şu ki, bu oldukça yaygın bir sorundur ve birçok yazılım geliştiricisinin bu sorunu zaten düşündüğüne ve çözdüğüne bahse girebilirsiniz .
Brian, onu güçlü bir şekilde yazılmış bir koleksiyona dönüştürmek için güzel bir çözüm sunuyor.
Verilen CSV ayrıştırma yöntemlerinin çoğu, kaçan alanları veya CSV dosyalarının diğer bazı inceliklerini (alanları kırpma gibi) hesaba katmaz. İşte kişisel olarak kullandığım kod. Uçları biraz kaba ve neredeyse hiç hata bildirimi yok.
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;
}
Bunun, çift tırnaklarla sınırlandırılmayan alanların uç durumunu ele almadığını, ancak içinde tırnak işareti olan bir dize olduğunu unutmayın. Bkz Bu yayını daha iyi bunların açıklaması biraz yanı sıra bazı uygun kütüphanelere bazı bağlantılar.
@ NotMyself'e katılıyorum . FileHelpers iyi test edilmiştir ve kendi başınıza yaparsanız sonunda uğraşmanız gereken her türlü uç durumu ele alır. FileHelpers'ın ne yaptığına bir göz atın ve yalnızca (1) FileHelpers'ın yaptığı son durumlarla hiçbir zaman başa çıkmanıza gerek kalmayacağından veya (2) bu tür şeyleri yazmayı seviyorsanız ve bunun gibi şeyleri ayrıştırmanız gerektiğinde çok sevin:
1, "Bill", "Smith", "Süpervizör", "Yorum Yok"
2, "Drake", "O'Malley", "Kapıcı,
Oops, alıntı yapılmadı ve yeni bir satırdayım!
Sıkılmıştım, bu yüzden yazdığım bazı şeyleri değiştirdim. Dosya boyunca yineleme miktarını azaltırken, ayrıştırmayı OO tarzında kapsüllemeye çalışır, her biri en üstte yalnızca bir kez yineler.
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'te, bir çözüm için kod sağlayan iki makale vardır; bunlardan biri StreamReader'ı kullanır ve diğeri de Microsoft Text Driver'ı kullanarak CSV verilerini içe aktarır .
Bunu yapmanın iyi bir basit yolu, dosyayı açmak ve her satırı bir diziye, bağlantılı listeye, seçtiğiniz veri yapısına göre okumaktır. Yine de ilk satırı tutarken dikkatli olun.
Bu başınızı aşmış olabilir, ancak bir bağlantı dizesi kullanarak bunlara erişmenin doğrudan bir yolu var gibi görünüyor .
Neden C # veya VB yerine Python kullanmayı denemiyorsunuz? İçe aktarmak için tüm ağır işleri sizin için yapan güzel bir CSV modülüne sahiptir.
Bu yaz bir proje için .NET'te bir CSV ayrıştırıcısı kullanmak zorunda kaldım ve Microsoft Jet Metin Sürücüsüne yerleştim. Bir bağlantı dizesi kullanarak bir klasör belirtirsiniz, ardından bir SQL Select deyimini kullanarak bir dosyayı sorgulayabilirsiniz. Bir schema.ini dosyası kullanarak güçlü türler belirtebilirsiniz. İlk başta bunu yapmadım, ancak daha sonra IP numaraları veya "XYQ 3.9 SP1" gibi bir giriş gibi veri türünün hemen görünmediği kötü sonuçlar alıyordum.
Karşılaştığım bir sınırlama, 64 karakterin üzerindeki sütun adlarını işleyememesidir; keser. Çok zayıf tasarlanmış girdi verileriyle uğraşmam dışında bu bir sorun olmamalı. Bir ADO.NET DataSet döndürür.
Bulduğum en iyi çözüm buydu. Muhtemelen bazı son durumları kaçıracağım ve .NET için başka ücretsiz CSV ayrıştırma paketleri bulamadığım için kendi CSV ayrıştırıcımı yuvarlamak konusunda dikkatli olurdum.
DÜZENLEME: Ayrıca, dizin başına yalnızca bir schema.ini dosyası olabilir, bu nedenle gerekli sütunları güçlü bir şekilde yazmak için ona dinamik olarak ekledim. Yalnızca belirtilen sütunları güçlü bir şekilde yazacak ve belirtilmemiş herhangi bir alanı çıkaracaktır. Bunu gerçekten takdir ettim, çünkü 70+ sütunlu akışkan CSV'yi içe aktarmakla uğraşıyordum ve her bir sütunu belirtmek istemiyordum, sadece hatalı olanları belirtmek istiyordum.
Bir kod yazdım. Datagridviewer'daki sonuç iyi görünüyordu. Tek bir metin satırını nesnelerin bir dizi listesine ayrıştırır.
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;
}
Verilerde virgül olmadığını garanti edebiliyorsanız, en basit yol muhtemelen String.split kullanmak olacaktır .
Örneğin:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
Yardım etmek için kullanabileceğiniz kütüphaneler olabilir, ancak bu muhtemelen alabileceğiniz kadar basittir. Verilerde virgül bulunmamasına dikkat edin, aksi takdirde daha iyi ayrıştırmanız gerekir.