Programing

헤더를 사용하여 C #에서 CSV 파일 구문 분석

lottogame 2020. 4. 11. 09:38
반응형

헤더를 사용하여 C #에서 CSV 파일 구문 분석


C #에서 CSV 파일을 구문 분석하는 기본 / 공식 / 권장 방법이 있습니까? 내 파서를 굴리고 싶지 않다.

또한 ODBC / OLE DB를 사용하여 텍스트 드라이버를 통해 CSV를 읽는 사람들의 사례를 보았으며 많은 사람들이 "단점"으로 인해 이것을 권장하지 않습니다. 이러한 단점은 무엇입니까?

이상적으로는 첫 번째 레코드를 헤더 / 필드 이름으로 사용하여 열 이름으로 CSV를 읽을 수있는 방법을 찾고 있습니다. 주어진 답변 중 일부는 정확하지만 기본적으로 파일을 클래스로 직렬화 해제합니다.


도서관이 당신을 위해 모든 세부 사항을 처리하게하십시오! :-)

FileHelpers를 확인하고 DRY를 유지하십시오-스스로 반복하지 마십시오-바퀴를 다시 발명 할 필요가 없습니다 ....

기본적으로 공개 클래스 (따라서 기본값, NULL 값 대체 등과 같은 잘 알려진 속성), 포인트를 사용하여 데이터의 형태 (CSV의 개별 줄에있는 필드)를 정의하면됩니다. 파일의 FileHelpers 엔진 및 bingo-해당 파일에서 모든 항목을 가져옵니다. 하나의 간단한 조작-뛰어난 성능!


CSV 파서는 이제 .NET Framework의 일부입니다.

Microsoft.VisualBasic.dll에 대한 참조 추가 (C #에서는 잘 작동하지만 이름은 신경 쓰지 않음)

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData)
    {
        //Process row
        string[] fields = parser.ReadFields();
        foreach (string field in fields)
        {
            //TODO: Process field
        }
    }
}

문서는 여기에 있습니다 -TextFieldParser 클래스

PS CSV 내보내기 가 필요한 경우 CsvExport를 사용해보십시오 (Discl : I 'm atributors).


CsvHelper (내가 유지 관리하는 라이브러리)는 CSV 파일을 사용자 지정 개체로 읽습니다.

var csv = new CsvReader( File.OpenText( "file.csv" ) );
var myCustomObjects = csv.GetRecords<MyCustomObject>();

때로는 읽으려는 객체를 소유하지 않은 경우가 있습니다. 이 경우 클래스에 속성을 넣을 수 없으므로 매끄럽게 매핑 할 수 있습니다.

public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
{
    public MyCustomObjectMap()
    {
        Map( m => m.Property1 ).Name( "Column Name" );
        Map( m => m.Property2 ).Index( 4 );
        Map( m => m.Property3 ).Ignore();
        Map( m => m.Property4 ).TypeConverter<MySpecialTypeConverter>();
    }
}

비즈니스 응용 프로그램에서 codeproject.com, CSVReader 의 오픈 소스 프로젝트를 사용합니다 .

잘 작동하고 성능이 좋습니다. 내가 제공 한 링크에 대한 벤치마킹이 있습니다.

프로젝트 페이지에서 복사 한 간단한 예 :

using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true))
{
    int fieldCount = csv.FieldCount;
    string[] headers = csv.GetFieldHeaders();

    while (csv.ReadNextRecord())
    {
        for (int i = 0; i < fieldCount; i++)
            Console.Write(string.Format("{0} = {1};", headers[i], csv[i]));

        Console.WriteLine();
    }
}

보시다시피, 작업하기가 매우 쉽습니다.


나는 조금 늦었지만 CSS 파일을 처리 Microsoft.VisualBasic.FileIO하는 TextFieldParser클래스 있는 라이브러리 찾았습니다 .


csv 파일 만 읽어야하는 경우이 라이브러리를 권장합니다. 빠른 CSV 리더
csv 파일도 생성해야하는 경우 다음을 사용하십시오. FileHelpers

둘 다 무료이며 오픈 소스입니다.


이 스레드로 돌아 오는 경우를 위해 내가 자주 사용하는 도우미 클래스가 있습니다 (공유하고 싶었습니다).

사용할 준비가 된 프로젝트로 간단하게 이식하기 위해 이것을 사용합니다.

public class CSVHelper : List<string[]>
{
  protected string csv = string.Empty;
  protected string separator = ",";

  public CSVHelper(string csv, string separator = "\",\"")
  {
    this.csv = csv;
    this.separator = separator;

    foreach (string line in Regex.Split(csv, System.Environment.NewLine).ToList().Where(s => !string.IsNullOrEmpty(s)))
    {
      string[] values = Regex.Split(line, separator);

      for (int i = 0; i < values.Length; i++)
      {
        //Trim values
        values[i] = values[i].Trim('\"');
      }

      this.Add(values);
    }
  }
}

그리고 그것을 다음과 같이 사용하십시오 :

public List<Person> GetPeople(string csvContent)
{
  List<Person> people = new List<Person>();
  CSVHelper csv = new CSVHelper(csvContent);
  foreach(string[] line in csv)
  {
    Person person = new Person();
    person.Name = line[0];
    person.TelephoneNo = line[1];
    people.Add(person);
  }
  return people;
}

[CSV 도우미 업데이트 : 마지막 줄 바꾸기 문자가 줄 바꾸기를 만들 때 발생하는 버그 수정]


이 솔루션은 공식 Microsoft.VisualBasic 어셈블리를 사용하여 CSV를 구문 분석합니다.

장점 :

  • 분리 문자 이스케이프
  • 헤더를 무시
  • 트림 공간
  • 댓글 무시

암호:

    using Microsoft.VisualBasic.FileIO;

    public static List<List<string>> ParseCSV (string csv)
    {
        List<List<string>> result = new List<List<string>>();


        // To use the TextFieldParser a reference to the Microsoft.VisualBasic assembly has to be added to the project. 
        using (TextFieldParser parser = new TextFieldParser(new StringReader(csv))) 
        {
            parser.CommentTokens = new string[] { "#" };
            parser.SetDelimiters(new string[] { ";" });
            parser.HasFieldsEnclosedInQuotes = true;

            // Skip over header line.
            //parser.ReadLine();

            while (!parser.EndOfData)
            {
                var values = new List<string>();

                var readFields = parser.ReadFields();
                if (readFields != null)
                    values.AddRange(readFields);
                result.Add(values);
            }
        }

        return result;
    }

TinyCsvParser for .NET 을 작성 했습니다 . 이는 가장 빠른 .NET 파서 중 하나이며 거의 모든 CSV 형식을 구문 분석하도록 구성 할 수 있습니다.

MIT 라이센스에 따라 배포됩니다 :

NuGet사용 하여 설치할 수 있습니다. 패키지 관리자 콘솔 에서 다음 명령을 실행하십시오 .

PM> Install-Package TinyCsvParser

용법

persons.csv이름, 성 및 생년월일이 있는 CSV 파일에 사람 목록이 있다고 가정하십시오 .

FirstName;LastName;BirthDate
Philipp;Wagner;1986/05/12
Max;Musterman;2014/01/02

시스템의 해당 도메인 모델은 다음과 같습니다.

private class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

TinyCsvParser를 사용할 때는 CSV 데이터의 열과 도메인 모델의 속성 간의 매핑을 정의해야합니다.

private class CsvPersonMapping : CsvMapping<Person>
{

    public CsvPersonMapping()
        : base()
    {
        MapProperty(0, x => x.FirstName);
        MapProperty(1, x => x.LastName);
        MapProperty(2, x => x.BirthDate);
    }
}

그런 다음 매핑을 사용하여 CSV 데이터를로 구문 분석 할 수 있습니다 CsvParser.

namespace TinyCsvParser.Test
{
    [TestFixture]
    public class TinyCsvParserTest
    {
        [Test]
        public void TinyCsvTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvPersonMapping csvMapper = new CsvPersonMapping();
            CsvParser<Person> csvParser = new CsvParser<Person>(csvParserOptions, csvMapper);

            var result = csvParser
                .ReadFromFile(@"persons.csv", Encoding.ASCII)
                .ToList();

            Assert.AreEqual(2, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual("Philipp", result[0].Result.FirstName);
            Assert.AreEqual("Wagner", result[0].Result.LastName);

            Assert.AreEqual(1986, result[0].Result.BirthDate.Year);
            Assert.AreEqual(5, result[0].Result.BirthDate.Month);
            Assert.AreEqual(12, result[0].Result.BirthDate.Day);

            Assert.AreEqual("Max", result[1].Result.FirstName);
            Assert.AreEqual("Mustermann", result[1].Result.LastName);

            Assert.AreEqual(2014, result[1].Result.BirthDate.Year);
            Assert.AreEqual(1, result[1].Result.BirthDate.Month);
            Assert.AreEqual(1, result[1].Result.BirthDate.Day);
        }
    }
}

사용자 설명서

전체 사용 설명서는 다음에서 구할 수 있습니다.


내 KISS 구현은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Text;

class CsvParser
{
    public static List<string> Parse(string line)
    {
        const char escapeChar = '"';
        const char splitChar = ',';
        bool inEscape = false;
        bool priorEscape = false;

        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            switch (c)
            {
                case escapeChar:
                    if (!inEscape)
                        inEscape = true;
                    else
                    {
                        if (!priorEscape)
                        {
                            if (i + 1 < line.Length && line[i + 1] == escapeChar)
                                priorEscape = true;
                            else
                                inEscape = false;
                        }
                        else
                        {
                            sb.Append(c);
                            priorEscape = false;
                        }
                    }
                    break;
                case splitChar:
                    if (inEscape) //if in escape
                        sb.Append(c);
                    else
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                    break;
                default:
                    sb.Append(c);
                    break;
            }
        }

        if (sb.Length > 0)
            result.Add(sb.ToString());

        return result;
    }

}

얼마 전에 Microsoft.VisualBasic라이브러리를 기반으로 CSV 읽기 / 쓰기를위한 간단한 클래스를 작성했습니다 . 이 간단한 클래스를 사용하면 2 차원 배열과 같은 CSV로 작업 할 수 있습니다. 다음 링크를 통해 내 수업을 찾을 수 있습니다 : https://github.com/ukushu/DataExporter

간단한 사용법 예 :

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

헤더를 읽으려면 csv.Rows[0]을 읽는 것만 필요 합니다. :)


간단한 구문 분석 요구를위한 단일 소스 파일 솔루션. 불쾌한 모든 경우를 처리합니다. 줄 바꿈 정규화 및 따옴표 붙은 문자열 리터럴에서 줄 바꾸기 처리와 같은. 천만에요!

CSV 파일에 헤더가 있으면 첫 번째 행에서 열 이름을 읽고 열 인덱스를 계산하십시오. 그렇게 간단합니다.

참고 DumpLINQPad 방법입니다, 당신은 LINQPad을 사용하지 않는 경우 있음을 제거 할 수 있습니다.

void Main()
{
    var file1 = "a,b,c\r\nx,y,z";
    CSV.ParseText(file1).Dump();

    var file2 = "a,\"b\",c\r\nx,\"y,z\"";
    CSV.ParseText(file2).Dump();

    var file3 = "a,\"b\",c\r\nx,\"y\r\nz\"";
    CSV.ParseText(file3).Dump();

    var file4 = "\"\"\"\"";
    CSV.ParseText(file4).Dump();
}

static class CSV
{
    public struct Record
    {
        public readonly string[] Row;

        public string this[int index] => Row[index];

        public Record(string[] row)
        {
            Row = row;
        }
    }

    public static List<Record> ParseText(string text)
    {
        return Parse(new StringReader(text));
    }

    public static List<Record> ParseFile(string fn)
    {
        using (var reader = File.OpenText(fn))
        {
            return Parse(reader);
        }
    }

    public static List<Record> Parse(TextReader reader)
    {
        var data = new List<Record>();

        var col = new StringBuilder();
        var row = new List<string>();
        for (; ; )
        {
            var ln = reader.ReadLine();
            if (ln == null) break;
            if (Tokenize(ln, col, row))
            {
                data.Add(new Record(row.ToArray()));
                row.Clear();
            }
        }

        return data;
    }

    public static bool Tokenize(string s, StringBuilder col, List<string> row)
    {
        int i = 0;

        if (col.Length > 0)
        {
            col.AppendLine(); // continuation

            if (!TokenizeQuote(s, ref i, col, row))
            {
                return false;
            }
        }

        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == ',')
            {
                row.Add(col.ToString().Trim());
                col.Length = 0;
                i++;
            }
            else if (ch == '"')
            {
                i++;
                if (!TokenizeQuote(s, ref i, col, row))
                {
                    return false;
                }
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }

        if (col.Length > 0)
        {
            row.Add(col.ToString().Trim());
            col.Length = 0;
        }

        return true;
    }

    public static bool TokenizeQuote(string s, ref int i, StringBuilder col, List<string> row)
    {
        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == '"')
            {
                // escape sequence
                if (i + 1 < s.Length && s[i + 1] == '"')
                {
                    col.Append('"');
                    i++;
                    i++;
                    continue;
                }
                i++;
                return true;
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }
        return false;
    }
}

이 코드는 csv를 DataTable로 읽습니다.

public static DataTable ReadCsv(string path)
{
    DataTable result = new DataTable("SomeData");
    using (TextFieldParser parser = new TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
        bool isFirstRow = true;
        //IList<string> headers = new List<string>();

        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isFirstRow)
            {
                foreach (string field in fields)
                {
                    result.Columns.Add(new DataColumn(field, typeof(string)));
                }
                isFirstRow = false;
            }
            else
            {
                int i = 0;
                DataRow row = result.NewRow();
                foreach (string field in fields)
                {
                    row[i++] = field;
                }
                result.Rows.Add(row);
            }
        }
    }
    return result;
}

이 목록의 또 다른 하나 인 Cinchoo ETL- 여러 파일 형식 (CSV, 플랫 파일, Xml, JSON 등)을 읽고 쓰는 오픈 소스 라이브러리

아래 샘플은 CSV 파일을 빠르게 읽는 방법을 보여줍니다 (POCO 객체 필요 없음)

string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

using (var p = ChoCSVReader.LoadText(csv)
    .WithFirstLineHeader()
    )
{
    foreach (var rec in p)
    {
        Console.WriteLine($"Id: {rec.Id}");
        Console.WriteLine($"Name: {rec.Name}");
    }
}

아래 샘플은 POCO 객체를 사용하여 CSV 파일을 읽는 방법을 보여줍니다

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

static void CSVTest()
{
    string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

    using (var p = ChoCSVReader<EmployeeRec>.LoadText(csv)
        .WithFirstLineHeader()
        )
    {
        foreach (var rec in p)
        {
            Console.WriteLine($"Id: {rec.Id}");
            Console.WriteLine($"Name: {rec.Name}");
        }
    }
}

CodeProject 에서 사용 방법에 대한 기사를 확인 하십시오.


C # split () 함수를 사용하여 CSV를 올바르게 분할하는 방법 에 대한 무제한의 게시물을 기반으로 합니까? :

string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");

참고 : 이스케이프 / 중첩 쉼표 등은 처리하지 않으므로 특정 간단한 CSV 목록에만 적합합니다.

참고 URL : https://stackoverflow.com/questions/2081418/parsing-csv-files-in-c-with-header

반응형