language-agnostic parsing csv

language agnostic - Dividir una cadena ignorando las secciones citadas



language-agnostic parsing (12)

Dada una cadena como esta:

a, "cadena, con", varios, "valores, y algunos", citado

¿Qué es un buen algoritmo para dividir esto basado en comas mientras se ignoran las comas dentro de las secciones citadas?

La salida debe ser una matriz:

["a", "cadena, con", "varios", "valores, y algunos", "citado"]


¿Qué pasa si aparece un número impar de citas en la cadena original?

Esto se parece misteriosamente al análisis CSV, que tiene algunas peculiaridades para el manejo de campos cotizados. El campo solo se escapa si el campo está delimitado con comillas dobles, entonces:

field1, "field2, field3", field4, "field5, field6" field7

se convierte

campo1

campo2, campo3

campo4

"campo5

campo6 "campo7

Observe si no comienza y termina con una cita, entonces no es un campo entre comillas y las comillas dobles simplemente se tratan como comillas dobles.

Insertablemente, mi código al que alguien se vinculó no lo maneja correctamente, si no recuerdo mal.


Aquí hay uno en pseudocódigo (también conocido como Python) en un pase :-P

def parsecsv(instr): i = 0 j = 0 outstrs = [] # i is fixed until a match occurs, then it advances # up to j. j inches forward each time through: while i < len(instr): if j < len(instr) and instr[j] == ''"'': # skip the opening quote... j += 1 # then iterate until we find a closing quote. while instr[j] != ''"'': j += 1 if j == len(instr): raise Exception("Unmatched double quote at end of input.") if j == len(instr) or instr[j] == '','': s = instr[i:j] # get the substring we''ve found s = s.strip() # remove extra whitespace # remove surrounding quotes if they''re there if len(s) > 2 and s[0] == ''"'' and s[-1] == ''"'': s = s[1:-1] # add it to the result outstrs.append(s) # skip over the comma, move i up (to where # j will be at the end of the iteration) i = j+1 j = j+1 return outstrs def testcase(instr, expected): outstr = parsecsv(instr) print outstr assert expected == outstr # Doesn''t handle things like ''1, 2, "a, b, c" d, 2'' or # escaped quotes, but those can be added pretty easily. testcase(''a, b, "1, 2, 3", c'', [''a'', ''b'', ''1, 2, 3'', ''c'']) testcase(''a,b,"1, 2, 3" , c'', [''a'', ''b'', ''1, 2, 3'', ''c'']) # odd number of quotes gives a "unmatched quote" exception #testcase(''a,b,"1, 2, 3" , "c'', [''a'', ''b'', ''1, 2, 3'', ''c''])


Aquí hay un algoritmo simple:

  1. Determine si la cadena comienza con un carácter ''"''
  2. Divida la cadena en una matriz delimitada por el carácter ''"'' .
  3. Marque las comillas con un marcador de posición #COMMA#
    • Si la entrada comienza con un ''"'' , marque los elementos en la matriz donde el índice% 2 == 0
    • De lo contrario, marque esos elementos en la matriz donde el índice% 2 == 1
  4. Concatenar los elementos en la matriz para formar una cadena de entrada modificada.
  5. Divida la cadena en una matriz delimitada por el carácter '','' .
  6. Reemplace todas las instancias en la matriz de marcadores de posición #COMMA# con el carácter '','' .
  7. La matriz es tu salida.

Aquí está la implementación de Python:
(fijado para manejar ''"a, b", c, "d, e, f, h", "i, j, k"'')

def parse_input(input): quote_mod = int(not input.startswith(''"'')) input = input.split(''"'') for item in input: if item == '''': input.remove(item) for i in range(len(input)): if i % 2 == quoted_mod: input[i] = input[i].replace(",", "#COMMA#") input = "".join(input).split(",") for item in input: if item == '''': input.remove(item) for i in range(len(input)): input[i] = input[i].replace("#COMMA#", ",") return input # parse_input(''a,"string, with",various,"values, and some",quoted'') # -> [''a,string'', '' with,various,values'', '' and some,quoted''] # parse_input(''"a,b",c,"d,e,f,h","i,j,k"'') # -> [''a,b'', ''c'', ''d,e,f,h'', ''i,j,k'']


Este es un análisis de estilo CSV estándar. Mucha gente intenta hacer esto con expresiones regulares. Puede obtener aproximadamente el 90% con expresiones regulares, pero realmente necesita un analizador de CSV real para hacerlo correctamente. ¡Hace unos meses encontré un analizador rápido y excelente de C # CSV en CodeProject que recomiendo!


Lo uso para analizar cadenas, no estoy seguro si ayuda aquí; pero con algunas modificaciones menores tal vez?

function getstringbetween($string, $start, $end){ $string = " ".$string; $ini = strpos($string,$start); if ($ini == 0) return ""; $ini += strlen($start); $len = strpos($string,$end,$ini) - $ini; return substr($string,$ini,$len); } $fullstring = "this is my [tag]dog[/tag]"; $parsed = getstringbetween($fullstring, "[tag]", "[/tag]"); echo $parsed; // (result = dog)

/ mp


Parece que tienes algunas buenas respuestas aquí.

Para aquellos de ustedes que buscan manejar su propio análisis de archivo CSV, presten atención al consejo de los expertos y no despliegue su propio analizador CSV .

Su primer pensamiento es: "Necesito manejar las comas dentro de las comillas".

Su próximo pensamiento será: "Oh, mierda, necesito manejar las comillas dentro de las comillas. Las comillas saltadas. Las comillas dobles. Las comillas simples ..."

Es un camino a la locura. No escribas el tuyo Encuentre una biblioteca con una extensa cobertura de prueba unitaria que llegue a todas las partes difíciles y haya pasado por un infierno para usted. Para .NET, use la biblioteca gratuita FileHelpers .


Simplemente no pude resistirme a ver si podía hacerlo funcionar en un one-liner de Python:

arr = [i.replace("|", ",") for i in re.sub(''"([^"]*)/,([^"]*)"'',"/g<1>|/g<2>", str_to_test).split(",")]

Devuelve [''a'', ''string, with'', ''various'', ''values, y algunos'', ''cotizados'']

Funciona reemplazando primero las comillas '','' dentro de otro separador (|), dividiendo la cadena en '','' y reemplazando el | separador de nuevo.


Por supuesto, usar un analizador de CSV es mejor, pero solo por diversión podrías:

Loop on the string letter by letter. If current_letter == quote : toggle inside_quote variable. Else if (current_letter ==comma and not inside_quote) : push current_word into array and clear current_word. Else append the current_letter to current_word When the loop is done push the current_word into array


Pitón:

import csv reader = csv.reader(open("some.csv")) for row in reader: print row


Aquí hay una implementación simple de Python basada en el pseudocódigo de Pat:

def splitIgnoringSingleQuote(string, split_char, remove_quotes=False): string_split = [] current_word = "" inside_quote = False for letter in string: if letter == "''": if not remove_quotes: current_word += letter if inside_quote: inside_quote = False else: inside_quote = True elif letter == split_char and not inside_quote: string_split.append(current_word) current_word = "" else: current_word += letter string_split.append(current_word) return string_split


Si mi lenguaje de elección no ofreciera una manera de hacer esto sin pensar, inicialmente consideraría dos opciones como la salida más fácil:

  1. Realice un análisis previo y reemplace las comas dentro de la cadena con otro carácter de control y luego divídelas, seguido de un análisis posterior en la matriz para reemplazar el carácter de control utilizado anteriormente con las comas.

  2. Alternativamente, divídelas en las comas y luego analiza la matriz resultante en otra matriz, verificando las comillas principales en cada entrada de la matriz y concatenando las entradas hasta que llegue a una comilla de terminación.

Sin embargo, estos son hacks, y si se trata de un ejercicio mental puro, entonces sospecho que no serán útiles. Si esto es un problema del mundo real, sería útil conocer el idioma para poder ofrecer algún consejo específico.


El autor aquí soltó un bloque de código C # que maneja el escenario con el que estás teniendo un problema:

Importaciones de archivos CSV en .Net

No debería ser demasiado difícil de traducir.