Expresión regular de Python para leer filas similares a CSV

Quiero analizar las filas de datos de tipo CSV entrantes. Los valores se separan con comas (y podría haber espacios en blanco iniciales y finales alrededor de comas), y se pueden citar con ‘o con “. Por ejemplo, esta es una fila válida:

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

pero este está mal formado:

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

– las comillas solo pueden ser precedidas o arrastradas por espacios.

Estas filas con formato incorrecto deben reconocerse; lo mejor sería marcar de alguna manera el valor con formato incorrecto dentro de la fila, pero si las expresiones regulares no coinciden con toda la fila, entonces también es aceptable.

Estoy tratando de escribir expresiones regulares capaces de analizar esto, usando cualquiera de las coincidencias () de findall (), pero todas las expresiones regulares con las que vengo tienen algunos problemas con los casos de borde.

Entonces, ¿quizás alguien con experiencia en analizar algo similar podría ayudarme en esto? (O tal vez esto es demasiado complejo para expresiones regulares y debería escribir una función)

EDIT1:

csv módulo csv no es de mucha utilidad aquí:

  >>> 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 menos que esto pueda ser sintonizado?

EDIT2: algunas ediciones de idioma, espero que ahora sea más válido en inglés

EDIT3: Gracias por todas las respuestas, ahora estoy bastante seguro de que la expresión regular no es tan buena idea ya que (1) cubrir todos los casos de borde puede ser complicado (2) la salida del escritor no es regular. Escribiendo eso, decidí verificar el contenido de los párrafos mencionados y usarlo, o escribir un analizador personalizado tipo FSM.

Aunque es probable que sea posible con alguna combinación de preprocesamiento, uso del módulo csv , postprocesamiento y uso de expresiones regulares, sus requisitos establecidos no encajan bien con el diseño del módulo csv , ni posiblemente con expresiones regulares ( dependiendo de la complejidad de las comillas anidadas que pueda tener que manejar).

En casos complejos de análisis, la reproducción es siempre un buen paquete para recurrir. Si esta no es una situación excepcional, es probable que produzca el resultado más directo y fácil de mantener, al costo de un posible esfuerzo adicional por adelantado. Sin embargo, considera que la inversión se reembolsará rápidamente, ya que te ahorras el esfuerzo adicional de depurar las soluciones de expresiones regulares para manejar los casos de esquina …

Es probable que pueda encontrar ejemplos de análisis de CSV basados ​​en pyparsing fácilmente, con esta pregunta tal vez sea suficiente para comenzar.

Si bien el módulo csv es la respuesta correcta aquí, una expresión regular que podría hacer esto es bastante factible:

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

EDITAR: para validar líneas, puede reutilizar la expresión regular anterior con pequeñas adiciones:

 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. 

Python tiene un módulo de biblioteca estándar para leer archivos csv:

 import csv reader = csv.reader(open('file.csv')) for line in reader: print line 

Para su ejemplo de entrada esto imprime

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

EDITAR:

debe agregar skipinitalspace = True para permitir espacios antes de comillas dobles para los ejemplos adicionales que proporcionó. No estoy seguro acerca de las comillas simples todavía.

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

No es posible darle una respuesta, porque no ha especificado completamente el protocolo que está utilizando el escritor.

Evidentemente contiene reglas como:

Si un campo contiene comas o comillas simples, cítelo entre comillas dobles.
De lo contrario, si el campo contiene comillas dobles, cítelo entre comillas simples.
Nota: el resultado sigue siendo válido si intercambias doble y sencillo en las 2 cláusulas anteriores.
Si no, no lo cites.
El campo resultante puede tener espacios (u otros espacios en blanco?) Precedidos o anexados.
Los campos así aumentados se ensamblan en una fila, separados por comas y terminados por la nueva línea de la plataforma (LF o CRLF).

Lo que no se menciona es lo que hace el escritor en estos casos:
(0) el campo contiene AMBAS comillas simples y comillas dobles
(1) el campo contiene espacios en blanco que no son de nueva línea
(2) el campo contiene espacios en blanco que no son de nueva línea
(3) el campo contiene nuevas líneas.
Cuando el escritor ignore cualquiera de estos casos, especifique qué resultados desea.

También menciona “las comillas solo pueden ser precedidas o arrastradas por espacios”; seguramente quiere decir que también se permiten comas; de lo contrario, su ejemplo 'data4""',,,data5, falla en la primera coma.

¿Cómo se codifican tus datos?

Probablemente esto suene demasiado simple, pero en realidad, por lo que parece, está buscando una cadena que contenga [a-zA-Z0-9] [“‘] + [a-zA-Z0-9], quiero decir sin en pruebas de profundidad contra los datos realmente lo que estás buscando es una cita o una cita doble (o cualquier combinación) entre letras (también puedes agregar números allí).

Según lo que estaba preguntando, realmente no importa que sea un CSV, sí importa que tenga datos que no se ajusten. Lo cual creo que solo es hacer una búsqueda de una letra, luego cualquier combinación de uno o más “y” y otra letra.

¿Ahora está buscando obtener una “cantidad” o simplemente una copia impresa de la línea que lo contiene para que sepa cuáles volver y arreglar?

Lo siento, no sé pyex de expresiones regulares pero en perl esto se vería así:

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

Simplemente convierta eso para cuando mire una línea.

Lo siento si entendi mal la pregunta

¡Espero que ayude!

Si su objective es convertir los datos a XML (o JSON o YAML), busque en este ejemplo una syntax de gelatina que produzca el siguiente resultado:

   data1 data2  data3''' data4""   data5    

Tenga en cuenta que la gelatina también tiene una API de Python:

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