带注释的Scala CSV解析器

时间:2015-10-14 09:48:48

标签: regex scala parsing csv

首先:学分。此代码基于此处的解决方案:Use Scala parser combinator to parse CSV files

我要解析的CSV文件可以有注释,以#开头的行。并避免混淆:CSV文件以制表符分隔。有更多的约束条件会使解析器变得更容易,但由于我完全不熟悉Scala,我认为最好保持尽可能接近(工作)原文。

我遇到的问题是我的类型不匹配。显然,评论的正则表达式不会产生列表。我希望Scala将评论解释为1元素列表,但事实并非如此。

那么我如何修改我可以处理此注释行的代码呢?和closly相关:是否有一种优雅的方式来查询解析器结果,所以我可以在myfunc中写一些像

if (isComment(a)) continue

所以这是实际的代码:

import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import scala.util.parsing.combinator._

object MyParser extends RegexParsers {

    override val skipWhitespace = false   // meaningful spaces in CSV

    def COMMA   = ","
    def TAB     = "\t"
    def DQUOTE  = "\""
    def HASHTAG = "#"
    def DQUOTE2 = "\"\"" ^^ { case _ => "\"" }  // combine 2 dquotes into 1
    def CRLF    = "\r\n" | "\n"
    def TXT     = "[^\",\r\n]".r
    def SPACES  = "[ ]+".r

    def file: Parser[List[List[String]]] = repsep((comment|record), CRLF) <~ (CRLF?)
    def comment: Parser[List[String]] = HASHTAG<~TXT
    def record: Parser[List[String]] = "[^#]".r<~repsep(field, TAB)
    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 applyParser(s: String) = parseAll(file, s) match {
        case Success(res, _) => res
        case e => throw new Exception(e.toString)
    }

    def myfunc( a: (String, String)) = {
        val parserResult = applyParser(a._2)
        println("APPLY PARSER FOR " + a._1)
        for( a <- parserResult ){
            a.foreach { println }
        }
    }

    def main(args: Array[String]) {
        val filesPath = "/home/user/test/*.txt"
        val conf = new SparkConf().setAppName("Simple Application")
        val sc = new SparkContext(conf)
        val logData = sc.wholeTextFiles(filesPath).cache()
        logData.foreach( x => myfunc(x))
    }
}

1 个答案:

答案 0 :(得分:1)

由于注释的解析器和记录的解析器是“or-ed”在一起,因此它们必须属于同一类型。
您需要进行以下更改:

def comment: Parser[List[String]] = HASHTAG<~TXT ^^^ {List()}

通过使用^^^,我们将解析器的结果(由HASHTAG解析器返回的结果)转换为空列表。
也改变:

def record: Parser[List[String]] = repsep(field, TAB)

请注意,因为注释和记录解析器是or-ed并且因为注释首先出现,所以如果行以"#"开头,它将由注释解析器解析。

编辑:
为了将注释文本保留为解析器的输出(如果您想稍后打印它们),并且因为您使用|,您可以执行以下操作:
定义以下类:

trait Line
case class Comment(text: String) extends Line
case class Record(elements: List[String]) extends Line

现在定义评论,记录&amp;文件解析器如下:

val comment: Parser[Comment] = "#" ~> TXT ^^ Comment
val record :Parser[Line]= repsep(field, TAB) ^^ Record
val file: Parser[List[Line]] = repsep(comment | record, CRLF) <~ (CRLF?)

现在您可以定义打印功能myFunc

def myfunc( a: (String, String)) = {
  parseAll(file, a._2).map { lines =>
   lines.foreach{
     case Comment(t) => println(s"This is a comment: $t")
     case Record(elems) => println(s"This is a record: ${elems.mkString(",")}")
   }
  }
}