Regex para dividir um CSV

Eu sei que isso (ou similar) tem sido feito muitas vezes, mas tendo experimentado inúmeras possibilidades, não consegui encontrar um regex que funciona 100%.

Eu tenho um arquivo CSV e estou tentando dividi-lo em uma matriz, mas encontrando dois problemas: vírgulas e elementos vazios citados.

O CSV se parece com:

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

O regex que eu tentei usar é:

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

O único problema é que no meu array de saída o 5º elemento sai como 123987564 e não como uma string vazia.

Descrição

Em vez de usar uma divisão, acho que seria mais fácil simplesmente executar uma correspondência e processar todas as correspondências encontradas.

Esta expressão irá:

  • divida seu texto de amostra nos limites de vírgula
  • processará valores vazios
  • irá ignorar aspas duplas, desde que aspas duplas não sejam aninhadas
  • apara a vírgula delimitadora do valor retornado
  • apara as cotações circundantes do valor retornado

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

insira a descrição da imagem aqui

Exemplo

Texto de amostra

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

Exemplo ASP usando a expressão não-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

Corresponde usando a expressão não-java

O grupo 0 obtém toda a substring que inclui a vírgula
O grupo 1 recebe a cotação se for usado
Grupo 2 obtém o valor sem include a vírgula

 [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 

Eu criei isso há alguns meses para um projeto.

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

Visualização de expressão regular

Ele funciona em C # e o Debuggex ficou feliz quando selecionei o Python e o PCRE. Javascript não reconhece esta forma de Proceeded By ? < = ....

Para seus valores, ele criará correspondências em

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

Observe que nada entre aspas não tem uma vírgula principal, mas a tentativa de corresponder a uma vírgula principal foi necessária para o caso de uso de valor vazio. Uma vez feito, ajuste os valores conforme necessário.

Eu uso o RegexHero.Net para testar o meu Regex.

Eu também precisava dessa resposta, mas achei as respostas, embora informativas, um pouco difíceis de serem seguidas e replicadas para outros idiomas. Aqui está a expressão mais simples que eu criei para uma única coluna fora da linha CSV. Eu não estou me separando. Estou criando um regex para corresponder a uma coluna do CSV, portanto não estou dividindo a linha:

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

Isso corresponde a uma única coluna da linha CSV. A primeira parte "([^"]*)" da expressão é para coincidir com uma input entre aspas, a segunda parte [^,]* é para coincidir com uma input não citada. Então, seja seguido por um , ou fim da linha $ .

E o debuggex acompanhante para testar a expressão.

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

Estou atrasado para a festa, mas o seguinte é a expressão regular que uso:

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

Este padrão tem três grupos de captura:

  1. Conteúdo de uma célula citada
  2. Conteúdo de um celular sem cotação
  3. Uma nova linha

Esse padrão manipula todos os itens a seguir:

  • Conteúdo normal das células sem qualquer característica especial: um, dois, três
  • Célula contendo uma aspa dupla (“é escapado para” “): sem aspas,” a “” aspas “” coisa “, final
  • A célula contém um caractere de nova linha: um, dois \ n três, quatro
  • Conteúdos de células normais que têm uma citação interna: um, dois “três, quatro
  • A célula contém aspas seguidas de vírgula: uma, “dois” “três” “, quatro”, cinco

Veja este padrão em uso.

Se você estiver usando um sabor mais capaz de regex com grupos nomeados e lookbehinds, prefiro o seguinte:

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

Veja este padrão em uso.

A vantagem de usar o JScript para páginas ASP clássicas é que você pode usar uma das muitas bibliotecas que foram escritas para JavaScript.

Como este aqui: https://github.com/gkindel/CSV-JS . Faça o download, inclua em sua página ASP, analise o CSV com ele.

 < %@ language="javascript" %>   

Eu pessoalmente tentei muitas expressões RegEx sem ter encontrado a perfeita que combina com todos os casos.

Eu acho que expressões regulares são difíceis de configurar corretamente para corresponder todos os casos corretamente. Embora poucas pessoas não gostem do namespace (e eu fiz parte delas), proponho algo que faz parte do framework .Net e forneço os resultados adequados em todos os casos em todos os casos (gerenciando muito bem todos os casos de aspas duplas):

Microsoft.VisualBasic.FileIO.TextFieldParser

Encontrei aqui: StackOverflow

Exemplo 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 isso possa ajudar.

Trabalhei nisso um pouco e encontrei esta solução:

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

Experimente aqui!

Essa solução lida com dados “bons” 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" 

e coisas mais feias como

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

Aqui está uma explicação de como isso 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 ) 

Em Java esse padrão ",(?=([^\"]*\"[^\"]*\")*(?![^\"]*\"))" Quase funciona para mim:

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

saída:

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

Desvantagem: não funciona, quando a coluna tem um número ímpar de citações 🙁

Ainda outra resposta com alguns resources extras, como suporte para valores entre aspas que contenham aspas com escape e caracteres CR / LF (valores únicos que abrangem várias linhas).

OBSERVAÇÃO: Embora a solução abaixo provavelmente possa ser adaptada para outros mecanismos de expressão regular, usá-la como está exigirá que seu mecanismo de expressão regular trate vários grupos de captura nomeados usando o mesmo nome de um único grupo de captura. (.NET faz isso por padrão)


Quando várias linhas / registros de um arquivo / stream CSV (correspondendo ao padrão RFC 4180 ) são passados ​​para a expressão regular abaixo, ele retornará uma correspondência para cada linha / registro não vazio. Cada correspondência conterá um grupo de captura denominado Value que contém os valores capturados nessa linha / registro (e potencialmente um grupo de captura OpenValue se houver uma cotação aberta no final da linha / registro) .

Aqui está o padrão comentado (teste no 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 

Aqui está o padrão bruto sem todos os comentários ou espaços em branco.

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

Aqui está uma visualização do Debuggex.com (grupos de captura nomeados para clareza): Visualização Debuggex.com

Exemplos sobre como usar o padrão regex podem ser encontrados na minha resposta a uma pergunta semelhante aqui , ou no c # pad aqui , ou aqui .

Eu estou usando este, ele funciona com o separador de coma e aspas duplas. Normalmente isso deve resolver o seu problema:

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

Aaa e outra resposta aqui. 🙂 Desde que eu não consegui fazer os outros trabalharem.

Minha solução manipula aspas com escape (ocorrências duplas) e não inclui delimitadores na correspondência.

Note que eu tenho correspondido contra ' vez de " pois esse foi o meu cenário, mas simplesmente substituí-los no padrão para o mesmo efeito.

Aqui vai (lembre-se de usar a flag “whitespace ignore” /x se você usar a versão comentada abaixo):

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

Versão única linha:

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

Visualização de expressão regular (se funcionar, o debux tem problemas agora parece - mais siga o próximo link)

Debuggex Demo

Exemplo de regex101

Eu tive uma necessidade semelhante de dividir valores CSV de instruções de inserção SQL.

No meu caso, eu poderia supor que as seqüências de caracteres foram agrupadas em citações simples e números não foram.

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

Por algum motivo provavelmente óbvio, esse regex produz alguns resultados em branco. Eu poderia ignorá-los, já que quaisquer valores vazios em meus dados eram representados como ...,'',... e não ...,,...

Se eu tentar o regex postado por @chubbsondubs em http://regex101.com usando o sinalizador ‘g’, existem correspondências, que contêm apenas ‘,’ ou uma string vazia. Com esta regex:
(?:"([^"]*)"|([^,]*))(?:[,])
Eu posso combinar as partes do CSV (incluindo as partes citadas). (A linha deve ser terminada com um ‘,’ caso contrário, a última parte não é reconhecida.)
https://regex101.com/r/dF9kQ8/4
Se o CSV se parece com:
"",huhu,"hel lo",world,
existem 4 jogos:

‘huhu’
‘Olá’
‘mundo’

Se você sabe que não terá um campo vazio (,,) então esta expressão funciona bem:

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

Como no exemplo a seguir …

 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 

No entanto, se você antecipar um campo vazio e seu texto for relativamente pequeno, considere a possibilidade de replace os campos vazios por um espaço antes da análise para garantir que eles sejam capturados. Por exemplo…

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

E se você precisar manter a integridade dos campos, poderá restaurar as vírgulas e testar espaços vazios dentro do loop. Este pode não ser o método mais eficiente, mas faz o trabalho.

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

Este regex trabalha com aspas simples e duplas e também para uma citação dentro de outro!

Este corresponde a tudo que eu preciso em c #:

 (?< =(^|,)(?"?))([^"]|(""))*?(?=\(?=,|$)) 
  • tiras de citações
  • deixa novas linhas
  • permite aspas duplas na string citada
  • deixa vírgulas na string citada

A expressão regular correta para corresponder a um único valor entre aspas e aspas simples com escape [dobrado] é:

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