Разделить строку в С#

Я думал, что это будет тривиально, но я не могу заставить это работать.

Предположим, что строка в CSV файле: "Barack Obama", 48, "President", "1600 Penn Ave, Washington DC"

string[] tokens = line.split(',')

Я ожидаю этого:

 "Barack Obama"
 48
 "President"
 "1600 Penn Ave, Washington DC"

но последний токен    'Washington DC' не   "1600 Penn Ave, Washington DC".

Есть ли простой способ заставить функцию split игнорировать запятую внутри кавычек?

У меня нет контроля над файлом CSV, и он не отправляется мне. Клиент A будет использовать приложение для чтения файлов, предоставленных внешним человеком.

Ответ 1

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

  • Итерации через каждый char в строке
  • Когда вы нажмете символ ", переключите логическое
  • Когда вы нажимаете запятую, если bool является true, игнорируйте его, иначе у вас есть ваш токен

Вот пример:

public static class StringExtensions
{
    public static string[] SplitQuoted(this string input, char separator, char quotechar)
    {
        List<string> tokens = new List<string>();

        StringBuilder sb = new StringBuilder();
        bool escaped = false;
        foreach (char c in input)
        {
            if (c.Equals(separator) && !escaped)
            {
                // we have a token
                tokens.Add(sb.ToString().Trim());
                sb.Clear();
            }
            else if (c.Equals(separator) && escaped)
            {
                // ignore but add to string
                sb.Append(c);
            }
            else if (c.Equals(quotechar))
            {
                escaped = !escaped;
                sb.Append(c);
            }
            else
            {
                sb.Append(c);
            }
        }
        tokens.Add(sb.ToString().Trim());

        return tokens.ToArray();
    }
}

Затем просто позвоните:

string[] tokens = line.SplitQuoted(',','\"');

Бенчмарки

Ниже приведены результаты бенчмаркинга моего кода и кода Dan Tao. Я рад оценить любые другие решения, если люди хотят их?

Код:

string input = "\"Barak Obama\", 48, \"President\", \"1600 Penn Ave, Washington DC\""; // Console.ReadLine()
string[] tokens = null;

// run tests
DateTime start = DateTime.Now;
for (int i = 0; i < 1000000; i++)
    tokens = input.SplitWithQualifier(',', '\"', false);
Console.WriteLine("1,000,000 x SplitWithQualifier = {0}ms", DateTime.Now.Subtract(start).TotalMilliseconds);

start = DateTime.Now;
for (int i = 0; i<1000000;i++)
    tokens = input.SplitQuoted(',', '\"');
Console.WriteLine("1,000,000 x SplitQuoted =        {0}ms", DateTime.Now.Subtract(start).TotalMilliseconds);

Вывод:

1,000,000 x SplitWithQualifier = 8156.25ms
1,000,000 x SplitQuoted =        2406.25ms

Ответ 2

У меня есть метод расширения SplitWithQualifier, который я использую здесь и там, который использует Regex.

Я не претендую на надежность этого кода, но он все время работал у меня.

// mangled code horribly to fit without scrolling
public static class CsvSplitter
{
    public static string[] SplitWithQualifier(this string text,
                                              char delimiter,
                                              char qualifier,
                                              bool stripQualifierFromResult)
    {
        string pattern = string.Format(
            @"{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))",
            Regex.Escape(delimiter.ToString()),
            Regex.Escape(qualifier.ToString())
        );

        string[] split = Regex.Split(text, pattern);

        if (stripQualifierFromResult)
            return split.Select(s => s.Trim().Trim(qualifier)).ToArray();
        else
            return split;
    }
}

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

string csv = "\"Barak Obama\", 48, \"President\", \"1600 Penn Ave, Washington DC\"";
string[] values = csv.SplitWithQualifier(',', '\"', true);

foreach (string value in values)
    Console.WriteLine(value);

Вывод:

Barak Obama
48
President
1600 Penn Ave, Washington DC

Ответ 3

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

Быстрый считыватель CSV

Я бы рекомендовал библиотеку (исходный код), которую вы можете получить на этой странице CodeProject: http://www.codeproject.com/KB/database/CsvReader.aspx p >

Я лично использую его сам и люблю. Это родной код .NET и намного быстрее, чем использование OLEDB (который также может выполнять синтаксический анализ CSV для вас, но поверьте, он медленный).

Ответ 5

Это будет ожидаемое поведение, поскольку кавычки - это всего лишь еще один строковый символ в С#. Похоже, что вы находитесь за процитированными токенами или числовыми токенами.

Я думаю, вам может понадобиться использовать Regex для разделения строк, если кто-то другой не знает лучшего способа.

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

Ответ 6

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

Вот ссылка на простой метод С#, основанный на регулярном выражении, который преобразует ваш CSV в ручную DataTable:

http://www.hotblue.com/article0000.aspx?a=0006

Работа с DataTables очень проста - сообщите мне, если вам нужен образец кода для этого.

Ответ 7

Я бы рекомендовал вместо этого использовать регулярное выражение. Это позволит вам извлекать более сложные подстроки гораздо более универсальным образом (именно так, как вы хотите).

http://www.c-sharpcorner.com/uploadfile/prasad_1/regexppsd12062005021717am/regexppsd.aspx

http://oreilly.com/windows/archive/csharp-regular-expressions.html

Ответ 8

Не можете ли вы изменить способ создания CSV? Используя OpenOffice, вы можете установить разделитель char (use;) и как строка делится (используя "или" ).

Это было бы так: "Президент", "1600 Пенн-авеню, Вашингтон, округ Колумбия"

Ответ 9

string temp = line.Replace( "\" "," ");

string [] tokens = temp.Split(',')