【发布时间】:2011-07-01 02:03:40
【问题描述】:
我正在尝试使用 Scala 解析器组合器编写 CSV 解析器。语法基于RFC4180。我想出了以下代码。它几乎可以工作,但我无法让它正确分离不同的记录。我错过了什么?
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))
更新:问题已解决
默认的 RegexParsers 使用正则表达式 [\s]+ 忽略空格,包括空格、制表符、回车和换行符。上面的解析器无法分离记录的问题就是因为这个。我们需要禁用 skipWhitespace 模式。将空格定义替换为 [ \t]} 并不能解决问题,因为它将忽略字段中的所有空格(因此 CSV 中的“foo bar”变为“foobar”),这是不希望的。因此解析器的更新源是
import scala.util.parsing.combinator._
// A CSV parser based on RFC4180
// https://www.rfc-editor.org/rfc/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)
}
}
【问题讨论】:
-
为什么常量是用def而不是val定义的?有什么好处吗?
-
Check this out。 tl;dr def 使用更少的内存,val 更快。
-
对于编译时常量几乎没有什么区别——“val”将在构造函数中使用该常量初始化一个字段,然后创建一个返回其值的方法,而“def”将简单地返回常量 - 对于编译时常量,这实际上是免费的。
-
@rancidfishbreath 它是一个对象,所以只有 1 个实例,所以如果它节省任何内存,这将是微不足道的
标签: parsing scala csv parser-combinators