Разбор CSV файлов в С# с заголовком
Есть ли способ по умолчанию/официальный/рекомендуемый способ разбора CSV файлов на С#? Я не хочу качать собственный парсер.
Кроме того, я видел экземпляры людей, использующих ODBC/OLE DB для чтения CSV через драйвер Text, и многие люди препятствуют этому из-за его "недостатков". Каковы эти недостатки?
В идеале я ищу способ, с помощью которого я могу прочитать CSV по имени столбца, используя первую запись в качестве имен заголовка/поля. Некоторые из приведенных ответов верны, но работают, чтобы в основном десериализовать файл в классы.
Ответы
Ответ 1
Позвольте библиотеке обрабатывать все подробные детали для вас!: -)
Откажитесь от FileHelpers и оставайтесь СУХОЙ - не повторяйте себя - не нужно повторно изобретать колесо в gazillionth time....
В основном вам просто нужно определить ту форму ваших данных - поля в вашей отдельной строке в CSV - с помощью открытого класса (и так хорошо продуманные атрибуты, как значения по умолчанию, замены для значений NULL и т.д.), укажите движок FileHelpers в файле и bingo - вы получите все записи из этого файла. Одна простая операция - отличная производительность!
Ответ 2
Анализатор CSV теперь является частью .NET Framework.
Добавьте ссылку на Microsoft.VisualBasic.dll (прекрасно работает в С#, не берите в голову название)
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 (диск: я один из участников)
Ответ 3
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>();
}
}
Ответ 4
В бизнес-приложении я использую проект с открытым исходным кодом на 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();
}
}
Как вы можете видеть, с ним очень легко работать.
Ответ 5
Я знаю его немного позже, но только что нашел библиотеку Microsoft.VisualBasic.FileIO
, у которой есть класс TextFieldParser
для обработки файлов csv.
Ответ 6
Если вам нужны только чтение файлов csv, я рекомендую эту библиотеку: Быстрый CSV-ридер
Если вам также нужно сгенерировать файлы csv, используйте следующий: FileHelpers
Оба они являются свободными и openource.
Ответ 7
Вот вспомогательный класс, который я часто использую, если кто-нибудь когда-нибудь возвращается к этой теме (я хотел бы поделиться ею).
Я использую это для простоты переноса его в готовые к использованию проекты:
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: исправлена ошибка, когда последний новый символ строки создал новую строку]
Ответ 8
Это решение использует официальную сборку 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;
}
Ответ 9
Я написал 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);
}
}
}
Руководство пользователя
Полное руководство пользователя доступно по адресу:
Ответ 10
Я искал очень быстрое решение и не хотел добавлять дополнительные зависимости. Поскольку то, что я нашел, не было оптимальным для того, что я хотел сделать, я написал свой собственный. Не стесняйтесь использовать его.
ОБНОВЛЕННЫЙ КОД ЗДЕСЬ:
https://gist.github.com/mariodivece/9614872
Ответ 11
Нет официального пути, о котором я знаю, но вы действительно должны использовать существующие библиотеки. Вот один, который я нашел очень полезным в CodeProject:
http://www.codeproject.com/KB/database/CsvReader.aspx
Ответ 12
Вот моя реализация 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;
}
}
Ответ 13
Некоторое время назад я написал простой класс для чтения/записи CSV на основе библиотеки Microsoft.VisualBasic
. Используя этот простой класс, вы сможете работать с 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]
:)
Ответ 14
Единый исходный файл для простого анализа, полезен. Имеет дело со всеми неприятными крайними случаями. Например, нормализация новой строки и обработка новых строк в строковых литералах в кавычках. Пожалуйста!
Если ваш 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;
}
}
Ответ 15
Основываясь на unlimit post на Как правильно разбить CSV с помощью функции С# split()?:
string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");
ПРИМЕЧАНИЕ: это не относится к экранированным/вложенным запятым и т.д., и поэтому подходит только для некоторых простых списков CSV.
Ответ 16
Этот код считывает 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;
}
Ответ 17
Еще одна в этом списке, 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 о том, как его использовать.