2010-02-06 4 views
12

Je souhaite analyser les lignes de données CSV entrantes. . Les valeurs sont séparées par des virgules (et il pourrait y avoir avant et arrière espaces blancs autour des virgules), et peuvent être cités soit avec "ou » Par exemple - c'est une ligne valide:Expression régulière Python pour lire les lignes de type CSV

data1, data2 ,"data3'''", 'data4""',,,data5, 

mais celui-ci est malformé :

data1, data2, da"ta3", 'data4', 

- entre guillemets ne peuvent être préfixés ou traînés par des espaces

Ces lignes malformés doivent être reconnues - mieux serait de marquer en quelque sorte la valeur malformé dans les rangs, mais si regex ne correspond pas. toute la rangée, alors c'est aussi acceptable

J'essaie d'écrire regex capable d'analyser ceci, en utilisant soit match() de findall(), mais chaque regex que je viens avec a quelques problèmes avec les cas de bordure. Donc, peut-être quelqu'un ayant de l'expérience dans l'analyse de quelque chose de similaire pourrait m'aider à ce sujet? (Ou peut-être cela est trop complexe pour regex et je devrais écrire une fonction)

EDIT1:

Module csv est pas beaucoup d'utilisation ici:

>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''))) 
    [['2', ' "dat', 'a1"', " 'dat", "a2'", '']] 

    >>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''))) 
    [['2', 'dat,a1', "'dat", "a2'", '']] 

- à moins que cela peut être réglé?

EDIT2: Quelques modifications langue - j'espère que ce sera plus valide anglais maintenant

EDIT3: Merci pour toutes les réponses, je suis maintenant assez sûr que l'expression régulière n'est pas une bonne idée ici (1) couvrant tous les cas de bord peuvent être difficiles (2) la sortie de l'auteur n'est pas régulière. En écrivant cela, j'ai décidé de vérifier le pypars mentionné et de l'utiliser, ou d'écrire un analyseur personnalisé de type FSM.

+2

Le module 'csv' * ne peut pas * être" réglé "pour gérer les situations que vous décrivez. Même votre premier exemple, avec deux styles différents de guillemets, ne peut pas être traité, pour autant que je sache, nonobstant toutes les personnes qui ne peuvent pas prendre la peine de lire votre question à fond. –

+0

@Peter Hansen: vous avez raison; le format décrit ci-dessus ne peut pas être géré par le module csv - il ne gère pas les caractères de citation alternatifs. –

+0

En alternative, demandez à celui qui prépare le fichier regex, utilisez les valeurs séparées par des tabulations (TSV), afin de pouvoir le lire avec csv.reader. – mootmoot

Répondre

6

Bien qu'il serait probablement possible avec une combinaison de pré-traitement, l'utilisation de csv module, post-traitement, et l'utilisation d'expressions régulières, vos exigences indiquées ne correspondent pas bien avec la conception du module csv, ni éventuellement avec des expressions régulières (en fonction de la complexité des guillemets imbriqués que vous pourriez avoir à gérer).

Dans les cas d'analyse complexe, pyparsing est toujours un bon package sur lequel se rabattre. S'il ne s'agit pas d'une situation ponctuelle, cela produira probablement le résultat le plus simple et le plus prévisible, au prix d'un petit effort supplémentaire à l'avance. Considérez cet investissement pour être remboursé rapidement, cependant, comme vous vous épargnez l'effort supplémentaire de déboguer les solutions regex pour gérer les cas de coin ...

Vous pouvez probablement trouver des exemples d'analyse syntaxique basé sur pyparsing facilement, avec this question assez pour vous aider à démarrer.

+0

+1 pour le lien vers la question connexe, plus la bonne description de quand vous sentez que l'atteinte de pyparsing est appropriée. Contrairement à l'autre question, qui avait un format très rigide, cette question serait plus susceptible d'utiliser quelque chose comme 'delimitedList (Word (alphanums) | quotedString)', car une delimitedList recherche par défaut des délimiteurs de virgules. – PaulMcG

4

Python dispose d'un module de bibliothèque standard pour lire les fichiers csv:

import csv 

reader = csv.reader(open('file.csv')) 

for line in reader: 
    print line 

Pour cela imprime votre entrée exemple

['data1', ' data2 ', "data3'''", ' \'data4""\'', '', '', 'data5', ''] 

EDIT:

vous devez ajouter skipinitalspace = True pour permettre espaces avant les guillemets doubles pour les exemples supplémentaires que vous avez fournis. Je ne suis pas encore sûr des citations simples.

>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''), skipinitialspace=True)) 
[['2', 'dat,a1', "'dat", "a2'", '']] 

>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''), skipinitialspace=True)) 
[['2', 'dat,a1', "'dat", "a2'", '']] 
+0

Oui, ce serait beaucoup plus propre que regex dans ce cas, et notez que le module est assez flexible - vous pouvez définir vos propres délimiteurs, citation de caractères et terminaisons de ligne assez facilement. Voir: http://docs.python.org/library/csv.html. –

+0

@Max S, ai-je oublié quelque chose dans les docs, ou existe-t-il un moyen de le configurer pour gérer deux caractères de guillemets différents comme les demandes OP? –

+0

@Peter: Non, c'est la seule fonctionnalité manquante dont le demandeur a besoin, c'est pourquoi j'ai posté ma solution regex plus générale. Cependant, il est peu probable dans une donnée réelle, et 'csv' est plus propre. –

8

Alors que le module csv est la bonne réponse ici, une expression rationnelle qui pourrait le faire est tout à fait faisable:

import re 

r = re.compile(r''' 
    \s*    # Any whitespace. 
    (     # Start capturing here. 
     [^,"']+?   # Either a series of non-comma non-quote characters. 
     |    # OR 
     "(?:    # A double-quote followed by a string of characters... 
      [^"\\]|\\. # That are either non-quotes or escaped... 
     )*    # ...repeated any number of times. 
     "    # Followed by a closing double-quote. 
     |    # OR 
     '(?:[^'\\]|\\.)*'# Same as above, for single quotes. 
    )     # Done capturing. 
    \s*    # Allow arbitrary space before the comma. 
    (?:,|$)   # Followed by a comma or the end of a string. 
    ''', re.VERBOSE) 

line = r"""data1, data2 ,"data3'''", 'data4""',,,data5,""" 

print r.findall(line) 

# That prints: ['data1', 'data2', '"data3\'\'\'"', '\'data4""\'', 'data5'] 

EDIT: Pour valider les lignes, vous pouvez réutiliser l'expression rationnelle ci-dessus avec une petite ajouts:

import re 

r_validation = re.compile(r''' 
    ^(?: # Capture from the start. 
     # Below is the same regex as above, but condensed. 
     # One tiny modification is that it allows empty values 
     # The first plus is replaced by an asterisk. 
     \s*([^,"']*?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*(?:,|$) 
    )*$ # And don't stop until the end. 
    ''', re.VERBOSE) 

line1 = r"""data1, data2 ,"data3'''", 'data4""',,,data5,""" 
line2 = r"""data1, data2, da"ta3", 'data4',""" 

if r_validation.match(line1): 
    print 'Line 1 is valid.' 
else: 
    print 'Line 1 is INvalid.' 

if r_validation.match(line2): 
    print 'Line 2 is valid.' 
else: 
    print 'Line 2 is INvalid.' 

# Prints: 
# Line 1 is valid. 
# Line 2 is INvalid. 
+0

Je suis à peu près sûr que vous devez changer les deux occurrences '[" "] | \\.' (Et les faire comme ceci: '\\. | [^"] ') ou les changer en '[^" \\] | \\. '. Parce que' ["]' va correspondre à une barre oblique inverse, une barre oblique inverse ou une double citation échappée ne sera pas correctement * analysée *. –

+0

Exactement. J'oublie toujours de mettre le backslash dans la classe de caractères. Le simple fait de les commuter ne fonctionnerait pas pour valider, car le moteur peut simplement revenir en arrière sur le backslash. –

+0

Vrai, les ajouter dans la classe de caractères négative est la meilleure option. Je le remarque toujours parce que j'ai moi-même glissé tant de fois!:) –

1

Cela semble probablement trop simple, mais vraiment de l'apparence des choses que vous recherchez une chaîne qui contient soit [a-zA-Z0-9] ["'] + [a-zA-Z0-9], Je veux dire sans tests approfondis sur les données, ce que vous cherchez est vraiment un devis ou une double citation (ou une combinaison) entre les lettres (vous pouvez également ajouter des chiffres)

D'après ce que vous demandiez, Peu importe que ce soit un CSV, c'est que vous avez des données non conformes, ce que je crois juste faire une recherche de lettre, puis une combinaison d'un ou plusieurs "ou" et d'une autre lettre.

Maintenant cherchez-vous à obtenir une "quantité" ou juste une impression de la ligne qui le contient afin que vous sachiez lesquels retourner et réparer?

Je suis désolé, je ne sais pas de python regex mais en perl ce ressemblerait à quelque chose comme ceci:

# Look for one or more letter/number at least one ' or " or more and at least one  
# or more letter/number 
if ($line =~ m/[a-zA-Z0-9]+['"]+[a-zA-Z0-9]+/ig) 
{ 
    # Prints the line if the above regex is found 
    print $line; 

} 

Tout simplement convertir que lorsque vous regardez une ligne.

Je suis désolé si j'ai mal compris la question

J'espère que ça aide!

2

Il n'est pas possible de vous donner une réponse, car vous n'avez pas spécifié complètement le protocole utilisé par l'enregistreur.

Il contient évidemment des règles comme:

Si un champ contient des virgules ou des guillemets simples, je cite avec guillemets doubles.
Sinon, si le champ contient des guillemets doubles, citez-le avec des guillemets simples.
Note: le résultat est toujours valide si vous échangez le double et le simple dans les 2 clauses ci-dessus.
Sinon, ne le citez pas.
Le champ résultant peut contenir des espaces (ou d'autres espaces?) Ajoutés ou ajoutés.
Les champs ainsi augmentés sont assemblés en une rangée, séparés par des virgules et terminés par la nouvelle ligne de la plate-forme (LF ou CRLF).

Ce qui n'est pas mentionné est ce que l'auteur fait dans ces cas:
(0) champ contient des apostrophes et des guillemets doubles
(1) champ contient trois non-retour à la ligne des espaces
champ (2) contient de fuite champ non-newline espace
(3) contient toutes les nouvelles lignes.
Si l'auteur ignore l'un de ces cas, veuillez spécifier les résultats souhaités.

Vous mentionnez également "les guillemets ne peuvent être ajoutés ou précédés que par des espaces" - vous voulez certainement dire que les virgules sont également autorisées, sinon votre exemple 'data4""',,,data5, échoue à la première virgule.

Comment vos données sont-elles codées?

+0

Ceci est l'un de ces cas où vous devez «être libéral dans ce que vous acceptez» - je ne sais pas comment fonctionne l'écrivain (c'est un service web à source fermée, probablement sujet à changement), je n'ai que quelques exemples de données directives qui dit plus ou moins "vous pouvez vous attendre à presque tout". –

+2

"Soyez libéral", etc. s'applique lorsque vous avez un standard et que vous avez déterminé que les auteurs s'écartent légèrement d'une manière qui peut être tolérée par un lecteur sans effets secondaires. Vous n'avez aucune norme et aucune expérience avec les écrivains. Oui, vous pouvez vous attendre à presque n'importe quoi, mais n'avez montré aucune preuve d'agir sur cette attente. Je suggère que vous soyez plutôt intolérant: établissez une attente, un programme pour cela, et notez les différences par rapport aux attentes. La vérification du nombre min/max de colonnes dans chaque ensemble de données vaut toujours la peine. Utilisez un outil de haut niveau comme pyParsing pour minimiser le temps de réparation. citation/virgule? codage? –

0

Si votre objectif est de convertir les données en XML (ou JSON ou YAML), regardez this example pour une syntaxe Gelatin qui produit la sortie suivante:

<xml> 
    <line> 
    <column>data1</column> 
    <column>data2 </column> 
    <column>data3'''</column> 
    <column>data4""</column> 
    <column/> 
    <column/> 
    <column>data5</column> 
    <column/> 
    </line> 
</xml> 

Notez que Gélatine dispose également d'une API Python :

from Gelatin.util import compile, generate_to_file 
syntax = compile('syntax.gel') 
generate_to_file(syntax, 'input.csv', 'output.xml', 'xml') 
Questions connexes