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的輪子😂


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM