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]]()
}
}