2012-06-13 25 views
6

Xin lỗi về câu hỏi này, nhưng tôi có một ứng dụng web mà tôi muốn gửi một tệp có khả năng lớn đến máy chủ và phân tích cú pháp định dạng. Tôi đang sử dụng khung Play20 và tôi mới sử dụng Scala.Phân tích cú pháp tệp với BodyParser trong Scala Play20 bằng dòng mới

Ví dụ: nếu tôi có csv, tôi muốn chia từng hàng theo "," và cuối cùng tạo một List[List[String]] với từng trường.

Hiện tại, tôi đang nghĩ cách tốt nhất để làm điều này là với một BodyParser (nhưng tôi có thể sai). Mã của tôi trông giống như sau:

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

câu hỏi đầu tiên của tôi là, làm thế nào để đối phó với một tình huống như hình dưới đây, nơi một đoạn đã được chia ở giữa của một dòng:

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

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

của tôi câu hỏi thứ hai là, viết cho riêng BodyParser của tôi đúng cách để đi về điều này? Có cách nào tốt hơn để phân tích cú pháp tệp này không? Mối quan tâm chính của tôi là tôi muốn cho phép các tệp quá lớn nên tôi có thể xóa bộ đệm tại một số điểm và không giữ toàn bộ tệp trong bộ nhớ.

Trả lời

10

Nếu csv của bạn không chứa các dòng mới đã thoát thì rất dễ dàng để thực hiện phân tích tiến bộ mà không cần đưa toàn bộ tệp vào bộ nhớ. Thư viện iteratee đi kèm với một tìm kiếm phương pháp bên play.api.libs.iteratee.Parsing:

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

mà sẽ phân vùng luồng của mình vào Matched[Array[Byte]]Unmatched[Array[Byte]]

Sau đó, bạn có thể kết hợp một iteratee đầu tiên mà có một tiêu đề và phần còn lại sẽ gấp vào umatched các kết quả. Mã này trông giống như sau:

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

Tất nhiên bạn có thể cải thiện việc phân tích cú pháp. Nếu bạn làm thế, tôi sẽ đánh giá cao nếu bạn chia sẻ nó với cộng đồng.

Vì vậy, điều khiển Play2 của bạn sẽ là một cái gì đó như:

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

Mã này trông đầy hứa hẹn nhưng nó sẽ đưa tôi một chút để hiểu ... tất cả các nhà khai thác Scala có cung cấp cho nó một đường cong học tập lớn. –

+0

Tuyệt đối không, bạn có thể viết lại mã trước thay thế><> bằng cách soạn thư, & >> bằng cách biến đổi, | >>> bằng cách chạy. Các toán tử này không phải từ scala mà là các phương thức của các đối tượng tương ứng. – Sadache

+0

À vâng, tôi đã đọc qua các tài liệu trên Enumeratees một lần nữa và điều này có ý nghĩa. Cảm ơn! –

1

Nếu bạn không nhớ giữ hai lần kích thước của List[List[String]] trong bộ nhớ sau đó bạn có thể sử dụng một bộ phân tích cơ thể như play.api.mvc.BodyParsers.parse.tolerantText:

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") 
} 

Lưu ý rằng nếu bạn muốn giảm mức tiêu thụ bộ nhớ, tôi khuyên bạn nên sử dụng Array[Array[String]] hoặc Vector[Vector[String]] tùy thuộc vào việc bạn có muốn xử lý dữ liệu có thể thay đổi hoặc không thay đổi được hay không.

Nếu bạn đang xử lý lượng dữ liệu thực sự lớn (hoặc mất yêu cầu dữ liệu kích thước trung bình) và việc xử lý của bạn có thể được thực hiện từng bước, bạn có thể xem xét phân tích cú pháp cơ thể của riêng bạn. Trình phân tích cú pháp cơ thể đó sẽ không tạo ra một List[List[String]] mà thay vào đó phân tích cú pháp các dòng khi chúng đến và gấp từng dòng vào kết quả gia tăng. Nhưng điều này khá phức tạp hơn một chút, đặc biệt nếu CSV của bạn đang sử dụng dấu ngoặc kép để hỗ trợ các trường có dấu phẩy, dòng mới hoặc dấu ngoặc kép.

Các vấn đề liên quan