Regex para dividir un CSV

Sé que esto (o algo similar) se ha preguntado muchas veces, pero después de haber probado numerosas posibilidades, no he podido encontrar una expresión regular que funcione al 100%.

Tengo un archivo CSV y estoy tratando de dividirlo en una matriz, pero encuentro dos problemas: comas comilladas y elementos vacíos.

El CSV se ve así:

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

La expresión regular que he tratado de usar es:

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

El único problema es que en mi matriz de salida, el quinto elemento aparece como 123987564 y no como una cadena vacía.

Descripción

En lugar de usar una división, creo que sería más fácil simplemente ejecutar una coincidencia y procesar todas las coincidencias encontradas.

Esta expresión:

  • divide tu texto de muestra en las comas delimita
  • procesará valores vacíos
  • ignorará las comillas dobles, siempre que las comillas dobles no estén anidadas
  • recorta la coma de delimitación del valor devuelto
  • recorta las cotizaciones que rodean el valor devuelto

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

enter image description here

Ejemplo

Texto de ejemplo

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

Ejemplo ASP utilizando la expresión no 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 "
" & results

Coincide usando la expresión no java

El grupo 0 obtiene toda la subcadena que incluye la coma
El grupo 1 recibe la cita si se usa
El grupo 2 obtiene el valor sin incluir la coma

 [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 

Creé esto hace unos meses para un proyecto.

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

Visualización de expresión regular

Funciona en C # y Debuggex estaba contento cuando seleccioné Python y PCRE. Javascript no reconoce esta forma de Proceeded By ? < = ....

Para sus valores, creará coincidencias en

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

Tenga en cuenta que cualquier cosa entre comillas no tiene una coma inicial, pero se intentó unir con una coma inicial para el caso de uso de valor vacío. Una vez hecho, recortar los valores según sea necesario.

Uso RegexHero.Net para probar mi Regex.

Necesitaba esta respuesta también, pero encontré las respuestas, aunque informativas, un poco difíciles de seguir y reproducir para otros idiomas. Aquí está la expresión más simple que se me ocurrió para una sola columna fuera de la línea CSV. No me estoy separando. Estoy construyendo una expresión regular para que coincida con una columna del CSV, así que no estoy dividiendo la línea:

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

Esto coincide con una sola columna de la línea CSV. La primera parte "([^"]*)" de la expresión es para coincidir con una entrada entrecomillada, la segunda parte [^,]* es para hacer coincidir una entrada que no está entre comillas. Luego, seguida por una , o al final de la línea $ .

Y el debuggex que lo acompaña para probar la expresión.

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

Llego tarde a la fiesta, pero la siguiente es la expresión regular que uso:

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

Este patrón tiene tres grupos de captura:

  1. Contenido de una celda citada
  2. Contenido de una celda sin cita
  3. Una nueva línea

Este patrón maneja todo lo siguiente:

  • Contenido de celda normal sin características especiales: uno, 2, tres
  • Celda que contiene una comilla doble (“se ha escapado a” “): sin cita,” a “” citado “,” cosa “, fin
  • Cell contiene un carácter de nueva línea: uno, dos \ ntres, cuatro
  • Contenido de celda normal que tiene una cita interna: uno, dos “tres, cuatro
  • La celda contiene comillas seguidas de comas: una, “dos”, “tres”, cuatro “, cinco

Vea este patrón en uso.

Si tiene un sabor de regex más capaz con grupos nombrados y lookbehinds, prefiero lo siguiente:

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

Vea este patrón en uso.

La ventaja de usar JScript para páginas ASP clásicas es que puede usar una de las muchas, muchas bibliotecas que se han escrito para JavaScript.

Como este: https://github.com/gkindel/CSV-JS . Descárguelo, inclúyalo en su página ASP, analice CSV con él.

 < %@ language="javascript" %>   

Personalmente probé muchas expresiones RegEx sin haber encontrado la perfecta que coincida con todos los casos.

Creo que las expresiones regulares son difíciles de configurar correctamente para que coincidan todos los casos correctamente. Aunque a pocas personas no les gustará el espacio de nombres (y yo formé parte de ellos), propongo algo que forma parte del framework .Net y me da los resultados adecuados todas las veces en todos los casos (principalmente manejando muy bien todos los casos de comillas dobles):

Microsoft.VisualBasic.FileIO.TextFieldParser

Lo encontré aquí: StackOverflow

Ejemplo de uso:

 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) { ... 

Espero que pueda ayudar.

Trabajé en esto por un tiempo y se me ocurrió esta solución:

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

Pruébalo aquí!

Esta solución maneja datos “agradables” de CSV como

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

y cosas más feas como

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

Aquí hay una explicación de cómo funciona :

 (?:,|\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 ) 

En Java este patrón ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))" Casi funciona para mí:

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

salida:

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

Desventaja: no funciona, cuando la columna tiene un número impar de citas 🙁

Otra respuesta más con algunas características adicionales como soporte para valores entrecomillados que contienen comillas escapadas y caracteres CR / LF (valores únicos que abarcan varias líneas).

NOTA: Aunque la solución a continuación probablemente se pueda adaptar para otros motores regex, usarla tal como está requerirá que su motor regex trate a múltiples grupos de captura nombrados usando el mismo nombre que un solo grupo de captura. (.NET hace esto por defecto)


Cuando se pasan múltiples líneas / registros de un archivo / secuencia CSV (que coincida con el estándar RFC 4180 ) a la expresión regular siguiente, se devolverá una coincidencia para cada línea / registro no vacío. Cada coincidencia contendrá un grupo de captura llamado Value que contiene los valores capturados en esa línea / registro (y potencialmente un grupo de captura OpenValue si hubiera una cotización abierta al final de la línea / registro) .

Aquí está el patrón comentado ( pruébelo en 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 "(?(?:[^"]|"")*)"| // Quoted value -or- (?(?!")[^,\r\n]+)| // Unquoted value -or- "(?(?:[^"]|"")*)(?=\r|\n|$)| // Open ended quoted value -or- (?) // 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) (?:(?< =,)(?))? // 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 

Aquí está el patrón en bruto sin todos los comentarios o espacios en blanco.

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

Aquí hay una visualización de Debuggex.com (grupos de captura llamados por claridad): Visualización de Debuggex.com

Los ejemplos sobre cómo usar el patrón de expresiones regulares se pueden encontrar en mi respuesta a una pregunta similar aquí , o en C # pad aquí , o aquí .

Estoy usando este, funciona con separador de coma y escapada doble. Normalmente eso debería resolver su problema:

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

Aaa y otra respuesta aquí. 🙂 Ya que no pude hacer que los demás funcionaran bastante .

Mi solución maneja las comillas escapadas (apariciones dobles) y no incluye delimitadores en la coincidencia.

Tenga en cuenta que he estado haciendo coincidir con ' lugar de " ya que ese era mi escenario, pero simplemente sustitúyalos en el patrón para obtener el mismo efecto.

Aquí va (recuerde utilizar el indicador “ignore el espacio en blanco” /x si usa la versión comentada a continuación):

 # 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 (?=,|$) 

Versión de línea única:

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

Visualización de expresiones regulares (si funciona, debux tiene problemas en este momento, parece - else sigue el siguiente enlace)

Demostración de Debuggex

Ejemplo de regex101

Tenía una necesidad similar de dividir los valores de CSV de las instrucciones de inserción de SQL.

En mi caso, podría suponer que las cadenas estaban envueltas en citas simples y los números no.

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

Por alguna razón probablemente obvia, esta expresión regular produce algunos resultados en blanco. Podría ignorarlos, ya que cualquier valor vacío en mis datos se representaba como ...,'',... y no ...,,...

Si pruebo la expresión regular publicada por @chubbsondubs en http://regex101.com usando la bandera ‘g’, hay coincidencias, que contienen solo ‘,’ o una cadena vacía. Con esta expresión regular:
(?:"([^"]*)"|([^,]*))(?:[,])
puedo hacer coincidir las partes del CSV (incluidas las partes citadas). (La línea debe terminar con un ‘,’ de lo contrario no se reconoce la última parte).
https://regex101.com/r/dF9kQ8/4
Si el CSV se ve así:
"",huhu,"hel lo",world,
hay 4 partidos:

‘huhu’
‘Hola’
‘mundo’

Si sabes que no tendrás un campo vacío (,,) entonces esta expresión funciona bien:

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

Como en el siguiente ejemplo …

 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 

Sin embargo, si prevé un campo vacío y su texto es relativamente pequeño, podría reemplazar los campos vacíos con un espacio antes del análisis para asegurarse de que se capturan. Por ejemplo…

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

Y si necesita mantener la integridad de los campos, puede restaurar las comas y probar espacios vacíos dentro del bucle. Este puede no ser el método más eficiente, pero hace el trabajo.

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

¡Esta expresión regular funciona con comillas simples y dobles y también para una cita dentro de otra!

Este combina todo lo que necesito en c #:

 (?< =(^|,)(?"?))([^"]|(""))*?(?=\(?=,|$)) 
  • citas de tiras
  • deja nuevas líneas
  • permite comillas dobles en la cadena citada
  • deja comas en la cadena citada

La expresión regular correcta para hacer coincidir un único valor entrecomillado con comillas simples escapadas [duplicadas] es:

 '([^n']|(''))+'