강력한 형식의 데이터 구조로 CSV 파일을 가져 오는 가장 좋은 방법은 무엇입니까?
강력한 형식의 데이터 구조로 CSV 파일을 가져 오는 가장 좋은 방법은 무엇입니까?
답변:
Microsoft의 TextFieldParser 는 안정적이며 CSV 파일의 경우 RFC 4180 을 따릅니다 . Microsoft.VisualBasic
이름 공간 때문에 미루지 마십시오 . .NET Framework의 표준 구성 요소이므로 전역 Microsoft.VisualBasic
어셈블리에 대한 참조를 추가하기 만하면 됩니다.
Windows (Mono와 반대) 용으로 컴파일하고 "파손 된"(RFC 비준수) CSV 파일을 구문 분석 할 필요가없는 경우, 이것이 무료이고 제한되지 않고 안정적이기 때문에 이것이 확실한 선택이 될 것입니다. 그리고 적극적으로 지원되며 대부분은 FileHelpers에 대해 말할 수 없습니다.
참고 항목 : 방법 : VB 코드 예제 는 Visual Basic의 쉼표로 구분 된 텍스트 파일에서 읽기
TextFieldParser
너무 탭으로 구분 된 다른 이상한 엑셀에서 생성 된 cruft에 대한 의지 작동합니다. 귀하의 이전 답변이 라이브러리가 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의 도구와 같은 훌륭한 도구가 많이 있습니다 .
요점은 이것은 매우 일반적인 문제이며 많은 소프트웨어 개발자가 이미이 문제에 대해 생각하고 해결했다고 확신 할 수 있습니다.
Brian은 강력한 형식의 컬렉션으로 변환 할 수있는 좋은 솔루션을 제공합니다.
제공된 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;
}
이것은 큰 따옴표로 구분되지 않는 필드의 가장자리 케이스를 처리하지 않지만 그 안에 따옴표로 묶인 문자열이있는 meerley를 처리합니다. 더 나은 확장과 적절한 라이브러리에 대한 링크는 이 게시물 을 참조하십시오 .
@NotMyself에 동의합니다 . FileHelpers 는 잘 테스트되었으며 사용자가 직접 처리 할 경우 결국 처리해야하는 모든 종류의 엣지 케이스를 처리합니다. FileHelpers가 수행하는 작업을 살펴보고 (1) FileHelpers가 수행하는 엣지 케이스를 처리 할 필요가 전혀 없다고 확신하는 경우에만 직접 작성하거나 (2) 이런 종류의 작성을 좋아하고 다음과 같이 구문 분석해야 할 때 기뻐하십시오.
1, "Bill", "Smith", "Supervisor", "No Comment"
2, 'Drake,', 'O'Malley', "Janitor,
죄송합니다. 저는 인용되지 않았고 새 줄에 있습니다!
지루해서 내가 쓴 내용을 수정했습니다. 파일을 통한 반복의 양을 줄이면서 OO 방식으로 파싱을 캡슐화하려고 시도하며, 상위 foreach에서 한 번만 반복합니다.
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의 두 기사가 있습니다 에서는 StreamReader를 하고 있다는 한 수입은 데이터 CSV 사용하여 마이크로 소프트 텍스트 드라이버 .
이를 수행하는 좋은 간단한 방법은 파일을 열고 각 행을 배열, 연결 목록, 선택한 데이터 구조로 읽는 것입니다. 그래도 첫 번째 줄을 처리하는 데주의하십시오.
이것은 머리 위에있을 수 있지만 연결 문자열을 사용하여 직접 액세스하는 방법도있는 것 같습니다 .
C # 또는 VB 대신 Python을 사용해 보지 않겠습니까? 그것은 당신을 위해 모든 무거운 짐을 가져 오는 멋진 CSV 모듈을 가지고 있습니다.
이번 여름에 프로젝트를 위해 .NET에서 CSV 파서를 사용해야했고 Microsoft Jet Text Driver를 사용했습니다. 연결 문자열을 사용하여 폴더를 지정한 다음 SQL Select 문을 사용하여 파일을 쿼리합니다. schema.ini 파일을 사용하여 강력한 유형을 지정할 수 있습니다. 처음에는이 작업을 수행하지 않았지만 IP 번호 나 "XYQ 3.9 SP1"과 같은 항목과 같이 데이터 유형이 즉시 명확하지 않은 잘못된 결과를 얻었습니다.
한 가지 제한 사항은 64 자 이상의 열 이름을 처리 할 수 없다는 것입니다. 잘립니다. 이것은 매우 잘못 설계된 입력 데이터를 다루고 있다는 점을 제외하고는 문제가되지 않아야합니다. ADO.NET DataSet을 반환합니다.
이것이 내가 찾은 최고의 솔루션이었습니다. 최종 사례 중 일부를 놓칠 수 있고 .NET 용 다른 무료 CSV 구문 분석 패키지를 찾지 못했기 때문에 내 자신의 CSV 파서 롤링을 조심할 것입니다.
편집 : 또한 디렉터리 당 하나의 schema.ini 파일 만있을 수 있으므로 필요한 열을 강력하게 입력하기 위해 동적으로 추가했습니다. 지정된 열만 강력하게 입력하고 지정되지 않은 필드를 추론합니다. 유동적 인 70 개 이상의 열 CSV 가져 오기를 처리하고 각 열을 지정하지 않고 오작동하는 열만 지정하고 싶었 기 때문에 정말 감사했습니다.
코드를 입력했습니다. datagridviewer의 결과는 좋아 보였습니다. 한 줄의 텍스트를 객체의 배열 목록으로 구문 분석합니다.
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]);
도움을주기 위해 사용할 수있는 라이브러리가있을 수 있지만 아마도 얻을 수있는만큼 간단 할 것입니다. 데이터에 쉼표를 사용할 수 없는지 확인하십시오. 그렇지 않으면 더 잘 구문 분석해야합니다.