Подтвердить что ты не робот

Разделите строку с разделителями-запятыми как с кавычками, так и с номерами без кавычек

У меня есть следующая строка, разделенная запятыми, которую нужно разбить. Проблема в том, что часть содержимого находится внутри кавычек и содержит запятые, которые не должны использоваться в расколе...

String

111,222,"33,44,55",666,"77,88","99"

Мне нужен вывод:

111  
222  
33,44,55  
666  
77,88  
99  

Я пробовал это:

(?:,?)((?<=")[^"]+(?=")|[^",]+)   

Но он читает запятую между "77,88", "99" как хитом, и я получаю следующий вывод:

111  
222  
33,44,55  
666  
77,88  
,  
99  

Может кто-нибудь мне помочь? У меня не хватает часов...:) /Peter

4b9b3361

Ответ 1

В зависимости от ваших потребностей, вы не сможете использовать парсер CSV, и на самом деле можете захотеть заново изобрести колесо!

Вы можете сделать это с помощью простого регулярного выражения

(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)

Это сделает следующее:

(?:^|,)= Выражение Match "Начало строки или строки , "

(\"(?:[^\"]+|\"\")*\"|[^,]*)= Пронумерованная группа захвата, она будет выбирать между 2 альтернативами:

  1. вещи в кавычках
  2. вещи между запятыми

Это должно дать вам результат, который вы ищете.

Пример кода в С#

 static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);

public static string[] SplitCSV(string input)
{

  List<string> list = new List<string>();
  string curr = null;
  foreach (Match match in csvSplit.Matches(input))
  {        
    curr = match.Value;
    if (0 == curr.Length)
    {
      list.Add("");
    }

    list.Add(curr.TrimStart(','));
  }

  return list.ToArray();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
    Console.WriteLine(SplitCSV("111,222,\"33,44,55\",666,\"77,88\",\"99\""));
}

Предупреждение Согласно комментарию @MrE - если в плохо сформированном csv файле появляется мошеннический символ новой строки, и в результате вы получите неровную ("строку"), вы получите катастрофический возврат (https://www.regular-expressions.info/catastrophic.html) в вашем регулярном выражении, и ваша система, скорее всего, выйдет из строя (как это сделала наша производственная система). Может быть легко скопирована в Visual Studio, и, как я обнаружил, это приведет к ее аварийному завершению. Простая попытка/отлов также не решит эту проблему.

Вы должны использовать:

(?:^|,)(\"(?:[^\"])*\"|[^,]*)

вместо

Ответ 2

Мне очень нравится ответ jimplode, но я думаю, что версия с возвратом доходности немного более полезна, поэтому вот она:

public IEnumerable<string> SplitCSV(string input)
{
    Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);

    foreach (Match match in csvSplit.Matches(input))
    {
        yield return match.Value.TrimStart(',');
    }
}

Может быть, еще более полезно использовать его как метод расширения:

public static class StringHelper
{
    public static IEnumerable<string> SplitCSV(this string input)
    {
        Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);

        foreach (Match match in csvSplit.Matches(input))
        {
            yield return match.Value.TrimStart(',');
        }
    }
}

Ответ 3

Это регулярное выражение работает без необходимости цитировать значения и TrimStart(','), как в принятом ответе:

((?<=\")[^\"]*(?=\"(,|$)+)|(?<=,|^)[^,\"]*(?=,|$))

Вот реализация в С#:

string values = "111,222,\"33,44,55\",666,\"77,88\",\"99\"";

MatchCollection matches = new Regex("((?<=\")[^\"]*(?=\"(,|$)+)|(?<=,|^)[^,\"]*(?=,|$))").Matches(values);

foreach (var match in matches)
{
    Console.WriteLine(match);
}

Выходы

111  
222  
33,44,55  
666  
77,88  
99  

Ответ 5

Попробуйте следующее:

       string s = @"111,222,""33,44,55"",666,""77,88"",""99""";

       List<string> result = new List<string>();

       var splitted = s.Split('"').ToList<string>();
       splitted.RemoveAll(x => x == ",");
       foreach (var it in splitted)
       {
           if (it.StartsWith(",") || it.EndsWith(","))
           {
               var tmp = it.TrimEnd(',').TrimStart(',');
               result.AddRange(tmp.Split(','));
           }
           else
           {
               if(!string.IsNullOrEmpty(it)) result.Add(it);
           }
      }
       //Results:

       foreach (var it in result)
       {
           Console.WriteLine(it);
       }

Ответ 6

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

    private string[] splitString(string stringToSplit)
{
    char[] characters = stringToSplit.ToCharArray();
    List<string> returnValueList = new List<string>();
    string tempString = "";
    bool blockUntilEndQuote = false;
    bool blockUntilEndQuote2 = false;
    int characterCount = 0;
    foreach (char character in characters)
    {
        characterCount = characterCount + 1;

        if (character == '"' && !blockUntilEndQuote2)
        {
            if (blockUntilEndQuote == false)
            {
                blockUntilEndQuote = true;
            }
            else if (blockUntilEndQuote == true)
            {
                blockUntilEndQuote = false;
            }
        }
        if (character == '\'' && !blockUntilEndQuote)
        {
            if (blockUntilEndQuote2 == false)
            {
                blockUntilEndQuote2 = true;
            }
            else if (blockUntilEndQuote2 == true)
            {
                blockUntilEndQuote2 = false;
            }
        }

        if (character != ',')
        {
            tempString = tempString + character;
        }
        else if (character == ',' && (blockUntilEndQuote == true || blockUntilEndQuote2 == true))
        {
            tempString = tempString + character;
        }
        else
        {
            returnValueList.Add(tempString);
            tempString = "";
        }

        if (characterCount == characters.Length)
        {
            returnValueList.Add(tempString);
            tempString = "";
        }
    }

    string[] returnValue = returnValueList.ToArray();
    return returnValue;
}

Ответ 7

Ни один из этих ответов не работает, когда строка содержит запятую внутри кавычек, как в "value, 1", или экранированные двойные кавычки, как в "value ""1""", которые действительный CSV, который следует анализировать как value, 1 и value "1" соответственно.

Это также будет работать с форматом с разделителями табуляции, если вы передадите вкладку вместо запятой в качестве разделителя.

public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
    var currentString = new StringBuilder();
    var inQuotes = false;
    var quoteIsEscaped = false; //Store when a quote has been escaped.
    row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
    foreach (var character in row.Select((val, index) => new {val, index}))
    {
        if (character.val == delimiter) //We hit a delimiter character...
        {
            if (!inQuotes) //Are we inside quotes? If not, we've hit the end of a cell value.
            {
                Console.WriteLine(currentString);
                yield return currentString.ToString();
                currentString.Clear();
            }
            else
            {
                currentString.Append(character.val);
            }
        } else {
            if (character.val != ' ')
            {
                if(character.val == '"') //If we've hit a quote character...
                {
                    if(character.val == '\"' && inQuotes) //Does it appear to be a closing quote?
                    {
                        if (row[character.index + 1] == character.val) //If the character afterwards is also a quote, this is to escape that (not a closing quote).
                        {
                            quoteIsEscaped = true; //Flag that we are escaped for the next character. Don't add the escaping quote.
                        }
                        else if (quoteIsEscaped)
                        {
                            quoteIsEscaped = false; //This is an escaped quote. Add it and revert quoteIsEscaped to false.
                            currentString.Append(character.val);
                        }
                        else
                        {
                            inQuotes = false;
                        }
                    }
                    else
                    {
                        if (!inQuotes)
                        {
                            inQuotes = true;
                        }
                        else
                        {
                            currentString.Append(character.val); //...It a quote inside a quote.
                        }
                    }
                }
                else
                {
                    currentString.Append(character.val);
                }
            }
            else
            {
                if (!string.IsNullOrWhiteSpace(currentString.ToString())) //Append only if not new cell
                {
                    currentString.Append(character.val);
                }
            }
        }
    }
}

Ответ 8

Быстро и просто:

    public static string[] SplitCsv(string line)
    {
        List<string> result = new List<string>();
        StringBuilder currentStr = new StringBuilder("");
        bool inQuotes = false;
        for (int i = 0; i < line.Length; i++) // For each character
        {
            if (line[i] == '\"') // Quotes are closing or opening
                inQuotes = !inQuotes;
            else if (line[i] == ',') // Comma
            {
                if (!inQuotes) // If not in quotes, end of current string, add it to result
                {
                    result.Add(currentStr.ToString());
                    currentStr.Clear();
                }
                else
                    currentStr.Append(line[i]); // If in quotes, just add it 
            }
            else // Add any other character to current string
                currentStr.Append(line[i]); 
        }
        result.Add(currentStr.ToString());
        return result.ToArray(); // Return array of all strings
    }

С этой строкой в ​​качестве входных данных:

 111,222,"33,44,55",666,"77,88","99"

Он вернется:

111  
222  
33,44,55  
666  
77,88  
99  

Ответ 9

Я знаю, что я немного опаздываю на это, но для поисков, вот как я сделал то, о чем вы спрашиваете, в C sharp

private string[] splitString(string stringToSplit)
    {
        char[] characters = stringToSplit.ToCharArray();
        List<string> returnValueList = new List<string>();
        string tempString = "";
        bool blockUntilEndQuote = false;
        int characterCount = 0;
        foreach (char character in characters)
        {
            characterCount = characterCount + 1;

            if (character == '"')
            {
                if (blockUntilEndQuote == false)
                {
                    blockUntilEndQuote = true;
                }
                else if (blockUntilEndQuote == true)
                {
                    blockUntilEndQuote = false;
                }
            }

            if (character != ',')
            {
                tempString = tempString + character;
            }
            else if (character == ',' && blockUntilEndQuote == true)
            {
                tempString = tempString + character;
            }
            else
            {
                returnValueList.Add(tempString);
                tempString = "";
            }

            if (characterCount == characters.Length)
            {
                returnValueList.Add(tempString);
                tempString = "";
            }
        }

        string[] returnValue = returnValueList.ToArray();
        return returnValue;
    }

Ответ 10

С небольшими обновлениями к функции, предоставляемой "Чадом Хедгкоком".

Обновления включены:

Строка 26: character.val == '\ "' - Это никогда не может быть правдой из-за проверки, сделанной в строке 24. Т.е. character.val == '" '

Строка 28: if (строка [character.index + 1] == character.val) добавлена! quoteIsEscaped, чтобы избежать 3 последовательных кавычек.

public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
var currentString = new StringBuilder();
var inQuotes = false;
var quoteIsEscaped = false; //Store when a quote has been escaped.
row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
foreach (var character in row.Select((val, index) => new {val, index}))
{
    if (character.val == delimiter) //We hit a delimiter character...
    {
        if (!inQuotes) //Are we inside quotes? If not, we've hit the end of a cell value.
        {
            //Console.WriteLine(currentString);
            yield return currentString.ToString();
            currentString.Clear();
        }
        else
        {
            currentString.Append(character.val);
        }
    } else {
        if (character.val != ' ')
        {
            if(character.val == '"') //If we've hit a quote character...
            {
                if(character.val == '"' && inQuotes) //Does it appear to be a closing quote?
                {
                    if (row[character.index + 1] == character.val && !quoteIsEscaped) //If the character afterwards is also a quote, this is to escape that (not a closing quote).
                    {
                        quoteIsEscaped = true; //Flag that we are escaped for the next character. Don't add the escaping quote.
                    }
                    else if (quoteIsEscaped)
                    {
                        quoteIsEscaped = false; //This is an escaped quote. Add it and revert quoteIsEscaped to false.
                        currentString.Append(character.val);
                    }
                    else
                    {
                        inQuotes = false;
                    }
                }
                else
                {
                    if (!inQuotes)
                    {
                        inQuotes = true;
                    }
                    else
                    {
                        currentString.Append(character.val); //...It a quote inside a quote.
                    }
                }
            }
            else
            {
                currentString.Append(character.val);
            }
        }
        else
        {
            if (!string.IsNullOrWhiteSpace(currentString.ToString())) //Append only if not new cell
            {
                currentString.Append(character.val);
            }
        }
    }
}

}

Ответ 11

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

Если вы выполняете синтаксический анализ CSV, вы должны просто придерживаться с использованием парсера CSV - не изобретать колесо.

Ответ 12

Вот моя самая быстрая реализация, основанная на строковой обработке указателя:

string[] FastSplit(string sText, char? cSeparator = null, char? cQuotes = null)
    {            
        string[] oTokens;

        if (null == cSeparator)
        {
            cSeparator = DEFAULT_PARSEFIELDS_SEPARATOR;
        }

        if (null == cQuotes)
        {
            cQuotes = DEFAULT_PARSEFIELDS_QUOTE;
        }

        unsafe
        {
            fixed (char* lpText = sText)
            {
                #region Fast array estimatation

                char* lpCurrent      = lpText;                    
                int   nEstimatedSize = 0;

                while (0 != *lpCurrent)
                {
                    if (cSeparator == *lpCurrent)
                    {
                        nEstimatedSize++;
                    }

                    lpCurrent++;
                }

                nEstimatedSize++; // Add EOL char(s)
                string[] oEstimatedTokens = new string[nEstimatedSize];

                #endregion

                #region Parsing

                char[] oBuffer = new char[sText.Length];
                int    nIndex  = 0;
                int    nTokens = 0;

                lpCurrent      = lpText;

                while (0 != *lpCurrent)
                {
                    if (cQuotes == *lpCurrent)
                    {
                        // Quotes parsing

                        lpCurrent++; // Skip quote
                        nIndex = 0;  // Reset buffer

                        while (
                               (0       != *lpCurrent)
                            && (cQuotes != *lpCurrent)
                        )
                        {
                            oBuffer[nIndex] = *lpCurrent; // Store char

                            lpCurrent++; // Move source cursor
                            nIndex++;    // Move target cursor
                        }

                    } 
                    else if (cSeparator == *lpCurrent)
                    {
                        // Separator char parsing

                        oEstimatedTokens[nTokens++] = new string(oBuffer, 0, nIndex); // Store token
                        nIndex                      = 0;                              // Skip separator and Reset buffer
                    }
                    else
                    {
                        // Content parsing

                        oBuffer[nIndex] = *lpCurrent; // Store char
                        nIndex++;                     // Move target cursor
                    }

                    lpCurrent++; // Move source cursor
                }

                // Recover pending buffer

                if (nIndex > 0)
                {
                    // Store token

                    oEstimatedTokens[nTokens++] = new string(oBuffer, 0, nIndex);
                }

                // Build final tokens list

                if (nTokens == nEstimatedSize)
                {
                    oTokens = oEstimatedTokens;
                }
                else
                {
                    oTokens = new string[nTokens];
                    Array.Copy(oEstimatedTokens, 0, oTokens, 0, nTokens);
                }

                #endregion
            }
        }

        // Epilogue            

        return oTokens;
    }

Ответ 13

В настоящее время я использую следующее регулярное выражение:

    public static Regex regexCSVSplit = new Regex(@"(?x:(
        (?<FULL>
        (^|[,;\t\r\n])\s*
        ( (?<CODAT> (?<CO>[""'])(?<DAT>([^,;\t\r\n]|(?<!\k<CO>\s*)[,;\t\r\n])*)\k<CO>) |
          (?<CODAT> (?<DAT> [^""',;\s\r\n]* )) )
        (?=\s*([,;\t\r\n]|$))
        ) |
        (?<FULL>
        (^|[\s\t\r\n])
        ( (?<CODAT> (?<CO>[""'])(?<DAT> [^""',;\s\t\r\n]* )\k<CO>) |
        (?<CODAT> (?<DAT> [^""',;\s\t\r\n]* )) )
        (?=[,;\s\t\r\n]|$))
        ))", RegexOptions.Compiled);

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

Вот как передать результат в массив:

    var data = regexCSVSplit.Matches(line_to_process).Cast<Match>().Select(x => x.Groups["DAT"].Value).ToArray();

См. этот пример в действии ЗДЕСЬ

Ответ 14

Мне нужно было что-то немного более надежное, поэтому я взял отсюда и создал это... Это решение немного менее изящное и немного более подробное, но в моем тестировании (с образцом в 1 000 000 строк) я нашел это в 2 - 3 раза быстрее. Кроме того, он обрабатывает неэкранированные, внедренные кавычки. Из-за требований моего решения я использовал разделитель строк и квалификаторы вместо символов. Мне было труднее, чем я ожидал найти хороший, общий CSV-парсер, поэтому я надеюсь, что этот алгоритм синтаксического анализа может помочь кому-то.

    public static string[] SplitRow(string record, string delimiter, string qualifier, bool trimData)
    {
        // In-Line for example, but I implemented as string extender in production code
        Func <string, int, int> IndexOfNextNonWhiteSpaceChar = delegate (string source, int startIndex)
        {
            if (startIndex >= 0)
            {
                if (source != null)
                {
                    for (int i = startIndex; i < source.Length; i++)
                    {
                        if (!char.IsWhiteSpace(source[i]))
                        {
                            return i;
                        }
                    }
                }
            }

            return -1;
        };

        var results = new List<string>();
        var result = new StringBuilder();
        var inQualifier = false;
        var inField = false;

        // We add new columns at the delimiter, so append one for the parser.
        var row = $"{record}{delimiter}";

        for (var idx = 0; idx < row.Length; idx++)
        {
            // A delimiter character...
            if (row[idx]== delimiter[0])
            {
                // Are we inside qualifier? If not, we've hit the end of a column value.
                if (!inQualifier)
                {
                    results.Add(trimData ? result.ToString().Trim() : result.ToString());
                    result.Clear();
                    inField = false;
                }
                else
                {
                    result.Append(row[idx]);
                }
            }

            // NOT a delimiter character...
            else
            {
                // ...Not a space character
                if (row[idx] != ' ')
                {
                    // A qualifier character...
                    if (row[idx] == qualifier[0])
                    {
                        // Qualifier is closing qualifier...
                        if (inQualifier && row[IndexOfNextNonWhiteSpaceChar(row, idx + 1)] == delimiter[0])
                        {
                            inQualifier = false;
                            continue;
                        }

                        else
                        {
                            // ...Qualifier is opening qualifier
                            if (!inQualifier)
                            {
                                inQualifier = true;
                            }

                            // ...It a qualifier inside a qualifier.
                            else
                            {
                                inField = true;
                                result.Append(row[idx]);
                            }
                        }
                    }

                    // Not a qualifier character...
                    else
                    {
                        result.Append(row[idx]);
                        inField = true;
                    }
                }

                // ...A space character
                else
                {
                    if (inQualifier || inField)
                    {
                        result.Append(row[idx]);
                    }
                }
            }
        }

        return results.ToArray<string>();
    }

Некоторые тестовые коды:

        //var input = "111,222,\"33,44,55\",666,\"77,88\",\"99\"";

        var input =
            "111, 222, \"99\",\"33,44,55\" ,      \"666 \"mark of a man\"\", \" spaces \"77,88\"   \"";

        Console.WriteLine("Split with trim");
        Console.WriteLine("---------------");
        var result = SplitRow(input, ",", "\"", true);
        foreach (var r in result)
        {
            Console.WriteLine(r);
        }
        Console.WriteLine("");

        // Split 2
        Console.WriteLine("Split with no trim");
        Console.WriteLine("------------------");
        var result2 = SplitRow(input, ",", "\"", false);
        foreach (var r in result2)
        {
            Console.WriteLine(r);
        }
        Console.WriteLine("");

        // Time Trial 1
        Console.WriteLine("Experimental Process (1,000,000) iterations");
        Console.WriteLine("-------------------------------------------");
        watch = Stopwatch.StartNew();
        for (var i = 0; i < 1000000; i++)
        {
            var x1 = SplitRow(input, ",", "\"", false);
        }
        watch.Stop();
        elapsedMs = watch.ElapsedMilliseconds;
        Console.WriteLine($"Total Process Time: {string.Format("{0:0.###}", elapsedMs / 1000.0)} Seconds");
        Console.WriteLine("");

Результаты

Split with trim
---------------
111
222
99
33,44,55
666 "mark of a man"
spaces "77,88"

Split with no trim
------------------
111
222
99
33,44,55
666 "mark of a man"
 spaces "77,88"

Original Process (1,000,000) iterations
-------------------------------
Total Process Time: 7.538 Seconds

Experimental Process (1,000,000) iterations
--------------------------------------------
Total Process Time: 3.363 Seconds

Ответ 15

Попробуйте это

private string[] GetCommaSeperatedWords(string sep, string line)
    {
        List<string> list = new List<string>();
        StringBuilder word = new StringBuilder();
        int doubleQuoteCount = 0;
        for (int i = 0; i < line.Length; i++)
        {
            string chr = line[i].ToString();
            if (chr == "\"")
            {
                if (doubleQuoteCount == 0)
                    doubleQuoteCount++;
                else
                    doubleQuoteCount--;

                continue;
            }
            if (chr == sep && doubleQuoteCount == 0)
            {
                list.Add(word.ToString());
                word = new StringBuilder();
                continue;
            }
            word.Append(chr);
        }

        list.Add(word.ToString());

        return list.ToArray();
    }

Ответ 16

Это ответ Чада, переписанный с логикой, основанной на состоянии. Его ответ потерпел неудачу для меня, когда он натолкнулся на """BRAD""" как поле. Это должно вернуть "BRAD" но он просто съел все оставшиеся поля. Когда я попытался отладить его, я просто переписал его как основанную на состоянии логику:

enum SplitState { s_begin, s_infield, s_inquotefield, s_foundquoteinfield };
public static IEnumerable<string> SplitRow(string row, char delimiter = ',')
{
    var currentString = new StringBuilder();
    SplitState state = SplitState.s_begin;
    row = string.Format("{0}{1}", row, delimiter); //We add new cells at the delimiter, so append one for the parser.
    foreach (var character in row.Select((val, index) => new { val, index }))
    {
        //Console.WriteLine("character = " + character.val + " state = " + state);
        switch (state)
        {
            case SplitState.s_begin:
                if (character.val == delimiter)
                {
                    /* empty field */
                    yield return currentString.ToString();
                    currentString.Clear();
                } else if (character.val == '"')
                {
                    state = SplitState.s_inquotefield;
                } else
                {
                    currentString.Append(character.val);
                    state = SplitState.s_infield;
                }
                break;
            case SplitState.s_infield:
                if (character.val == delimiter)
                {
                    /* field with data */
                    yield return currentString.ToString();
                    state = SplitState.s_begin;
                    currentString.Clear();
                } else
                {
                    currentString.Append(character.val);
                }
                break;
            case SplitState.s_inquotefield:
                if (character.val == '"')
                {
                    // could be end of field, or escaped quote.
                    state = SplitState.s_foundquoteinfield;
                } else
                {
                    currentString.Append(character.val);
                }
                break;
            case SplitState.s_foundquoteinfield:
                if (character.val == '"')
                {
                    // found escaped quote.
                    currentString.Append(character.val);
                    state = SplitState.s_inquotefield;
                }
                else if (character.val == delimiter)
                {
                    // must have been last quote so we must find delimiter
                    yield return currentString.ToString();
                    state = SplitState.s_begin;
                    currentString.Clear();
                }
                else
                {
                    throw new Exception("Quoted field not terminated.");
                }
                break;
            default:
                throw new Exception("unknown state:" + state);
        }
    }
    //Console.WriteLine("currentstring = " + currentString.ToString());
}

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