多日前的上篇介紹了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的輪子😂