fractionner une chaîne séparée par des virgules à la fois avec des chaînes entre guillemets et sans guillemets

J’ai la chaîne de caractères séparée par des virgules que je dois séparer. Le problème est qu’une partie du contenu est entre guillemets et contient des virgules qui ne devraient pas être utilisées dans le fractionnement …

Chaîne:

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

Je veux la sortie:

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

J’ai essayé ceci:

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

Mais il lit la virgule entre “77,88”, “99” comme coup et j’obtiens la sortie suivante:

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

Quelqu’un peut-il m’aider? Je suis à court d’heures … 🙂 / Peter

Selon vos besoins, vous ne pourrez peut-être pas utiliser un parsingur csv et voudrez peut-être ré-inventer la roue !!

Vous pouvez le faire avec une simple expression rationnelle

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

Cela fera ce qui suit:

(?:^|,) = Correspond à l’expression “Début de ligne ou de chaîne”

(\"(?:[^\"]+|\"\")*\"|[^,]*) = Un groupe de capture numéroté, cela sélectionnera entre 2 alternatives:

  1. farcir entre guillemets
  2. des choses entre les virgules

Cela devrait vous donner la sortie que vous recherchez.

Exemple de code en C #

 public static ssortingng[] SplitCSV(ssortingng input) { Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Comstackd); List list = new List(); ssortingng 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\"")); } 

Avertissement Selon le commentaire de @MEDE – si un nouveau caractère de ligne apparaît dans un fichier csv mal formé et que vous vous retrouvez avec une chaîne inégale (“vous allez avoir un retour en arrière catastrophique” ( https://www.regular-expressions.info/ catastrophic.html ) dans votre regex et votre système va probablement tomber en panne (comme notre système de production l’a fait). Peut facilement être répliqué dans Visual Studio et comme je l’ai découvert va le planter. Un simple try / catch ne va pas bloquer ce problème non plus.

Tu devrais utiliser:

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

au lieu

J’aime vraiment la réponse de jimplode, mais je pense qu’une version avec rendement est un peu plus utile, la voici:

 public IEnumerable SplitCSV(ssortingng input) { Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Comstackd); foreach (Match match in csvSplit.Matches(input)) { yield return match.Value.TrimStart(','); } } 

Peut-être qu’il est encore plus utile de l’avoir comme une méthode d’extension:

 public static class SsortingngHelper { public static IEnumerable SplitCSV(this ssortingng input) { Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Comstackd); foreach (Match match in csvSplit.Matches(input)) { yield return match.Value.TrimStart(','); } } } 

Cette expression régulière fonctionne sans avoir besoin de parcourir les valeurs et TrimStart(',') , comme dans la réponse acceptée:

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

Voici l'implémentation en C #:

 ssortingng values = "111,222,\"33,44,55\",666,\"77,88\",\"99\""; MatchCollection matches = new Regex("((?<=\")[^\"]*(?=\"(,|$)+)|(?<=,|^)[^,\"]*(?=,|$))").Matches(values); foreach (var match in matches) { Console.WriteLine(match); } 

Les sorties

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

Essaye ça:

  ssortingng s = @"111,222,""33,44,55"",666,""77,88"",""99"""; List result = new List(); var splitted = s.Split('"').ToList(); 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(!ssortingng.IsNullOrEmpty(it)) result.Add(it); } } //Results: foreach (var it in result) { Console.WriteLine(it); } 

Pour la réponse de Jay, si vous utilisez un 2e booléen, vous pouvez insérer des guillemets doubles entre guillemets simples et vice-versa.

  private ssortingng[] splitSsortingng(ssortingng ssortingngToSplit) { char[] characters = ssortingngToSplit.ToCharArray(); List returnValueList = new List(); ssortingng tempSsortingng = ""; 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 != ',') { tempSsortingng = tempSsortingng + character; } else if (character == ',' && (blockUntilEndQuote == true || blockUntilEndQuote2 == true)) { tempSsortingng = tempSsortingng + character; } else { returnValueList.Add(tempSsortingng); tempSsortingng = ""; } if (characterCount == characters.Length) { returnValueList.Add(tempSsortingng); tempSsortingng = ""; } } ssortingng[] returnValue = returnValueList.ToArray(); return returnValue; } 

Aucune de ces réponses ne fonctionne lorsque la chaîne a une virgule entre guillemets, comme dans "value, 1" , ou des guillemets doubles, comme dans "value ""1""" , qui sont des CSV valides devant être analysés comme value, 1 et la value "1" , respectivement.

Cela fonctionnera également avec le format délimité par des tabulations si vous passez dans un onglet au lieu d’une virgule comme délimiteur.

 public static IEnumerable SplitRow(ssortingng row, char delimiter = ',') { var currentSsortingng = new SsortingngBuilder(); var inQuotes = false; var quoteIsEscaped = false; //Store when a quote has been escaped. row = ssortingng.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(currentSsortingng); yield return currentSsortingng.ToSsortingng(); currentSsortingng.Clear(); } else { currentSsortingng.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. currentSsortingng.Append(character.val); } else { inQuotes = false; } } else { if (!inQuotes) { inQuotes = true; } else { currentSsortingng.Append(character.val); //...It's a quote inside a quote. } } } else { currentSsortingng.Append(character.val); } } else { if (!ssortingng.IsNullOrWhiteSpace(currentSsortingng.ToSsortingng())) //Append only if not new cell { currentSsortingng.Append(character.val); } } } } } 

Je sais que je suis un peu en retard pour cela, mais pour les recherches, voici comment j’ai fait ce que vous demandiez en C sharp

 private ssortingng[] splitSsortingng(ssortingng ssortingngToSplit) { char[] characters = ssortingngToSplit.ToCharArray(); List returnValueList = new List(); ssortingng tempSsortingng = ""; 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 != ',') { tempSsortingng = tempSsortingng + character; } else if (character == ',' && blockUntilEndQuote == true) { tempSsortingng = tempSsortingng + character; } else { returnValueList.Add(tempSsortingng); tempSsortingng = ""; } if (characterCount == characters.Length) { returnValueList.Add(tempSsortingng); tempSsortingng = ""; } } ssortingng[] returnValue = returnValueList.ToArray(); return returnValue; } 

Ne réinventez pas un parsingur CSV, essayez FileHelpers .

Avec des mises à jour mineures de la fonction fournie par “Chad Hedgcock”.

Les mises à jour sont sur:

Ligne 26: character.val == ‘\ “‘ – Cela ne peut jamais être vrai à cause de la vérification faite sur la ligne 24. ie character.val == ‘”‘

Ligne 28: if (ligne [character.index + 1] == character.val) ajouté! QuoteIsEscaped pour échapper à 3 guillemets consécutifs.

 public static IEnumerable SplitRow(ssortingng row, char delimiter = ',') { var currentSsortingng = new SsortingngBuilder(); var inQuotes = false; var quoteIsEscaped = false; //Store when a quote has been escaped. row = ssortingng.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(currentSsortingng); yield return currentSsortingng.ToSsortingng(); currentSsortingng.Clear(); } else { currentSsortingng.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. currentSsortingng.Append(character.val); } else { inQuotes = false; } } else { if (!inQuotes) { inQuotes = true; } else { currentSsortingng.Append(character.val); //...It's a quote inside a quote. } } } else { currentSsortingng.Append(character.val); } } else { if (!ssortingng.IsNullOrWhiteSpace(currentSsortingng.ToSsortingng())) //Append only if not new cell { currentSsortingng.Append(character.val); } } } } 

}

Rapide et facile:

  public static ssortingng[] SplitCsv(ssortingng line) { List result = new List(); SsortingngBuilder currentStr = new SsortingngBuilder(""); 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 } 

Avec cette chaîne en entrée:

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

Il reviendra:

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

Une fois, j’ai dû faire quelque chose de similaire et à la fin, je me suis retrouvé avec des expressions régulières. L’incapacité de Regex à avoir un état le rend assez compliqué – j’ai fini par écrire un petit parsingur simple .

Si vous effectuez une parsing CSV, vous devez vous limiter à utiliser un parsingur CSV – ne réinventez pas la roue.

Voici mon implémentation la plus rapide basée sur la manipulation de pointeurs bruts:

 ssortingng[] FastSplit(ssortingng sText, char? cSeparator = null, char? cQuotes = null) { ssortingng[] 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) ssortingng[] oEstimatedTokens = new ssortingng[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 ssortingng(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 ssortingng(oBuffer, 0, nIndex); } // Build final tokens list if (nTokens == nEstimatedSize) { oTokens = oEstimatedTokens; } else { oTokens = new ssortingng[nTokens]; Array.Copy(oEstimatedTokens, 0, oTokens, 0, nTokens); } #endregion } } // Epilogue return oTokens; } 

J’avais besoin de quelque chose d’un peu plus robuste, j’ai donc pris d’ici et créé ceci … Cette solution est un peu moins élégante et un peu plus prolixe, mais dans mes tests (avec un échantillon de 1 000 000 lignes), j’ai trouvé que c’était 2 à 3 fois plus vite. De plus, il gère les devis incorporés non échappés. J’ai utilisé un délimiteur de chaîne et des qualificatifs à la place des caractères à cause des exigences de ma solution. J’ai trouvé cela plus difficile que prévu à trouver un bon parsingur CSV générique, alors j’espère que cet algorithme d’parsing peut aider quelqu’un.

  public static ssortingng[] SplitRow(ssortingng record, ssortingng delimiter, ssortingng qualifier, bool sortingmData) { // In-Line for example, but I implemented as ssortingng extender in production code Func  IndexOfNextNonWhiteSpaceChar = delegate (ssortingng 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(); var result = new SsortingngBuilder(); 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's 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(); } 

Un code de test:

  //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 sortingm"); Console.WriteLine("---------------"); var result = SplitRow(input, ",", "\"", true); foreach (var r in result) { Console.WriteLine(r); } Console.WriteLine(""); // Split 2 Console.WriteLine("Split with no sortingm"); 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(""); 

Résultats

 Split with sortingm --------------- 111 222 99 33,44,55 666 "mark of a man" spaces "77,88" Split with no sortingm ------------------ 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 

Actuellement, j’utilise la regex suivante:

  public static Regex regexCSVSplit = new Regex(@"(?x:( (? (^|[,;\t\r\n])\s* ( (? (?[""'])(?([^,;\t\r\n]|(?\s*)[,;\t\r\n])*)\k) | (? (? [^""',;\s\r\n]* )) ) (?=\s*([,;\t\r\n]|$)) ) | (? (^|[\s\t\r\n]) ( (? (?[""'])(? [^""',;\s\t\r\n]* )\k) | (? (? [^""',;\s\t\r\n]* )) ) (?=[,;\s\t\r\n]|$)) ))", RegexOptions.Comstackd); 

Cette solution peut gérer des cas assez chaotiques comme ci-dessous: entrer la description de l'image ici

Voici comment alimenter le résultat dans un tableau:

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

Voir cet exemple en action ICI

Essaye ça

 private ssortingng[] GetCommaSeperatedWords(ssortingng sep, ssortingng line) { List list = new List(); SsortingngBuilder word = new SsortingngBuilder(); 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(); } 

C’est la réponse de Chad réécrite avec la logique basée sur l’état. Sa réponse a échoué pour moi quand il a rencontré """BRAD""" comme un champ. Cela devrait renvoyer "BRAD" mais il suffit de manger tous les champs restants. Lorsque j’ai essayé de le déboguer, j’ai fini par le réécrire en tant que logique basée sur l’état:

 enum SplitState { s_begin, s_infield, s_inquotefield, s_foundquoteinfield }; public static IEnumerable SplitRow(ssortingng row, char delimiter = ',') { var currentSsortingng = new SsortingngBuilder(); SplitState state = SplitState.s_begin; row = ssortingng.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 currentSsortingng.ToSsortingng(); currentSsortingng.Clear(); } else if (character.val == '"') { state = SplitState.s_inquotefield; } else { currentSsortingng.Append(character.val); state = SplitState.s_infield; } break; case SplitState.s_infield: if (character.val == delimiter) { /* field with data */ yield return currentSsortingng.ToSsortingng(); state = SplitState.s_begin; currentSsortingng.Clear(); } else { currentSsortingng.Append(character.val); } break; case SplitState.s_inquotefield: if (character.val == '"') { // could be end of field, or escaped quote. state = SplitState.s_foundquoteinfield; } else { currentSsortingng.Append(character.val); } break; case SplitState.s_foundquoteinfield: if (character.val == '"') { // found escaped quote. currentSsortingng.Append(character.val); state = SplitState.s_inquotefield; } else if (character.val == delimiter) { // must have been last quote so we must find delimiter yield return currentSsortingng.ToSsortingng(); state = SplitState.s_begin; currentSsortingng.Clear(); } else { throw new Exception("Quoted field not terminated."); } break; default: throw new Exception("unknown state:" + state); } } //Console.WriteLine("currentssortingng = " + currentSsortingng.ToSsortingng()); } 

Ceci est beaucoup plus de lignes de code que les autres solutions, mais il est facile à modifier pour append des cas limites.