Синтаксический анализ CSV-файлов в C#, с заголовком

есть ли по умолчанию / официальный / рекомендуемый способ разбора CSV-файлов на C#? Я не хочу сворачивать свой собственный парсер.

кроме того, я видел примеры людей, использующих ODBC/OLE DB для чтения CSV через текстовый драйвер, и многие люди препятствуют этому из-за его "недостатков".- Что это за недостатки?

В идеале я ищу способ, с помощью которого я могу прочитать CSV по имени столбца, используя первую запись в качестве имен заголовков / полей. Некоторые из приведенных ответов верны, но работа по десериализации файла в классы.

16 ответов


пусть библиотека обрабатывает все мелкие детали для вас! :-)

проверить FileHelpers и остаться сухим - не повторяйся - не нужно заново изобретать колесо стопятьсот тыщ раз....

вам в основном просто нужно определить эту форму ваших данных-поля в вашей отдельной строке в CSV-с помощью открытого класса (и так хорошо продуманные атрибуты, такие как значения по умолчанию, замены для нулевых значений и так далее), укажите FileHelpers движок в файле, и бинго-вы получаете обратно все записи из этого файла. Одна простая операция-отличная производительность!


синтаксический анализатор 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


CsvHelper (библиотека, которую я поддерживаю) будет читать CSV-файл в пользовательские объекты.

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

иногда вы не владеете объектами, которые вы пытаетесь прочитать. В этом случае можно использовать fluent mapping, так как нельзя поместить атрибуты в класс.

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();
    }
}

Как вы можете видеть, это очень легко работать.


Я знаю, что это немного поздно, но только что нашел библиотеку Microsoft.VisualBasic.FileIO имеющего TextFieldParser класс для обработки CSV файлов.


Если вам нужно только читать 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 helper: Исправлена ошибка, когда последний символ новой строки создал новую строку]


это решение использует официальный 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 для .NET, который является одним из самых быстрых парсеров .NET и легко настраивается для анализа почти любого формата CSV.

он выпущен под лицензией MIT:

можно использовать NuGet для для ее установки. Выполните следующую команду в Диспетчер Пакетов Консоль.

PM> Install-Package TinyCsvParser

использование

представьте, что у нас есть список лиц в CSV-файле persons.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);
        }
    }
}

Руководство Пользователя

полное руководство пользователя доступно на:


Я не знаю официального способа, но вы действительно должны использовать существующие библиотеки. Вот один, который я нашел действительно полезным из CodeProject:

http://www.codeproject.com/KB/database/CsvReader.aspx


вот моя реализация 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;
    }

}

некоторое время назад я написал простой класс для чтения/записи CSV на основе Microsoft.VisualBasic библиотека. Используя этот простой класс, вы сможете работать с CSV как с блока 2 измерения. Вы можете найти мой класс по следующей ссылке: 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] клетки :)


решение с одним исходным файлом для простого синтаксического анализа, полезно. Имеет дело со всеми неприятными случаями edge. Например, нормализация новой строки и обработка новых строк в строковых литералах с кавычками. Добро пожаловать!

Если у CSV-файла есть заголовок, вы просто считываете имена столбцов (и вычисляете индексы столбцов) из первой строки. Просто.

отметим, что Dump является методом LINQPad, вы можете удалить его, если вы не используете 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;
    }
}

на основе сообщения unlimit на Как правильно разделить CSV с помощью функции C# split ()? :

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

Примечание: это не обрабатывает экранированные / вложенные запятые и т. д., и поэтому подходит только для некоторых простых списков CSV.


этот код читает 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)

static void ReadCSV()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoCSVReader(reader))
    {
        writer.WriteLine("id,name");
        writer.WriteLine("1,Carl");
        writer.WriteLine("2,Mark");
        writer.WriteLine("3,Tom");

        writer.Flush();
        stream.Position = 0;

        foreach (dynamic dr in parser)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr.id, dr.name);
        }
    }
}

пример ниже показывает, как читать CSV-файл с помощью объекта POCO

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; } 
}
static void ReadCSV()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoCSVReader<EmployeeRec>(reader))
    {
        writer.WriteLine("id,name");
        writer.WriteLine("1,Carl");
        writer.WriteLine("2,Mark");
        writer.WriteLine("3,Tom");

        writer.Flush();
        stream.Position = 0;

        foreach (var dr in parser)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr.id, dr.name);
        }
    }
}

пожалуйста, проверьте статьи на CodeProject о том, как использовать его.