2010-02-06 8 views
12

Desidero analizzare le righe di dati in arrivo simili al CSV. . I valori sono separati da virgole (e ci potrebbero essere iniziali e finali spazi bianchi intorno virgole), e possono essere citati sia con 'o con" Per esempio - questa è una riga valida:Espressione regolare di Python per la lettura di righe simili a CSV

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

ma questo non è corretto :

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

- virgolette possono essere precedute solo o trainati da spazi

Tali file malformati dovrebbero essere riconosciuti - meglio sarebbe quello di segnare in qualche modo il valore malformato all'interno di fila, ma se regex non corrisponde. anche l'intera riga è accettabile.

Sto provando a scrivere regex in grado di analizzarlo, utilizzando la corrispondenza() di findall(), ma ogni singola regex con cui sto arrivando ha alcuni problemi con i casi limite.

Quindi, forse qualcuno con esperienza nell'analisi di qualcosa di simile potrebbe aiutarmi in questo? (O forse questo è troppo complessa per regex e dovrei scrivere una funzione)

Edit1:

csv modulo non è molto d'uso qui:

>>> 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'", '']] 

- a meno che questo può essere sintonizzato?

EDIT2: Una lingua poche modifiche - Spero che sia più valida inglese ora

Edit3: Grazie per tutte le risposte, sono ormai abbastanza sicuro che l'espressione regolare non è che una buona idea anche qui (1) che copre tutti i casi limite possono essere complicati (2) l'output del writer non è regolare. Scrivendo ciò, ho deciso di controllare il pyparsing menzionato e usarlo o scrivere parser tipo FSM personalizzato.

+2

Il modulo 'csv' * non può * essere" sintonizzato "per gestire le situazioni che descrivi. Anche il tuo primo esempio, con due diversi stili di virgolette, non può essere gestito, per quanto posso dire, nonostante tutte le persone che non possono essere disturbate a leggere attentamente la tua domanda. –

+0

@Peter Hansen: sei corretto; il formato sopra descritto non può essere gestito dal modulo csv - non gestisce caratteri di virgolette alternativi. –

+0

In alternativa, chiedere a chi prepara il file regex, utilizzare Tab Separated Values ​​(TSV), in modo da poterlo leggere con csv.reader. – mootmoot

risposta

6

Sebbene sarebbe probabilmente possibile con una combinazione di pre-elaborazione, uso diModulo, post-elaborazione e uso di espressioni regolari, i requisiti dichiarati non si adattano bene al design del modulo csv, né eventualmente alle espressioni regolari (a seconda della complessità delle virgolette nidificate che potrebbe essere necessario gestire).

Nei casi di analisi complessi, pyparsing è sempre un buon pacchetto da utilizzare. Se questa non è una situazione una tantum, probabilmente produrrà il risultato più semplice e manutenibile, al costo di possibilmente un piccolo sforzo in più. Considerate che l'investimento deve essere ripagato rapidamente, tuttavia, risparmiate lo sforzo extra di debugging delle soluzioni regex per gestire casi angolari ...

Probabilmente si trovano facilmente esempi di parsing CSV basati su pyparsing, con this question forse abbastanza per iniziare.

+0

+1 per il collegamento alla domanda correlata, oltre alla bella descrizione di quando ritieni che raggiungere il pyparsing sia appropriato. Diversamente dall'altra domanda, che aveva un formato molto rigido, questa domanda sarebbe più probabile che usasse qualcosa come "delimitedList (Word (alphanums) | quotedString)", dal momento che un delimitedList cerca i delimitatori di virgola per impostazione predefinita. – PaulMcG

4

Python ha un modulo standard per leggere i file CSV:

import csv 

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

for line in reader: 
    print line 

per il vostro input esempio, questo stampa

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

EDIT:

è necessario aggiungere skipinitalspace = True per consentire spazi prima delle doppie virgolette per gli esempi extra che hai fornito. Non sono ancora sicuro delle virgolette singole.

>>> 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

Sì, in questo caso sarebbe molto più pulito rispetto alla regex e si noti che il modulo è abbastanza flessibile: è possibile impostare i propri delimitatori, virgolette caratteri e terminatori di riga in modo abbastanza semplice. Vedi: http://docs.python.org/library/csv.html. –

+0

@Max S, ho trascurato qualcosa nei documenti, oppure c'è un modo per impostarlo per gestire due caratteri di citazione diversi come richieste OP? –

+0

@Peter: No, questa è l'unica caratteristica mancante di cui l'asker ha bisogno, motivo per cui ho pubblicato la mia soluzione di regex più generale. Tuttavia, è improbabile in un dato reale e 'csv' è più pulito. –

8

Mentre il modulo csv è la risposta giusta qui, una regex che potrebbe fare questo è abbastanza fattibile:

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: Per convalidare le linee, è possibile riutilizzare l'espressione regolare di cui sopra con piccolo aggiunte:

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

Sono abbastanza sicuro che è necessario passare entrambe le occorrenze di '[^"] | \\. '(E farle così:' \\. | [^ "]') o cambiarli in '[^" \\] | \\. '. Perché' [^ "]' corrisponderà a una barra rovesciata, una barra rovesciata di escape o una virgoletta doppia non saranno * correttamente analizzati *. –

+0

Giusto. Mi dimentico sempre di inserire la barra rovesciata nella classe personaggio. Semplicemente cambiandoli non funzionerebbe per la convalida anche se il motore può semplicemente tornare indietro sul backslash. –

+0

Vero, aggiungerli nella classe di caratteri negata è l'opzione migliore. Ho sempre notato questo perché sono scivolato così tante volte!:) –

1

Questo probabilmente sembra troppo semplice, ma in realtà dall'aspetto delle cose che stai cercando una stringa che contiene [a-zA-Z0-9] ["'] + [a-zA-Z0-9], Voglio dire senza test approfonditi rispetto ai dati in realtà quello che stai cercando è una citazione o doppia citazione (o qualsiasi combinazione) tra lettere (potresti anche aggiungere dei numeri)

Sulla base di ciò che stavi chiedendo, non importa che sia un CSV, è importante che tu abbia dati che non sono conformi, il che credo sia solo una ricerca di una lettera, quindi qualsiasi combinazione di uno o più "o" e un'altra lettera.

Ora stai cercando di ottenere una "quantità" o solo una stampa della riga che lo contiene in modo da sapere quali tornare e correggere?

Mi dispiace non so regex pitone di ma in perl questo sarebbe simile a questa:

# 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; 

} 

Basta semplicemente convertire che per quando si guarda a una linea.

Mi dispiace se ho frainteso la questione

Spero che aiuta!

2

Non è possibile fornire una risposta, poiché non è stato specificato completamente il protocollo utilizzato dallo scrittore.

E 'evidentemente contiene regole come:

Se un campo contiene virgole o virgolette singole, citando con doppi apici.
Altrimenti se il campo contiene doppie virgolette, citarlo con virgolette singole.
Nota: il risultato è ancora valido se si scambia il doppio e il singolo nelle precedenti 2 clausole.
Altrimenti non lo cito.
Il campo risultante può contenere spazi (o altri spazi bianchi?) Anteposto o aggiunto.
I campi così aumentati sono raggruppati in una riga, separati da virgole e terminati dalla nuova riga della piattaforma (LF o CRLF).

Ciò che non è menzionato è quello che lo scrittore fa in questi casi:
(0) campo contiene sia apici singoli e doppi apici
(1) campo contiene leader non-ritorno a capo spazi
(2) campo contiene trailing spazio vuoto non nuovo
(3) campo contiene eventuali nuove righe.
Dove lo scrittore ignora qualcuno di questi casi, si prega di specificare quali risultati si desidera.

Si cita anche "le virgolette possono essere anteposte o trascinate da spazi" - sicuramente si intendono le virgole sono consentite anche, altrimenti l'esempio 'data4""',,,data5, non riesce sulla prima virgola.

Come vengono codificati i dati?

+0

Questo è uno di quei casi in cui devi "essere liberale in ciò che accetti" - non so come funziona lo scrittore (è un servizio web closed source, probabilmente soggetto a modifiche), ho solo alcuni esempi di dati e alcuni linee guida che dicono più o meno "puoi aspettarti quasi tutto". –

+2

"essere liberale" ecc si applica quando si ha uno standard e si è determinato che gli scrittori si allontanano leggermente in un modo che può essere tollerato da un lettore senza effetti negativi. Non hai esperienza standard né esperienza con gli scrittori. Sì, puoi aspettarti quasi tutto, ma non hai mostrato alcuna prova di agire in base a tale aspettativa. Ti suggerisco di essere piuttosto intollerante: imposta un'aspettativa, programma per quello e registra le differenze dalle aspettative. Controllare sempre il numero minimo/massimo di colonne in ogni set di dati. Utilizza uno strumento di alto livello come pyParsing per ridurre al minimo i tempi di correzione. preventivo/virgola? la codifica? –

0

Se il vostro obiettivo è quello di convertire i dati in XML (o JSON o YAML), guarda this example per una sintassi Gelatin che produce il seguente output:

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

noti che gelatina ha anche un'API di Python :

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