2012-06-13 13 views
6

इस प्रश्न की n00bness क्षमा करें, लेकिन मेरे पास एक वेब एप्लिकेशन है जहां मैं सर्वर पर संभावित रूप से बड़ी फ़ाइल भेजना चाहता हूं और इसे प्रारूप को पार्स करना चाहता हूं। मैं Play20 ढांचे का उपयोग कर रहा हूं और मैं स्कैला के लिए नया हूं।नई लाइनों के साथ स्काला प्ले 20 में बॉडीपार्सर के साथ एक फ़ाइल को पार्स करना

उदाहरण के लिए, यदि मेरे पास एक सीएसवी है, तो मैं प्रत्येक पंक्ति को "," से विभाजित करना चाहता हूं और आखिरकार प्रत्येक क्षेत्र के साथ List[List[String]] बना सकता हूं।

वर्तमान में, मैं सोच रहा हूं कि ऐसा करने का सबसे अच्छा तरीका बॉडीपार्सर के साथ है (लेकिन मैं गलत हो सकता हूं)। मेरे कोड लग रहा है कि:

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 

मेरे दूसरा सवाल यह है कि, इस बारे में जाने के लिए अपना खुद का बॉडीपार्स सही तरीका लिख ​​रहा है? क्या इस फाइल को पार्स करने के बेहतर तरीके हैं? मेरी मुख्य चिंता यह है कि मैं फ़ाइलों को बहुत बड़ा होने की अनुमति देना चाहता हूं ताकि मैं किसी बिंदु पर एक बफर फ्लश कर सकूं और पूरी फ़ाइल को स्मृति में न रख सकूं।

उत्तर

10

यदि आपके सीएसवी में बचने वाली न्यूलाइन नहीं है तो पूरी फ़ाइल को स्मृति में डाले बिना प्रगतिशील पार्सिंग करना बहुत आसान है। और

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

जो Matched[Array[Byte]] और Unmatched[Array[Byte]]

में अपनी स्ट्रीम विभाजन तो फिर तुम एक पहली iteratee कि एक हैडर लेता गठजोड़ कर सकते हैं होगा एक और है कि umatched में गुना होगा: 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

आह हाँ, मैंने फिर से एन्युमेरेट्स पर दस्तावेज़ों के माध्यम से पढ़ा और यह समझ में आता है। धन्यवाद! –

1

आप स्मृति में List[List[String]] का दो बार आकार पकड़े तो आप 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") 
} 

ध्यान दें कि अगर आप स्मृति की खपत को कम करना चाहते हैं, मैं Array[Array[String]] का उपयोग करना चाहिये या Vector[Vector[String]] इस पर निर्भर करता है कि क्या आप उत्परिवर्तनीय या अपरिवर्तनीय डेटा से निपटना चाहते हैं।

यदि आप वास्तव में बड़ी मात्रा में डेटा (या मध्यम आकार के डेटा के अनुरोधों से गुम हो गए हैं) से निपट रहे हैं और आपकी प्रसंस्करण में वृद्धि हो सकती है, तो आप अपने शरीर के पार्सर को घुमाएंगे। वह बॉडी पार्सर उत्पन्न नहीं करेगा, बल्कि इसके बाद लाइनों को पार्स कर देगा क्योंकि वे प्रत्येक पंक्ति को वृद्धिशील परिणाम में बदल देंगे। लेकिन यह काफी जटिल है, विशेष रूप से यदि आपका सीएसवी कॉमा, न्यूलाइन या डबल कोट्स वाले फ़ील्ड का समर्थन करने के लिए डबल कोट का उपयोग कर रहा है।

संबंधित मुद्दे