Як розділити CSV, стовпці якого можуть містити,


105

Дано

2,1016,7 / 31/2008 14: 22, Джефф Далгас, 6/5/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

Як ви бачите, один із стовпців містить, <= (Корваліс, АБО)

// оновлення // На основі C # Regex Split - коми поза котируваннями

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

1
Хоча в Java, аналогічне питання: stackoverflow.com/questions/1757065 / ...
sgokhales

1
Використовувати регулярний вираз для цього - погана порада. .NET Framework вже має вбудовану підтримку для розбору CSV. Дивіться цю відповідь, яку ви повинні прийняти. В іншому випадку я закрию це як мандат stackoverflow.com/questions/3147836/…, що так само неправильно.
Кев

Чи можете ви, будь ласка, розробити, що таке вбудована підтримка .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
Джефф Далгас
5.05.2011 22:21
http://stackoverflow.com
Корваліс, АБО
7679
351
81
b437f461b3fd27387c5d8ab47a293d35
34

Докладнішу інформацію див. У розділі Microsoft.VisualBasic.FileIO.TextFieldParser .

Вам потрібно додати посилання на Microsoft.VisualBasicвкладку .NET Add References .NET References.


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

@tim я використав це, і помічає його пропуск усіх парних номерів рядків, лише обробляючи непарні номери рядків у файлі, що містить 1050 рядків. якісь ідеї?
Сміт

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

Я навіть не знав про цю бібліотеку, поки не побачив цього - дякую! Якщо хтось хоче приклад, який аналізує весь файл CSV, дивіться цю відповідь ТАК: stackoverflow.com/a/3508572/3105807
Емі Барретт

2
Чи можемо ми лінчувати Microsoft за те, що він не надав конструктор, який займає рядок, тому нам доведеться перейти через обруч перетворення його в потік? Інакше приємна відповідь.
Лорен Печтел

43

Це так пізно, але це може бути корисно для когось. Ми можемо використовувати RegEx як нижче.

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

4
Це прекрасно. Краще використовувати це, ніж імпортувати цілу іншу бібліотеку. Браво.
TheGeekYouNeed

1
Матчі asdf, "", "as ,\" df ",

Це рішення працює не правильно - воно не враховує мовних знаків, тобто під час читання буде багато мовленнєвих позначок у неправильних місцях.
АйданH

Що робити, якщо кінцева цитата відсутня в якомусь рядку: asd, "", "as, \" df "," asd asd "," as
MarmiK

1
Це працювало на мене і припадало на процитовані мовленнєві оцінки. 30 мільйонів рядів з них. Дуже хороший і мінімальна кількість коду.
GBGOLC

4

Ви можете розділити всі коми, які мають парну кількість цитат після них.

Ви також хочете переглянути у specfформаті для CSV про обробку комами.

Корисне посилання: C# Regex Split - commas outside quotes


3
@ q0987 - це не правильна відповідь. Підтримка цього існує в рамках: stackoverflow.com/questions/6542996/…
Кев

4

Я бачу, що якщо ви вставите текст з обмеженим CSV в Excel і зробите "Текст до стовпців", він запитає вас про "текстовий класифікатор". Він за замовчуванням є подвійною цитатою, так що він розглядає текст у подвійних лапках як буквальний. Я думаю, що Excel реалізує це, переходячи по одному символу, якщо він стикається з "текстовим класифікатором", він продовжує переходити до наступного "класифікатора". Ви, ймовірно, можете реалізувати це самостійно за допомогою циклу for і boolean, щоб позначити, якщо ви знаходитесь у прямому тексті.

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

Одним із моїх підходів є те, як я тимчасово замінюю розмежувальні коми на нечіткий символ unicode. Цей символ повинен бути таким незрозумілим, він ніколи не відображатиметься у вашому файлі .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.