csv表格處理(下)--純JS解析匯入csv
多日前的上篇介紹了csv表格,以及JS結合後端PHP解析表格填充表單的方法。其中csv轉換成二維陣列的時候邏輯比較複雜多坑,幸好PHP有豐富的庫函式來處理,而現在用JS解析的話就沒有那麼幸運了,一切都要自己擼一個出來 或者 →_→ 引入一個庫。
JS匯入CSV--讀取文字
JS能前端讀取檔案嗎?以前只有通過 IE的ActiveXObject或者Flash才能本地讀取檔案。隨著H5的出現,這個問題有普遍解了。Talk is cheap,show you the code
$.fn.csv2arr = function( ){ var files = $(this)[0].files;if( typeof(FileReader) !== 'undefined' ){ //H5 var reader = new FileReader(); reader.readAsText( files[0] ); //以文字格式讀取 reader.onload = function(evt){ var data = evt.target.result; //讀到的資料 console.log(data); } }else{ alert("IE9及以下瀏覽器不支援,請使用Chrome或Firefox瀏覽器"); } } //呼叫方法 $("#startBtn").click(function(){ $("#csvInput").csv2arr(); });
這裡的關鍵就是 FileReader,是H5標準裡的讀取檔案的一個標準實現方式,IE10及以上版本以及chrome/firefox/safari等支援。呼叫方式方法也比較簡單,只需要傳入檔案輸入框的DOM,設定讀取方式然後繫結回撥函式就行了。這裡使用的是 readAsText() 的方式,讀取為文字格式。參考火狐的MDN文件,還有以base64,二進位制等方式,可自行參考嘗試。UTF8文字檔案讀取如下:
注意:readAsText()會自動把utf8檔案的BOM頭(如果有的話)去除,其它讀取方式要注意手動去除。
題外話:為什麼H5會出現這種直接讀取本地檔案的API,對安全的威脅大嗎?其實這對瀏覽器使用者的安全威脅是基本上沒有擴大的,試想一下,原來沒有這種讀本地檔案的API的時候,網站有沒有獲取本地檔案的許可權?當然是有的,還是通過這個input type="file",繫結一個onchange事件到 Ajax提交,使用者的檔案就悄悄地傳到網站後端去了。這問題還是得靠提高網民的安全意識,像以前的釣魚盜號網站,偽造個QQ登入介面就能坐收漁利。這種也能偽造一個下載按鈕和對話方塊,誘導使用者把重要機密檔案上傳上去。
JS匯入CSV--文字解析外掛
因為JS沒有像PHP那樣的CSV處理函式,上一篇文章說到裡面有不少複雜情況要處理,那麼最機(雞)智(賊)的方法當然是:找外掛。其中用的人最多的csv外掛是 PapaParse.js 。經典的使用方法如下
// Parse local CSV file $("#csvBtn").click(function(){ var file = $("input[name=csv]").[0].files[0]; Papa.parse(file, { complete: function(results) { console.log("Finished:", results.data); } }); });
這個外掛比較強大,解析上基本沒有什麼大問題,但仍然不是十分完善。問題如下:
- 檔案最末尾的空行沒有自動去除,可能會導致表單填多一點空資料;
- 不能自動識別UTF8與GBK,中文解析可能亂碼;
- UTF8編碼下, \r\n與\n混用時有可能會解析出問題,這個是PapaParse解析的演算法問題,還請高手去其官方github提供修復。
JS匯入CSV--編碼自動識別
剛說到的第三點,如果表格內容有中文的話,就是個大問題了。因為一般網頁的編碼是UTF8,匯出的表格也會是UTF8編碼格式,如果不修改直接上傳則為UTF8。但是如果修改,Windows平臺下的常用表格軟體包括Office和WPS全都將其轉換成GBK編碼。如果程式沒有自動識別編碼處理,將有很大概率導致亂碼。
另一方面,如果網頁使用GBK編碼格式下載,也不能確保使用者上傳的檔案就一定是GBK,因為MAC系統用的是UTF8,可能本來GBK的在修改後就成了UTF8了。
或者可以給個下拉欄讓使用者手動選擇編碼格式,但是你要指導使用者知道編碼格式是什麼東西,怎麼檢視,這可不是什麼容易讓人接受的事。那怎麼做編碼自動識別呢?UTF8與GBK是不是有明顯的編碼特徵用以區分,報歉的是還真沒有。那怎麼辦?找輪子。在哪找?對於JS的輪子,國內有個很好的CDN庫,雖然介紹是全英的但還是很好找。我們要找的是編碼解碼,那就Ctrl+F搜 encod (encode和encoding的前面幾個單詞),一個個看看介紹,還真能找到一個,名為jschardet。
點進去,沒有詳細說明,那就再去其github頁。看看示例程式碼
// "àíàçã" in UTF-8 jschardet.detect("\xc3\xa0\xc3\xad\xc3\xa0\xc3\xa7\xc3\xa3") // { encoding: "UTF-8", confidence: 0.9690625 } // "次常用國字標準字型表" in Big5 jschardet.detect("\xa6\xb8\xb1\x60\xa5\xce\xb0\xea\xa6\x72\xbc\xd0\xb7\xc7\xa6\x72\xc5\xe9\xaa\xed") // { encoding: "Big5", confidence: 0.99 }
什麼鬼?看起來用的好像不是普通字串啊,看起來像是十六進位制碼的樣子。實踐了發現,傳普通字串進去全部都是識別為ASCII編碼,確實有點難搞啊。怎麼辦呢?
莫慌莫慌,我們不是要讀取本地檔案拿來解析嗎?再看看火狐的MDN文件除了readAsText()讀取為字串以外還有什麼方法可以用。有個readAsBinaryString(),但是並不是標準的H5讀取方法,有些瀏覽器可能不支援。再看有一個readAsDataURL(),這什麼東西呢,試試便知道。結果得到一串這樣的東西
data:text/csv;base64,NiywzczYwPvM2KGksLIKMyzN0LDdtvLLuaGkx+0KOCy2xc3+oaS3xrDCxMkK
改檔案再試多幾次,原來是這樣子的:前面的 data:text/csv;base64, 是固定字串,僅對火狐,chrome和IE前面的是 data:;base64, ,後面的那一串是檔案內容經過base64編碼而成。那麼把後面這個一串解碼出來看看,IE>=10、火狐、chrome有原生的base64解碼函式 atob()。然後就得到了一個英文正常,中文全是亂碼的字串了,而且這個字串的亂碼看起來不像UTF8也不像GBK。那麼很可能這就是十六進位制碼了吧,用jschardet檢測一下,成功了!
總結整理
到這裡,我們已經用第三方的JS解決了最大的兩個難題,編碼識別和CSV解析。那麼就把這些整合一下,封裝成一個更方便呼叫的方法吧
/** * csv file to 2D arr * */ $.fn.csv2arr = function( callback ){ if( typeof(FileReader) == 'undefined' ){ //if not H5 alert("IE9及以下瀏覽器不支援,請使用Chrome或Firefox瀏覽器\nYour browser is too old,please use Chrome or Firefox"); return false; } if( ! $(this)[0].files[0]){ alert("請選擇檔案\nPlease select a file"); return false; } var fReader = new FileReader(); fReader.readAsDataURL( $(this)[0].files[0] ); $fileDOM = $(this); fReader.onload = function(evt){ var data = evt.target.result; // console.log( data ); var encoding = checkEncoding( data ); // console.log(encoding); //轉換成二維陣列,需要引入Papaparse.js Papa.parse( $($fileDOM)[0].files[0], { encoding: encoding, complete: function(results) { // UTF8 \r\n與\n混用時有可能會出問題 // console.log(results); var res = results.data; if( res[ res.length-1 ] == ""){ //去除最後的空行 res.pop(); } callback && callback( res ); } }); } fReader.onerror = function(evt){ // console.log(evt); alert("檔案已修改,請重新選擇(Firefox)\nThe file has changed,please select again.(Firefox)"); } //檢查編碼,引用了 jschardet function checkEncoding( base64Str ){ //這種方式得到的是一種二進位制串 var str = atob( base64Str.split(";base64,")[1] ); // console.log(str); //要用二進位制格式 var encoding = jschardet.detect( str ); encoding = encoding.encoding; // console.log( encoding ); if( encoding == "windows-1252"){ //有時會識別錯誤(如UTF8的中文二字) encoding = "ANSI"; } return encoding; } }
使用例子
<input type="file" name="csvfile" /> <input type="button" onclick="csv2()" value="JS轉換"/> <script src="__PJS__/jquery.js"></script> <script src="__PJS__/papaparse.js"></script> <script src="__PJS__/jschardet.js"></script> <script> function csv2(){ $("input[name=csvfile]").csv2arr(function(res){ alertTips("F12開啟瀏覽器控制檯看看"); console.log( res ); }); } </script>
下載與更新
演示地址DEMO
zip打包下載
Github地址,求加星,求一起修BUG
DRY--Don't Repeat Yourself. 別亂造些滿是bug的輪子