El analizador CSV utilizado en el complemento jquery-csv
Es un analizador de gramática básico de Chomsky Tipo III .
Un tokenizer regex se utiliza para evaluar los datos en una base de char-by-char. Cuando se encuentra un control char, el código se pasa a una instrucción switch para una evaluación adicional basada en el estado inicial. Los caracteres sin control se agrupan y copian en masa para reducir el número de operaciones de copia de cadena necesarias.
El tokenizador:
var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;
El primer conjunto de coincidencias son los caracteres de control: delimitador de valor (") separador de valor (,) y separador de entrada (todas las variaciones de nueva línea). La última coincidencia maneja la agrupación de caracteres sin control.
Hay 10 reglas que el analizador debe cumplir:
- Regla # 1 - Una entrada por línea, cada línea termina con una nueva línea
- Regla n. ° 2: se omite la nueva línea final al final del archivo
- Regla n. ° 3: la primera fila contiene datos de encabezado
- Regla n. ° 4: los espacios se consideran datos y las entradas no deben contener una coma final
- Regla # 5 - Las líneas pueden o no estar delimitadas por comillas dobles
- Regla n. ° 6: los campos que contienen saltos de línea, comillas dobles y comas deben ir entre comillas dobles
- Regla n. ° 7: si se utilizan comillas dobles para encerrar los campos, se debe escapar una comilla doble que aparezca dentro de un campo precediéndola con otra comilla doble
- Enmienda n. ° 1: un campo sin comillas puede o puede
- Enmienda # 2 - Un campo citado puede o no
- Enmienda # 3 - El último campo en una entrada puede o no contener un valor nulo
Nota: Las 7 reglas principales se derivan directamente de IETF RFC 4180 . Los últimos 3 se agregaron para cubrir casos extremos introducidos por aplicaciones modernas de hoja de cálculo (ex Excel, hoja de cálculo de Google) que no delimitan (es decir, cotizan) todos los valores de forma predeterminada. Intenté contribuir con los cambios al RFC pero aún no he escuchado una respuesta a mi consulta.
Suficiente con la liquidación, aquí está el diagrama:
Estados:
- estado inicial para una entrada y / o un valor
- se ha encontrado una cita de apertura
- se ha encontrado una segunda cita
- se ha encontrado un valor sin comillas
Transiciones:
- a. comprueba los valores entre comillas (1), los valores sin comillas (3), los valores nulos (0), las entradas nulas (0) y las nuevas entradas (0)
- si. busca una segunda cotización char (2)
- do. comprueba una cita escapada (1), fin de valor (0) y fin de entrada (0)
- re. comprueba el final del valor (0) y el final de la entrada (0)
Nota: en realidad le falta un estado. Debe haber una línea desde 'c' -> 'b' marcada con el estado '1' porque un segundo delimitador escapado significa que el primer delimitador aún está abierto. De hecho, probablemente sería mejor representarlo como otra transición. Crear estos es un arte, no hay una única forma correcta.
Nota: También le falta un estado de salida, pero en los datos válidos el analizador siempre termina en la transición 'a' y ninguno de los estados es posible porque no queda nada para analizar.
La diferencia entre estados y transiciones:
Un estado es finito, lo que significa que solo se puede inferir que significa una cosa.
Una transición representa el flujo entre estados, por lo que puede significar muchas cosas.
Básicamente, la relación estado-> transición es 1 -> * (es decir, uno a muchos). El estado define "qué es" y la transición define "cómo se maneja".
Nota: No se preocupe si la aplicación de estados / transiciones no se siente intuitiva, no es intuitiva. Se necesitó una correspondencia extensa con alguien mucho más inteligente que yo antes de que finalmente pudiera mantener el concepto.
El seudocódigo:
csv = // csv input string
// init all state & data
state = 0
value = ""
entry = []
output = []
endOfValue() {
entry.push(value)
value = ""
}
endOfEntry() {
endOfValue()
output.push(entry)
entry = []
}
tokenizer = /("|,|\n|\r|[^",\r\n]+)/gm
// using the match extension of string.replace. string.exec can also be used in a similar manner
csv.replace(tokenizer, function (match) {
switch(state) {
case 0:
if(opening delimiter)
state = 1
break
if(new-line)
endOfEntry()
state = 0
break
if(un-delimited data)
value += match
state = 3
break
case 1:
if(second delimiter encountered)
state = 2
break
if(non-control char data)
value += match
state = 1
break
case 2:
if(escaped delimiter)
state = 1
break
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
case 3:
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
}
}
Nota: Esta es la esencia, en la práctica hay mucho más que considerar. Por ejemplo, comprobación de errores, valores nulos, una línea en blanco al final (es decir, que es válida), etc.
En este caso, el estado es la condición de las cosas cuando el bloque de coincidencia de expresiones regulares finaliza una iteración. La transición se representa como las declaraciones de caso.
Como humanos, tenemos una tendencia a simplificar las operaciones de bajo nivel en resúmenes de alto nivel, pero trabajar con un FSM es trabajar con operaciones de bajo nivel. Si bien los estados y las transiciones son muy fáciles de trabajar individualmente, es inherentemente difícil visualizar todo de una vez. Me resultó más fácil seguir los caminos individuales de ejecución una y otra vez hasta que pude intuir cómo se desarrollan las transiciones. Es como aprender matemática básica, no podrás evaluar el código desde un nivel superior hasta que los detalles de nivel bajo comiencen a ser automáticos.
Aparte: si observa la implementación real, faltan muchos detalles. Primero, todos los caminos imposibles arrojarán excepciones específicas. Debería ser imposible golpearlos, pero si algo se rompe, desencadenarán excepciones en el corredor de prueba. En segundo lugar, las reglas del analizador de lo que está permitido en una cadena de datos CSV 'legal' son bastante flexibles, por lo que el código necesario para manejar una gran cantidad de casos específicos. Independientemente de ese hecho, este fue el proceso utilizado para burlarse del FSM antes de todas las correcciones de errores, extensiones y ajustes.
Como con la mayoría de los diseños, no es una representación exacta de la implementación, pero describe las partes importantes. En la práctica, en realidad hay 3 funciones de analizador diferentes derivadas de este diseño: un divisor de línea específico de csv, un analizador de una sola línea y un analizador de varias líneas completo. Todos operan de manera similar, difieren en la forma en que manejan los caracteres de nueva línea.