1. 程式人生 > >csv表格處理(下)--純JS解析匯入csv

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

});

這個外掛比較強大,解析上基本沒有什麼大問題,但仍然不是十分完善。問題如下:

  1. 檔案最末尾的空行沒有自動去除,可能會導致表單填多一點空資料;
  2. 不能自動識別UTF8與GBK,中文解析可能亂碼;
  3. 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的輪子