运维开发网

使用新线条在Scala Play20中使用BodyParser解析文件

运维开发网 https://www.qedev.com 2020-05-31 08:02 出处:网络 作者:运维开发网整理
请原谅这个问题的缺点,但我有一个Web应用程序,我想将一个可能很大的文件发送到服务器并让它解析格式.我正在使用Play20框架,而且我是 Scala的新手. 例如,如果我有一个csv,我想用“,”分隔每一行,最后用每个字段创建一个List [List [String]]. 目前,我认为最好的方法是使用BodyParser(但我可能是错的).我的代码看起来像: Iteratee.fold[Strin
请原谅这个问题的缺点,但我有一个Web应用程序,我想将一个可能很大的文件发送到服务器并让它解析格式.我正在使用Play20框架,而且我是 Scala的新手.

例如,如果我有一个csv,我想用“,”分隔每一行,最后用每个字段创建一个List [List [String]].

目前,我认为最好的方法是使用BodyParser(但我可能是错的).我的代码看起来像:

Iteratee.fold[String, List[List[String]]]() {
  (result, chunk) =>
    result = chunk.splitByNewLine.splitByDelimiter // Psuedocode
}

我的第一个问题是,我如何处理下面的一个情况,其中一个块被分割在一行中间:

Chunk 1:
1,2,3,4\n
5,6

Chunk 2:
7,8\n
9,10,11,12\n

我的第二个问题是,编写我自己的BodyParser是正确的方法吗?有没有更好的方法来解析这个文件?我主要担心的是我想允许文件非常大,所以我可以在某个时刻刷新缓冲区而不是将整个文件保存在内存中.

如果你的csv不包含转义的换行符,那么在不将整个文件放入内存的情况下进行渐进式解析非常容易. iteratee库在 play.api.libs.iteratee.Parsing中附带了一个方法搜索:

def search (needle: Array[Byte]): Enumeratee[Array[Byte], MatchInfo[Array[Byte]]]

它会将您的流划分为Matched [Array [Byte]]和Unmatched [Array [Byte]]

然后,您可以组合第一个带有标题的iteratee和另一个将折叠成umatched结果的iteratee.这应该类似于以下代码:

// break at each match and concat unmatches and drop the last received element (the match)
val concatLine: Iteratee[Parsing.MatchInfo[Array[Byte]],String] = 
  ( Enumeratee.breakE[Parsing.MatchInfo[Array[Byte]]](_.isMatch) ><>
    Enumeratee.collect{ case Parsing.Unmatched(bytes) => new String(bytes)} &>>
    Iteratee.consume() ).flatMap(r => Iteratee.head.map(_ => r))

// group chunks using the above iteratee and do simple csv parsing
val csvParser: Iteratee[Array[Byte], List[List[String]]] =
  Parsing.search("\n".getBytes) ><>
  Enumeratee.grouped( concatLine ) ><>
  Enumeratee.map(_.split(',').toList) &>>
  Iteratee.head.flatMap( header => Iteratee.getChunks.map(header.toList ++ _) )

// an example of a chunked simple csv file
val chunkedCsv: Enumerator[Array[Byte]] = Enumerator("""a,b,c
""","1,2,3","""
4,5,6
7,8,""","""9
""") &> Enumeratee.map(_.getBytes)

// get the result
val csvPromise: Promise[List[List[String]]] = chunkedCsv |>>> csvParser

// eventually returns List(List(a, b, c),List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))

当然,您可以改进解析.如果您这样做,我将不胜感激,如果您与社区分享.

所以你的Play2控制器将是这样的:

val requestCsvBodyParser = BodyParser(rh => csvParser.map(Right(_)))

// progressively parse the big uploaded csv like file
def postCsv = Action(requestCsvBodyParser){ rq: Request[List[List[String]]] => 
  //do something with data
}
0

精彩评论

暂无评论...
验证码 换一张
取 消