tener separado punto puede por notas ejemplo delimitar delimitado crear convertir con compatibles como comas coma caracteristicas bloc archivo parsing scala csv parser-combinators

parsing - separado - delimitar por comas en excel



Use el combinador de anĂ¡lisis Scala para analizar archivos CSV (3)

Con la biblioteca de Scala Parser Combinators fuera de la biblioteca estándar de Scala a partir de 2.11, no hay una buena razón para no usar la biblioteca Parboiled2, mucho más eficaz. Aquí hay una versión del analizador CSV en DSL de Parboiled2:

/* based on comments in https://github.com/sirthias/parboiled2/issues/61 */ import org.parboiled2._ case class Parboiled2CsvParser(input: ParserInput, delimeter: String) extends Parser { def DQUOTE = ''"'' def DELIMITER_TOKEN = rule(capture(delimeter)) def DQUOTE2 = rule("/"/"" ~ push("/"")) def CRLF = rule(capture("/r/n" | "/n")) def NON_CAPTURING_CRLF = rule("/r/n" | "/n") val delims = s"$delimeter/r/n" + DQUOTE def TXT = rule(capture(!anyOf(delims) ~ ANY)) val WHITESPACE = CharPredicate(" /t") def SPACES: Rule0 = rule(oneOrMore(WHITESPACE)) def escaped = rule(optional(SPACES) ~ DQUOTE ~ (zeroOrMore(DELIMITER_TOKEN | TXT | CRLF | DQUOTE2) ~ DQUOTE ~ optional(SPACES)) ~> (_.mkString(""))) def nonEscaped = rule(zeroOrMore(TXT | capture(DQUOTE)) ~> (_.mkString(""))) def field = rule(escaped | nonEscaped) def row: Rule1[Seq[String]] = rule(oneOrMore(field).separatedBy(delimeter)) def file = rule(zeroOrMore(row).separatedBy(NON_CAPTURING_CRLF)) def parsed() : Try[Seq[Seq[String]]] = file.run() }

Intento escribir un analizador de CSV usando los combinadores de analizador Scala. La gramática se basa en RFC4180 . Se me ocurrió el siguiente código. Casi funciona, pero no puedo conseguir que separe correctamente los diferentes registros. ¿Qué me perdí?

object CSV extends RegexParsers { def COMMA = "," def DQUOTE = "/"" def DQUOTE2 = "/"/"" ^^ { case _ => "/"" } def CR = "/r" def LF = "/n" def CRLF = "/r/n" def TXT = "[^/",/r/n]".r def file: Parser[List[List[String]]] = ((record~((CRLF~>record)*))<~(CRLF?)) ^^ { case r~rs => r::rs } def record: Parser[List[String]] = (field~((COMMA~>field)*)) ^^ { case f~fs => f::fs } def field: Parser[String] = escaped|nonescaped def escaped: Parser[String] = (DQUOTE~>((TXT|COMMA|CR|LF|DQUOTE2)*)<~DQUOTE) ^^ { case ls => ls.mkString("")} def nonescaped: Parser[String] = (TXT*) ^^ { case ls => ls.mkString("") } def parse(s: String) = parseAll(file, s) match { case Success(res, _) => res case _ => List[List[String]]() } } println(CSV.parse(""" "foo", "bar", 123""" + "/r/n" + "hello, world, 456" + "/r/n" + """ spam, 789, egg""")) // Output: List(List(foo, bar, 123hello, world, 456spam, 789, egg)) // Expected: List(List(foo, bar, 123), List(hello, world, 456), List(spam, 789, egg))

Actualización: problema resuelto

Los RegexParsers predeterminados ignoran espacios en blanco que incluyen espacio, tabulación, retorno de carro y saltos de línea usando la expresión regular [/s]+ . El problema del analizador anterior que no puede separar los registros se debe a esto. Necesitamos deshabilitar el modo skipWhitespace. Reemplazar la definición de whiteSpace a simplemente [ /t]} no resuelve el problema porque ignorará todos los espacios dentro de los campos (por lo tanto, "foo bar" en el CSV se convierte en "foobar"), lo cual no es deseado. La fuente actualizada del analizador es por lo tanto

import scala.util.parsing.combinator._ // A CSV parser based on RFC4180 // http://tools.ietf.org/html/rfc4180 object CSV extends RegexParsers { override val skipWhitespace = false // meaningful spaces in CSV def COMMA = "," def DQUOTE = "/"" def DQUOTE2 = "/"/"" ^^ { case _ => "/"" } // combine 2 dquotes into 1 def CRLF = "/r/n" | "/n" def TXT = "[^/",/r/n]".r def SPACES = "[ /t]+".r def file: Parser[List[List[String]]] = repsep(record, CRLF) <~ (CRLF?) def record: Parser[List[String]] = repsep(field, COMMA) def field: Parser[String] = escaped|nonescaped def escaped: Parser[String] = { ((SPACES?)~>DQUOTE~>((TXT|COMMA|CRLF|DQUOTE2)*)<~DQUOTE<~(SPACES?)) ^^ { case ls => ls.mkString("") } } def nonescaped: Parser[String] = (TXT*) ^^ { case ls => ls.mkString("") } def parse(s: String) = parseAll(file, s) match { case Success(res, _) => res case e => throw new Exception(e.toString) } }


El espacio en blanco predeterminado para los analizadores de RegexParsers es /s+ , que incluye nuevas líneas. Por lo tanto, CR , LF y CRLF nunca tienen la posibilidad de procesarse, ya que el analizador automático los omite.


Lo que te perdiste es el espacio en blanco. Lancé un par de mejoras extra.

import scala.util.parsing.combinator._ object CSV extends RegexParsers { override protected val whiteSpace = """[ /t]""".r def COMMA = "," def DQUOTE = "/"" def DQUOTE2 = "/"/"" ^^ { case _ => "/"" } def CR = "/r" def LF = "/n" def CRLF = "/r/n" def TXT = "[^/",/r/n]".r def file: Parser[List[List[String]]] = repsep(record, CRLF) <~ opt(CRLF) def record: Parser[List[String]] = rep1sep(field, COMMA) def field: Parser[String] = (escaped|nonescaped) def escaped: Parser[String] = (DQUOTE~>((TXT|COMMA|CR|LF|DQUOTE2)*)<~DQUOTE) ^^ { case ls => ls.mkString("")} def nonescaped: Parser[String] = (TXT*) ^^ { case ls => ls.mkString("") } def parse(s: String) = parseAll(file, s) match { case Success(res, _) => res case _ => List[List[String]]() } }