2012-06-13 1 views
6

실례지만이 질문의 n00bness는 문제가되지만 잠재적으로 큰 파일을 서버로 보내고 형식을 구문 분석 할 웹 응용 프로그램이 있습니다. Play20 프레임 워크를 사용하고 있으며 스칼라에 익숙하지 않습니다.스칼라 Play20의 BodyParser로 파일을 새로운 줄로 바꾸기

예를 들어 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이 일에 대해 올바른 방법을 쓰고있다? 이 파일을 분석하는 더 좋은 방법이 있습니까? 필자의 주된 관심사는 파일을 매우 크게 허용하여 일부 지점에서 버퍼를 플러시하고 전체 파일을 메모리에 보관하지 못하게 할 수 있다는 것입니다.

답변

10

csv에 이스케이프 처리 된 줄 바꿈 문자가 없으면 전체 파일을 메모리에 저장하지 않고 점진적 파싱을 수행하는 것이 매우 쉽습니다. umatched에 접어 것이다 또 다른 그럼 당신은 헤더를 취하는 첫번째 iteratee을 결합 할 수 있습니다 Matched[Array[Byte]]Unmatched[Array[Byte]]

로 스트림을 분할합니다

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

및 다음 iteratee 라이브러리는 play.api.libs.iteratee.Parsing 내부 방법 검색과 함께 제공 결과. 이 코드는 다음과 같습니다.

// 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)) 

물론 구문 분석을 향상시킬 수 있습니다. 그렇게하면 커뮤니티와 공유하면 좋을 것입니다. 이 코드는 유망 보이지만은 ... 스칼라가에게 큰 학습 곡선을 제공 한 모든 사업자를 이해하는 나에게 조금 걸릴 거예요

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

:

그래서 마찬가지로 play2 컨트롤러는 같은 것을 할 것이다. –

+0

물론, 이전 코드를><>를 작성으로 바꾸고, >>을 변환으로, >>>> 실행하여 바꿀 수 있습니다. 이 연산자는 스칼라가 아닌 해당 객체의 메소드입니다. – Sadache

+0

아, 예전의 Enumeratees 문서를 다시 읽었습니다. 감사! –

1

당신이 다음 play.api.mvc.BodyParsers.parse.tolerantText 같은 몸 파서 사용할 수있는 메모리의 List[List[String]] 크기의 두 배를 잡고 마음을하지 않는 경우 : 당신이 메모리 소비를 줄이려면

def toCsv = Action(parse.tolerantText) { request => 
    val data = request.body 
    val reader = new java.io.StringReader(data) 
    // use a Java CSV parsing library like http://opencsv.sourceforge.net/ 
    // to transform the text into CSV data 
    Ok("Done") 
} 

, 나는 Array[Array[String]]를 사용하는 것이 좋습니다 있음을 나 Vector[Vector[String]]은 변경 가능하거나 변경 불가능한 데이터를 처리하려는 경우에 따라 다릅니다.

대용량 데이터 (또는 중간 크기 데이터 요청 손실)를 처리하고 점진적으로 처리를 수행하는 경우 자신의 바디 파서를 롤링 할 수 있습니다. 해당 본문 파서는 List[List[String]]을 생성하지 않고 대신 줄을 구문 분석하여 각 줄을 증분 결과로 접습니다. CSV가 큰 따옴표를 사용하여 쉼표, 줄 바꿈 또는 큰 따옴표로 필드를 지원하는 경우 특히 그렇습니다.