Cách tốt nhất để nhập tệp CSV vào cấu trúc dữ liệu được đánh máy mạnh là gì?
Cách tốt nhất để nhập tệp CSV vào cấu trúc dữ liệu được đánh máy mạnh là gì?
Câu trả lời:
TextFieldParser của Microsoft ổn định và tuân theo RFC 4180 cho các tệp CSV. Không được đặt ra bởi Microsoft.VisualBasic
không gian tên; nó là một thành phần tiêu chuẩn trong .NET Framework, chỉ cần thêm một tham chiếu đến toàn cục Microsoft.VisualBasic
.
Nếu bạn đang biên dịch cho Windows (trái ngược với Mono) và không dự đoán sẽ phải phân tích cú pháp các tệp CSV "bị hỏng" (không tuân thủ RFC), thì đây sẽ là lựa chọn hiển nhiên, vì nó miễn phí, không hạn chế, ổn định, và được hỗ trợ tích cực, hầu hết trong số đó không thể không nói đến FileHelpers.
Xem thêm: Cách: Đọc Từ Tệp Văn bản Phân cách bằng Dấu phẩy trong Visual Basic để biết ví dụ về mã VB.
TextFieldParser
cũng sẽ hoạt động đối với các dấu hiệu được phân cách bằng tab và các dấu hiệu lạ khác do Excel tạo ra. Tôi nhận ra rằng câu trả lời trước của bạn đã không tuyên bố rằng thư viện là VB cụ thể, nó chỉ đi qua với tôi như ngụ ý rằng nó đã thực sự có ý nghĩa đối với VB, và không có ý định để được sử dụng từ C #, mà tôi không nghĩ là trường hợp - có một số lớp thực sự hữu ích trong MSVB.
Sử dụng kết nối 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();
Nếu bạn đang mong đợi các tình huống khá phức tạp cho phân tích cú pháp CSV, thậm chí đừng nghĩ đến việc sử dụng trình phân tích cú pháp của riêng chúng tôi . Có rất nhiều công cụ tuyệt vời như FileHelpers , hoặc thậm chí từ CodeProject .
Vấn đề là đây là một vấn đề khá phổ biến và bạn có thể đặt cược rằng rất nhiều nhà phát triển phần mềm đã nghĩ đến và giải quyết vấn đề này.
Brian đưa ra một giải pháp hay để chuyển nó thành một bộ sưu tập được đánh máy mạnh.
Hầu hết các phương pháp phân tích cú pháp CSV đã cung cấp không tính đến các trường thoát hoặc một số tính chất tinh vi khác của tệp CSV (như trường cắt xén). Đây là mã cá nhân tôi sử dụng. Nó hơi thô xung quanh các cạnh và hầu như không có báo cáo lỗi.
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;
}
Lưu ý rằng điều này không xử lý trường hợp cạnh của các trường không được phân tách bằng dấu ngoặc kép, nhưng meerley có một chuỗi được trích dẫn bên trong nó. Xem bài đăng này để biết rõ hơn một chút cũng như một số liên kết đến một số thư viện thích hợp.
Tôi đồng ý với @ NotMyself . FileHelpers được thử nghiệm tốt và xử lý tất cả các loại trường hợp phức tạp mà cuối cùng bạn sẽ phải đối phó nếu bạn tự làm. Hãy xem những gì FileHelpers làm và chỉ viết nội dung của riêng bạn nếu bạn hoàn toàn chắc chắn rằng (1) bạn sẽ không bao giờ cần phải xử lý các trường hợp phức tạp mà FileHelpers làm, hoặc (2) bạn thích viết loại nội dung này và sẽ vui mừng khôn xiết khi bạn phải phân tích cú pháp như thế này:
1, "Bill", "Smith", "Supervisor", "No Comment"
2, 'Drake,', 'O'Malley', "Người gác cổng,
Rất tiếc, tôi không được trích dẫn và tôi đang ở một dòng mới!
Tôi cảm thấy buồn chán vì vậy tôi đã sửa đổi một số thứ mà tôi đã viết. Nó cố gắng đóng gói phân tích cú pháp theo cách OO với điều kiện cắt giảm số lần lặp lại qua tệp, nó chỉ lặp lại một lần ở phần trên cùng.
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]),
};
}
}
}
}
Có hai bài viết trên CodeProject cung cấp mã cho giải pháp, một bài sử dụng StreamReader và một bài viết nhập dữ liệu CSV bằng Microsoft Text Driver .
Một cách đơn giản hay để làm điều đó là mở tệp và đọc từng dòng vào một mảng, danh sách được liên kết, cấu trúc dữ liệu do bạn lựa chọn. Hãy cẩn thận về việc xử lý dòng đầu tiên.
Điều này có thể vượt qua tầm nhìn của bạn, nhưng dường như có một cách trực tiếp để truy cập chúng cũng như sử dụng một chuỗi kết nối .
Tại sao không thử sử dụng Python thay vì C # hoặc VB? Nó có một mô-đun CSV tuyệt vời để nhập, thực hiện tất cả các công việc nặng nhọc cho bạn.
Tôi đã phải sử dụng trình phân tích cú pháp CSV trong .NET cho một dự án vào mùa hè này và giải quyết bằng Microsoft Jet Text Driver. Bạn chỉ định một thư mục bằng cách sử dụng chuỗi kết nối, sau đó truy vấn tệp bằng câu lệnh SQL Select. Bạn có thể chỉ định loại mạnh bằng tệp schema.ini. Tôi đã không làm điều này lúc đầu, nhưng sau đó tôi nhận được kết quả không tốt khi loại dữ liệu không rõ ràng ngay lập tức, chẳng hạn như số IP hoặc mục nhập như "XYQ 3.9 SP1".
Một hạn chế mà tôi gặp phải là nó không thể xử lý các tên cột trên 64 ký tự; nó cắt ngắn. Đây không phải là vấn đề, ngoại trừ tôi đang xử lý dữ liệu đầu vào được thiết kế rất kém. Nó trả về một Tập dữ liệu ADO.NET.
Đây là giải pháp tốt nhất mà tôi tìm thấy. Tôi sẽ cảnh giác với việc sử dụng trình phân tích cú pháp CSV của riêng mình, vì tôi có thể sẽ bỏ lỡ một số trường hợp cuối và tôi không tìm thấy bất kỳ gói phân tích cú pháp CSV miễn phí nào khác cho .NET ngoài đó.
CHỈNH SỬA: Ngoài ra, chỉ có thể có một tệp schema.ini cho mỗi thư mục, vì vậy tôi tự động thêm vào nó để gõ mạnh các cột cần thiết. Nó sẽ chỉ nhập mạnh các cột được chỉ định và suy ra cho bất kỳ trường không xác định nào. Tôi thực sự đánh giá cao điều này, vì tôi đang xử lý việc nhập CSV hơn 70 cột linh hoạt và không muốn chỉ định từng cột, chỉ những cột hoạt động sai.
Tôi đã nhập một số mã. Kết quả trong trình xem dữ liệu có vẻ tốt. Nó phân tích cú pháp một dòng văn bản thành một danh sách mảng các đối tượng.
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;
}
Nếu bạn có thể đảm bảo rằng không có dấu phẩy trong dữ liệu, thì cách đơn giản nhất có thể là sử dụng String.split .
Ví dụ:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
Có thể có thư viện bạn có thể sử dụng để trợ giúp, nhưng điều đó có thể đơn giản nhất bạn có thể làm được. Chỉ cần đảm bảo rằng bạn không thể có dấu phẩy trong dữ liệu, nếu không, bạn sẽ cần phải phân tích cú pháp tốt hơn.