Разбор CSV-файлов в C #, с заголовком - PullRequest
229 голосов
/ 17 января 2010

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

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

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

Ответы [ 16 ]

1 голос
/ 20 января 2018

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

Если ваш 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;
    }
}
1 голос
/ 01 апреля 2017

Некоторое время назад я написал простой класс для чтения / записи 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] ячейки:)

1 голос
/ 20 декабря 2016

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

}
0 голосов
/ 04 октября 2017

Еще один в этом списке, 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}");
    }
}

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

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 о том, как его использовать.

0 голосов
/ 16 июня 2017

Этот код читает 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;
}
0 голосов
/ 26 июля 2016

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

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

ПРИМЕЧАНИЕ: он не обрабатывает экранированные / вложенные запятые и т. Д. И поэтому подходит только для некоторых простых списков CSV.

...