web-dev-qa-db-fra.com

Regex pour scinder un CSV

Je sais que cela (ou similaire) a été demandé à plusieurs reprises, mais après avoir essayé de nombreuses possibilités, je n'ai pas été en mesure de trouver une regex qui fonctionne à 100%.

J'ai un fichier CSV et j'essaie de le scinder en un tableau, mais je rencontre deux problèmes: des virgules et des éléments vides.

Le CSV ressemble à:

123,2.99,AMO024,Title,"Description, more info",,123987564

La regex que j'ai essayé d'utiliser est:

thisLine.split(/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/)

Le seul problème est que dans mon tableau de sortie, le 5ème élément est le 123987564 et non une chaîne vide.

35
Code Ninja

La description

Au lieu d'utiliser une scission, je pense qu'il serait plus facile de simplement exécuter une correspondance et de traiter toutes les correspondances trouvées.

Cette expression va:

  • divisez votre exemple de texte sur les virgules
  • traitera les valeurs vides
  • ignorera les guillemets doubles, à condition que les guillemets doubles ne soient pas imbriqués
  • coupe la virgule de délimitation de la valeur renvoyée
  • coupe les citations environnantes à partir de la valeur renvoyée

Regex: (?:^|,)(?=[^"]|(")?)"?((?(1)[^"]*|[^,"]*))"?(?=,|$)

enter image description here

Exemple

Exemple de texte

123,2.99,AMO024,Title,"Description, more info",,123987564

ASP exemple utilisant l'expression non Java

Set regEx = New RegExp
regEx.Global = True
regEx.IgnoreCase = True
regEx.MultiLine = True
sourcestring = "your source string"
regEx.Pattern = "(?:^|,)(?=[^""]|("")?)""?((?(1)[^""]*|[^,""]*))""?(?=,|$)"
Set Matches = regEx.Execute(sourcestring)
  For z = 0 to Matches.Count-1
    results = results & "Matches(" & z & ") = " & chr(34) & Server.HTMLEncode(Matches(z)) & chr(34) & chr(13)
    For zz = 0 to Matches(z).SubMatches.Count-1
      results = results & "Matches(" & z & ").SubMatches(" & zz & ") = " & chr(34) & Server.HTMLEncode(Matches(z).SubMatches(zz)) & chr(34) & chr(13)
    next
    results=Left(results,Len(results)-1) & chr(13)
  next
Response.Write "<pre>" & results

Correspondances utilisant l'expression non Java

Le groupe 0 obtient la sous-chaîne entière qui inclut la virgule
Le groupe 1 obtient la citation si elle est utilisée
Le groupe 2 obtient la valeur sans la virgule

[0][0] = 123
[0][1] = 
[0][2] = 123

[1][0] = ,2.99
[1][1] = 
[1][2] = 2.99

[2][0] = ,AMO024
[2][1] = 
[2][2] = AMO024

[3][0] = ,Title
[3][1] = 
[3][2] = Title

[4][0] = ,"Description, more info"
[4][1] = "
[4][2] = Description, more info

[5][0] = ,
[5][1] = 
[5][2] = 

[6][0] = ,123987564
[6][1] = 
[6][2] = 123987564
44
Ro Yo Mi

J'ai créé cela il y a quelques mois pour un projet.

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

Regular expression visualization

Cela fonctionne en C # et Debuggex était heureux lorsque j'ai sélectionné Python et PCRE. Javascript ne reconnaît pas cette forme de Proceeded By ? <= ....

Pour vos valeurs, il créera des correspondances sur

123
,2.99
,AMO024
,Title
"Description, more info"
,
,123987564

Notez que tout ce qui est entre guillemets ne comporte pas de virgule, mais une tentative de correspondance avec une virgule était requise pour le cas d'utilisation de valeur vide. Une fois cela fait, réduisez les valeurs si nécessaire.

J'utilise RegexHero.Net pour tester mon regex.

9
scott.smart

J'avais besoin de cette réponse aussi, mais j'ai trouvé les réponses informatives, un peu difficiles à suivre et à reproduire pour d'autres langues. Voici l'expression la plus simple que j'ai trouvée pour une seule colonne de la ligne CSV. Je ne me sépare pas. Je construis une expression rationnelle pour faire correspondre une colonne du CSV, donc je ne divise pas la ligne:

("([^"]*)"|[^,]*)(,|$)

Cela correspond à une seule colonne de la ligne CSV. La première partie "([^"]*)" de l'expression doit correspondre à une entrée entre guillemets, la deuxième partie [^,]* doit correspondre à une entrée sans guillemet Ensuite, suivi d'un , ou de la fin de la ligne $.

Et le debuggex qui l'accompagne pour tester l'expression.

https://www.debuggex.com/r/s4z_Qi2gZiyzpAhx

6
chubbsondubs

Travaillé un peu sur ce sujet et proposé cette solution:

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

Essayez ici!

Cette solution gère les "belles" données CSV comme

"a","b",c,"d",e,f,,"g"

0: "a"
1: "b"
2: c
3: "d"
4: e
5: f
6:
7: "g"

et des choses plus laides comme

"""test"" one",test' two,"""test"" 'three'","""test 'four'"""

0: """test"" one"
1: test' two
2: """test"" 'three'"
3: """test 'four'"""

Voici une explication de comment cela fonctionne :

(?:,|\n|^)      # all values must start at the beginning of the file,  
                #   the end of the previous line, or at a comma  
(               # single capture group for ease of use; CSV can be either...  
  "             # ...(A) a double quoted string, beginning with a double quote (")  
    (?:         #        character, containing any number (0+) of  
      (?:"")*   #          escaped double quotes (""), or  
      [^"]*     #          non-double quote characters  
    )*          #        in any order and any number of times  
  "             #        and ending with a double quote character  

  |             # ...or (B) a non-quoted value  

  [^",\n]*      # containing any number of characters which are not  
                # double quotes ("), commas (,), or newlines (\n)  

  |             # ...or (C) a single newline or end-of-file character,  
                #           used to capture empty values at the end of  
  (?:\n|$)      #           the file or at the ends of lines  
)
6
awwsmm

Je suis en retard à la fête, mais voici l'expression régulière que j'utilise:

(?:,"|^")(""|[\w\W]*?)(?=",|"$)|(?:,(?!")|^(?!"))([^,]*?)(?=$|,)|(\r\n|\n)

Ce modèle comporte trois groupes de capture:

  1. Contenu d'une cellule citée
  2. Contenu d'une cellule non citée
  3. Une nouvelle ligne

Ce modèle gère tous les éléments suivants:

  • Contenu de la cellule normale sans caractéristiques particulières: un, 2, trois
  • Cellule contenant une double citation ("est échappé à" "): sans citation," a "" cité "" chose ", fin
  • La cellule contient un caractère de nouvelle ligne: un, deux\n trois, quatre
  • Contenu de la cellule normale avec une citation interne: un, deux "trois, quatre
  • La cellule contient des guillemets suivis d'une virgule: un, "deux" "trois" ", quatre", cinq

Voir ce modèle en cours d'utilisation.

Si vous utilisez un type plus rationnel de regex avec des groupes nommés et des recherches, je préfère ce qui suit:

(?<quoted>(?<=,"|^")(?:""|[\w\W]*?)*(?=",|"$))|(?<normal>(?<=,(?!")|^(?!"))[^,]*?(?=(?<!")$|(?<!"),))|(?<eol>\r\n|\n)

Voir ce modèle en cours d'utilisation.

Modifier

(?:^"|,")(""|[\w\W]*?)(?=",|"$)|(?:^(?!")|,(?!"))([^,]*?)(?=$|,)|(\r\n|\n)

Ce modèle légèrement modifié gère les lignes où la première colonne est vide tant que vous n'utilisez pas Javascript. Pour une raison quelconque, Javascript omettra la deuxième colonne avec ce modèle. Je suis incapable de gérer correctement ce cas Edge.

5
bublebboy

J'ai personnellement essayé de nombreuses expressions RegEx sans avoir trouvé celle qui convient à tous les cas.

Je pense que les expressions rationnelles sont difficiles à configurer correctement pour correspondre à tous les cas correctement. résultats corrects tous les temps dans tous les cas (principalement gérer très bien chaque cas de guillemets doubles):

Microsoft.VisualBasic.FileIO.TextFieldParser

Je l'ai trouvé ici: StackOverflow

Exemple d'utilisation:

TextReader textReader = new StringReader(simBaseCaseScenario.GetSimStudy().Study.FilesToDeleteWhenComplete);
Microsoft.VisualBasic.FileIO.TextFieldParser textFieldParser = new TextFieldParser(textReader);
textFieldParser.SetDelimiters(new string[] { ";" });
string[] fields = textFieldParser.ReadFields();
foreach (string path in fields)
{
    ...

J'espère que ça pourrait aider.

3
Eric Ouellet

L'avantage d'utiliser JScript pour les pages classiques ASP est que vous pouvez utiliser l'une des nombreuses bibliothèques écrites pour JavaScript.

Comme celui-ci: https://github.com/gkindel/CSV-JS . Téléchargez-le, incluez-le dans votre page ASP, analysez-le au format CSV.

<%@ language="javascript" %>

<script language="javascript" runat="server" src="scripts/csv.js"></script>
<script language="javascript" runat="server">

var text = '123,2.99,AMO024,Title,"Description, more info",,123987564',
    rows = CSV.parse(line);

    Response.Write(rows[0][4]);
</script>
3
Tomalak

En Java, ce motif ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))" ou presque travaille pour moi:

String text = "\",\",\",,\",,\",asdasd a,sd s,ds ds,dasda,sds,ds,\"";
String regex = ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))";
Pattern p = Pattern.compile(regex);
String[] split = p.split(text);
for(String s:split) {
    System.out.println(s);
}

sortie:

","
",a,,"

",asdasd a,sd s,ds ds,dasda,sds,ds,"

Inconvénient: ne fonctionne pas lorsque la colonne a un nombre impair de guillemets :(

3
marioosh

Aaaand une autre réponse ici. :) Puisque je ne pouvais pas faire fonctionner les autres tout à fait.

Ma solution gère à la fois les guillemets échappés (occurrences doubles) et n'inclut pas de délimiteurs dans la correspondance.

Notez que j'ai comparé avec ' au lieu de " car c'était mon scénario, mais remplacez-les simplement dans le motif pour le même effet.

Voilà (n'oubliez pas d'utiliser l'indicateur "Ignorer les espaces blancs" /x si vous utilisez la version commentée ci-dessous):

# Only include if previous char was start of string or delimiter
(?<=^|,)
(?:
  # 1st option: empty quoted string (,'',)
  '{2}
  |
  # 2nd option: nothing (,,)
  (?:)
  |
  # 3rd option: all but quoted strings (,123,)
  # (included linebreaks to allow multiline matching)
  [^,'\r\n]+
  |
  # 4th option: quoted strings (,'123''321',)
  # start pling
  ' 
    (?:
      # double quote
      '{2}
      |
      # or anything but quotes
      [^']+
    # at least one occurance - greedy
    )+
  # end pling
  '
)
# Only include if next char is delimiter or end of string
(?=,|$)

Version à une ligne:

(?<=^|,)(?:'{2}|(?:)|[^,'\r\n]+|'(?:'{2}|[^']+)+')(?=,|$)

Regular expression visualization (if it works, debux has issues right now it seems - else follow the next link)

Démo Debuggex

exemple regex101

1
Johny Skovdal

J'utilise celui-ci, il fonctionne avec le séparateur de coma et les guillemets doubles . Normalement, cela devrait résoudre votre problème:

/(?<=^|,)(\"(?:[^"]+|"")*\"|[^,]*)(?:$|,)/g
1

Encore une autre réponse avec quelques fonctionnalités supplémentaires, telles que la prise en charge des valeurs entre guillemets contenant des guillemets et des caractères CR/LF (valeurs uniques couvrant plusieurs lignes).

NOTE: Bien que la solution ci-dessous puisse probablement être adaptée à d'autres moteurs regex, son utilisation en l'état nécessitera que votre moteur regex traite plusieurs groupes de capture nommés utilisant le même nom as un seul groupe de capture. (.NET le fait par défaut)


Lorsque plusieurs lignes/enregistrements d'un fichier/flux CSV (correspondant norme RFC 4180 ) sont passés à l'expression régulière ci-dessous, une correspondance est trouvée pour chaque ligne/enregistrement non vide. Chaque correspondance contiendra un groupe de capture nommé Value qui contient les valeurs capturées dans cette ligne/enregistrement (et éventuellement un groupe de capture OpenValue s'il y avait un devis ouvert à la fin de la ligne/enregistrement).

Voici le motif commenté (testez-le sur Regexstorm.net ):

(?<=\r|\n|^)(?!\r|\n|$)                       // Records start at the beginning of line (line must not be empty)
(?:                                           // Group for each value and a following comma or end of line (EOL) - required for quantifier (+?)
  (?:                                         // Group for matching one of the value formats before a comma or EOL
    "(?<Value>(?:[^"]|"")*)"|                 // Quoted value -or-
    (?<Value>(?!")[^,\r\n]+)|                 // Unquoted value -or-
    "(?<OpenValue>(?:[^"]|"")*)(?=\r|\n|$)|   // Open ended quoted value -or-
    (?<Value>)                                // Empty value before comma (before EOL is excluded by "+?" quantifier later)
  )
  (?:,|(?=\r|\n|$))                           // The value format matched must be followed by a comma or EOL
)+?                                           // Quantifier to match one or more values (non-greedy/as few as possible to prevent infinite empty values)
(?:(?<=,)(?<Value>))?                         // If the group of values above ended in a comma then add an empty value to the group of matched values
(?:\r\n|\r|\n|$)                              // Records end at EOL


Voici le motif brut sans tous les commentaires ni les espaces.

(?<=\r|\n|^)(?!\r|\n|$)(?:(?:"(?<Value>(?:[^"]|"")*)"|(?<Value>(?!")[^,\r\n]+)|"(?<OpenValue>(?:[^"]|"")*)(?=\r|\n|$)|(?<Value>))(?:,|(?=\r|\n|$)))+?(?:(?<=,)(?<Value>))?(?:\r\n|\r|\n|$)


Voici une visualisation de Debuggex.com (groupes de capture nommés pour plus de clarté): Debuggex.com visualization

Vous trouverez des exemples d'utilisation du motif regex dans ma réponse à une question similaire ici , ou sur pavé C # ici ou ici .

1
David Woodward
,?\s*'.+?'|,?\s*".+?"|[^"']+?(?=,)|[^"']+  

Cette expression rationnelle fonctionne avec des guillemets simples et doubles et aussi pour une citation dans une autre!

0
zerocool

Si vous savez que vous n'avez pas de champ vide (,), cette expression fonctionne bien:

("[^"]*"|[^,]+)

Comme dans l'exemple suivant ...

Set rx = new RegExp
rx.Pattern = "(""[^""]*""|[^,]+)"
rx.Global = True
Set col = rx.Execute(sText)
For n = 0 to col.Count - 1
    if n > 0 Then s = s & vbCrLf
    s = s & col(n)
Next

Toutefois, si vous prévoyez un champ vide et que votre texte est relativement petit, vous pouvez envisager de remplacer les champs vides par un espace avant l'analyse afin de garantir leur capture. Par exemple...

...
Set col = rx.Execute(Replace(sText, ",,", ", ,"))
...

Et si vous devez maintenir l'intégrité des champs, vous pouvez restaurer les virgules et tester les espaces vides à l'intérieur de la boucle. Ce n’est peut-être pas la méthode la plus efficace, mais le travail est fait. 

0
k rey

Si j'essaye l'expression régulière publiée par @chubbsondubs sur http://regex101.com en utilisant l'indicateur 'g', il existe des correspondances contenant uniquement ',' ou une chaîne vide . Avec cette expression régulière: 
(?:"([^"]*)"|([^,]*))(?:[,]) 
Je peux faire correspondre les parties du CSV (y compris les parties citées). (La ligne doit être terminée par un ',' sinon la dernière partie n'est pas reconnue.) 
https://regex101.com/r/dF9kQ8/4
Si le CSV ressemble à:
"",huhu,"hel lo",world, 
il y a 4 correspondances:
'' 
'huhu' 
'Bonjour' 
'monde' 

0
deemon

Celui-ci correspond à tout ce dont j'ai besoin en c #:

(?<=(^|,)(?<quote>"?))([^"]|(""))*?(?=\<quote>(?=,|$))
  • bandes de citations
  • laisse de nouvelles lignes
  • permet les guillemets doubles dans la chaîne citée
  • permet des virgules dans la chaîne citée
0
Ilya Rudenko

J'avais le même besoin de fractionner les valeurs CSV à partir d'instructions SQL. 

Dans mon cas, je pouvais supposer que les chaînes étaient entourées de guillemets simples et que les chiffres ne l'étaient pas.

csv.split(/,((?=')|(?=\d))/g).filter(function(x) { return x !== '';});

Pour une raison probablement évidente, cette expression régulière produit des résultats vierges. Je pouvais les ignorer, car toutes les valeurs vides de mes données étaient représentées par ...,'',... et non pas ...,,....

0
peter