Как разбить CSV, столбцы которого могут содержать,


105

Дано

2,1016,7 / 31/2008 14:22, Джефф Далгас, 05.06.2011 22:21, http://stackoverflow.com , «Корваллис, ИЛИ», 7679,351,81, b437f461b3fd27387c5d8ab47a293d35,34

Как использовать C # для разделения приведенной выше информации на строки следующим образом:

2
1016
7/31/2008 14:22
Geoff Dalgas
6/5/2011 22:21
http://stackoverflow.com
Corvallis, OR
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

Как видите, один из столбцов содержит <= (Corvallis, OR)

// обновление // На основе C # Regex Split - запятые вне кавычек

string[] result = Regex.Split(samplestring, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

1
Хотя в Java аналогичный вопрос: stackoverflow.com/questions/1757065/…
sgokhales 01

1
Использование регулярного выражения для этого - плохой совет. .NET Framework уже имеет встроенную поддержку для анализа CSV. Посмотрите этот ответ, который вам следует принять. В противном случае я закрою это как обман stackoverflow.com/questions/3147836/… что так же неправильно.
Kev

Не могли бы вы подробнее рассказать, что такое встроенная поддержка .NET для анализа файлов CSV со встроенными запятыми? Вы имеете в виду класс Microsoft.VisualBasic.FileIO.TextFieldParser?
AllSolutions

Ответы:


182

Воспользуйтесь Microsoft.VisualBasic.FileIO.TextFieldParserклассом. Это будет обрабатывать синтаксический анализ файла с разделителями TextReaderили Streamкогда некоторые поля заключены в кавычки, а некоторые нет.

Например:

using Microsoft.VisualBasic.FileIO;

string csv = "2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,\"Corvallis, OR\",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

TextFieldParser parser = new TextFieldParser(new StringReader(csv));

// You can also read from a file
// TextFieldParser parser = new TextFieldParser("mycsvfile.csv");

parser.HasFieldsEnclosedInQuotes = true;
parser.SetDelimiters(",");

string[] fields;

while (!parser.EndOfData)
{
    fields = parser.ReadFields();
    foreach (string field in fields)
    {
        Console.WriteLine(field);
    }
} 

parser.Close();

Это должно привести к следующему результату:

2
1016
31.07.2008 14:22
Джефф Далгас
05.06.2011 22:21
http://stackoverflow.com
Корваллис, штат Орегон
7679
351
81 год
b437f461b3fd27387c5d8ab47a293d35
34

См. Microsoft.VisualBasic.FileIO.TextFieldParser для получения дополнительной информации.

Вам необходимо добавить ссылку на Microsoft.VisualBasicвкладку «Добавить ссылки .NET».


9
Чувак, большое спасибо за это решение, у меня есть около 500 тысяч + строк данных CSV, которые мне нужно загрузить в таблицу, и она загружена запятыми, содержащимися в кавычках. Я должен вам выпить по вашему выбору взрослый напиток, если наши пути когда-нибудь пересекутся.
Марк Крам

@tim Я использовал это и заметил, что он пропускает все четные номера строк, обрабатывая только нечетные номера строк в файле, который имеет 1050 строк. Любые идеи?
Smith

@Smith - Я понятия не имею, не видя вашего кода или образца ввода. Предлагаю разместить новый вопрос. Возможно, в файле отсутствует символ возврата каретки или другой маркер конца строки на четных строках?
Тим

Я даже не знал об этой библиотеке, пока не увидел это - спасибо! Если кому-то еще нужен пример, который анализирует весь CSV-файл, см. Этот ответ SO: stackoverflow.com/a/3508572/3105807
Эми Барретт

2
Можем ли мы линчевать Microsoft за то, что она не предоставила конструктор, который принимает строку, поэтому мы должны сначала перескочить через обруч, преобразовав его в поток? В противном случае хороший ответ.
Лорен Пехтель

43

Уже так поздно, но это может быть кому-то полезно. Мы можем использовать RegEx, как показано ниже.

Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = CSVParser.Split(Test);

4
Это потрясающе. Лучше использовать это, чем импортировать целую другую библиотеку. Браво.
TheGeekYouNeed 08

1
Соответствует asdf, "", "как ,\" df ",

Это решение работает некорректно - оно не учитывает речевые знаки, а это означает, что во время чтения будет много речевых знаков в неправильных местах.
AidanH

Что делать, если конечная цитата отсутствует в какой-либо строке: asd, "", "as, \" df "," asd asd "," as "
MarmiK 01

1
Это сработало для меня и учитывает цитируемые речевые знаки. 30 миллионов строк из них. Очень хорошее и минимальное количество кода.
GBGOLC

4

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

Вы также хотели бы просмотреть в specfформате CSV информацию об обработке запятых.

Полезная ссылка: C# Regex Split - commas outside quotes


3
@ q0987 - это неправильный ответ. Для этого есть встроенная поддержка в Framework: stackoverflow.com/questions/6542996/…
Кев

4

Я вижу, что если вы вставляете текст с разделителями CSV в Excel и выполняете «Текст в столбцы», он запрашивает у вас «квалификатор текста». По умолчанию используются двойные кавычки, поэтому текст в двойных кавычках воспринимается как буквальный. Я предполагаю, что Excel реализует это, переходя по одному символу за раз, если он встречает «текстовый квалификатор», он продолжает переходить к следующему «квалификатору». Вероятно, вы можете реализовать это самостоятельно с помощью цикла for и логического значения, чтобы указать, находитесь ли вы внутри буквального текста.

public string[] CsvParser(string csvText)
{
    List<string> tokens = new List<string>();

    int last = -1;
    int current = 0;
    bool inText = false;

    while(current < csvText.Length)
    {
        switch(csvText[current])
        {
            case '"':
                inText = !inText; break;
            case ',':
                if (!inText) 
                {
                    tokens.Add(csvText.Substring(last + 1, (current - last)).Trim(' ', ',')); 
                    last = current;
                }
                break;
            default:
                break;
        }
        current++;
    }

    if (last != csvText.Length - 1) 
    {
        tokens.Add(csvText.Substring(last+1).Trim());
    }

    return tokens.ToArray();
}

3

Используйте такую ​​библиотеку, как LumenWorks, для чтения CSV. Он будет обрабатывать поля с кавычками в них и, вероятно, в целом будет более надежным, чем ваше индивидуальное решение, в силу того, что существует уже давно.


2

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

Я создал метод ParseCsvRow (), который возвращает массив из строки csv. Сначала я имею дело с двойными кавычками в строке, разбивая строку на двойные кавычки в массив с именем quotesArray. Строковые .csv-файлы в кавычках действительны только в том случае, если есть четное количество двойных кавычек. Двойные кавычки в значении столбца следует заменить парой двойных кавычек (это подход Excel). Если файл .csv соответствует этим требованиям, можно ожидать, что разделительные запятые будут появляться только вне пар двойных кавычек. Запятые внутри пар двойных кавычек являются частью значения столбца и должны игнорироваться при разделении .csv на массив.

Мой метод будет проверять наличие запятых вне пар с двойными кавычками, глядя только на четные индексы quotesArray. Он также удаляет двойные кавычки из начала и конца значений столбца.

    public static string[] ParseCsvRow(string csvrow)
    {
        const string obscureCharacter = "ᖳ";
        if (csvrow.Contains(obscureCharacter)) throw new Exception("Error: csv row may not contain the " + obscureCharacter + " character");

        var unicodeSeparatedString = "";

        var quotesArray = csvrow.Split('"');  // Split string on double quote character
        if (quotesArray.Length > 1)
        {
            for (var i = 0; i < quotesArray.Length; i++)
            {
                // CSV must use double quotes to represent a quote inside a quoted cell
                // Quotes must be paired up
                // Test if a comma lays outside a pair of quotes.  If so, replace the comma with an obscure unicode character
                if (Math.Round(Math.Round((decimal) i/2)*2) == i)
                {
                    var s = quotesArray[i].Trim();
                    switch (s)
                    {
                        case ",":
                            quotesArray[i] = obscureCharacter;  // Change quoted comma seperated string to quoted "obscure character" seperated string
                            break;
                    }
                }
                // Build string and Replace quotes where quotes were expected.
                unicodeSeparatedString += (i > 0 ? "\"" : "") + quotesArray[i].Trim();
            }
        }
        else
        {
            // String does not have any pairs of double quotes.  It should be safe to just replace the commas with the obscure character
            unicodeSeparatedString = csvrow.Replace(",", obscureCharacter);
        }

        var csvRowArray = unicodeSeparatedString.Split(obscureCharacter[0]); 

        for (var i = 0; i < csvRowArray.Length; i++)
        {
            var s = csvRowArray[i].Trim();
            if (s.StartsWith("\"") && s.EndsWith("\""))
            {
                csvRowArray[i] = s.Length > 2 ? s.Substring(1, s.Length - 2) : "";  // Remove start and end quotes.
            }
        }

        return csvRowArray;
    }

Одним из недостатков моего подхода является то, что я временно заменяю запятые-разделители непонятным символом Юникода. Этот символ должен быть настолько неясным, чтобы он никогда не отображался в вашем файле .csv. Возможно, вы захотите приложить больше усилий для этого.


1

У меня была проблема с CSV, который содержит поля с символом кавычки в них, поэтому, используя TextFieldParser, я придумал следующее:

private static string[] parseCSVLine(string csvLine)
{
  using (TextFieldParser TFP = new TextFieldParser(new MemoryStream(Encoding.UTF8.GetBytes(csvLine))))
  {
    TFP.HasFieldsEnclosedInQuotes = true;
    TFP.SetDelimiters(",");

    try 
    {           
      return TFP.ReadFields();
    }
    catch (MalformedLineException)
    {
      StringBuilder m_sbLine = new StringBuilder();

      for (int i = 0; i < TFP.ErrorLine.Length; i++)
      {
        if (i > 0 && TFP.ErrorLine[i]== '"' &&(TFP.ErrorLine[i + 1] != ',' && TFP.ErrorLine[i - 1] != ','))
          m_sbLine.Append("\"\"");
        else
          m_sbLine.Append(TFP.ErrorLine[i]);
      }

      return parseCSVLine(m_sbLine.ToString());
    }
  }
}

StreamReader по-прежнему используется для чтения CSV построчно, как показано ниже:

using(StreamReader SR = new StreamReader(FileName))
{
  while (SR.Peek() >-1)
    myStringArray = parseCSVLine(SR.ReadLine());
}

1

С Cinchoo ETL - библиотеку с открытым исходным кодом, он может автоматически обрабатывает столбцы значения , содержащие разделители.

string csv = @"2,1016,7/31/2008 14:22,Geoff Dalgas,6/5/2011 22:21,http://stackoverflow.com,""Corvallis, OR"",7679,351,81,b437f461b3fd27387c5d8ab47a293d35,34";

using (var p = ChoCSVReader.LoadText(csv)
    )
{
    Console.WriteLine(p.Dump());
}

Вывод:

Key: Column1 [Type: String]
Value: 2
Key: Column2 [Type: String]
Value: 1016
Key: Column3 [Type: String]
Value: 7/31/2008 14:22
Key: Column4 [Type: String]
Value: Geoff Dalgas
Key: Column5 [Type: String]
Value: 6/5/2011 22:21
Key: Column6 [Type: String]
Value: http://stackoverflow.com
Key: Column7 [Type: String]
Value: Corvallis, OR
Key: Column8 [Type: String]
Value: 7679
Key: Column9 [Type: String]
Value: 351
Key: Column10 [Type: String]
Value: 81
Key: Column11 [Type: String]
Value: b437f461b3fd27387c5d8ab47a293d35
Key: Column12 [Type: String]
Value: 34

Для получения дополнительной информации, пожалуйста, посетите статью codeproject.

Надеюсь, поможет.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.