HTML5 + sheet.js 를 이용한 클라이언트 Excel parse


바야흐로 2017.05.31 기준. 


혹시나 싶어서 검색한 결과 진작에 클라이언트에서도 엑셀 파싱처리가 가능해졌다.


일전에 썼던글인 "HTML Table을 엑셀로 내보내기 : Export to excel sheet on client side" 에서는 


엑셀 저장하기를 다루었는데 a 태그의 HTML5 속성인 datatype과 download를 이용하면 형식과 파일명을 


직접 만들고 저장하기가 가능해져서 약간의 규칙만 지킨다면 라이브러리 따위없어도 엑셀형식을 갖춘 파일을 


만들 수 있었고 테이블 셀의 속성을 전부 재현해 내고 싶은 경우 라이브러리를 사용하면 (아마) 엑셀을 


출력키위한 정확한 헤더파일을 지칭하고 그 스타일(혹은 태그)을 온건히 반영한 정도가 분명할 것이기에 


그다지 새로운 내용을 접한 느낌은 아니었다.


그러나.


엑셀을 읽는다는것은 binary 혹은 Blob 같은 raw data 버퍼처리가 가능하다는 얘기인데.. 


처음 내 예상은 node.js를 통해서 사용한 바이너리읽기 정도로 예상했으나 ( 역시 npm에 있을줄 알았다 )[각주:1]


파일은 HTML5의 객체로 읽고, 이후의 내용 출력은 능력자가 오픈소스로 만든 sheet.js 라이브러리를 이용하여


binary 혹은 base64 형식에 대한 파싱이 가능하다.


자세한 내용은 계속 이어가겠다.




   HTML5 의 FileReader 객체



먼저 HTML5의 FileReader 객체는 클라이언트 File객체의 내용읽고 컴퓨터에 저장하는것을 가능하게 해준다.


           이 File객체를 얻기 위한 방법은 아래와 같은 3가지 솔루션이 있다. 


           1. 흔히 사용하는 <input> 태그를 이용하여 유저가 선택한 파일들의 결과인 FileList 객체


           2. 드래그 앤 드랍으로 반환된 DataTransfer 객체


           3. HTML CanvasElement의 mozGetAsFile() API 로 얻을 수 있다.



속성EDIT

FileReader.error Read only
DOMError  파일을 읽는 도중에 발생한 에러를 나타냅니다.
FileReader.readyState Read only
FileReader의 상태를 나타내는 숫자입니다

상태 값들

EMPTY   : 0 : 아직 데이터가 로드 되지 않았음.
LOADING : 1 : 데이터가 로딩 중.
DONE    : 2 : 모든 읽기 요청이 완료됨.
 
FileReader.result Read only
파일의 컨텐츠입니다. 이 속성은 읽기 작업이 완료되고 읽기 작업의 초기화에 사용한 방식으로 결정된 데이터의 포맷이 정해진 후에 유효합니다.

이벤트 핸들러

FileReader.onabort
abort 이벤트의 핸들러. 이 이벤트는 읽기 동작이 중단 될 때마다 발생합니다.
FileReader.onerror
error 이벤트의 핸들러. 이 이벤트는 읽기 동작에 에러가 생길 때마다 발생합니다.
FileReader.onload
load 이벤트의 핸들러. 이 이벤트는 읽기 동작이 성공적으로 완료 되었을 때마다 발생합니다.
FileReader.onloadstart
loadstart 이벤트 핸들러. 이 이벤트는 읽기 동작이 실행 될 때마다 발생합니다.
FileReader.onloadend
loadend 이벤트 핸들러. 이 이벤트는 읽기 동작이 끝났을 때마다 발생합니다. (읽기의 성공이나 실패 여부는 상관 않습니다.)
FileReader.onprogress
progress 이벤트 핸들러. 이 이벤트는 Blob 컨텐트를 읽는 동안 호출됩니다.


메소드EDIT

FileReader.abort()
읽기 요청을 중단시킵니다.  리턴이 되면 readyState 는 DONE이 됩니다.
FileReader.readAsArrayBuffer()
Starts reading the contents of the specified Blob, once finished, the result attribute contains an ArrayBuffer representing the file's data.
FileReader.readAsBinaryString()
Starts reading the contents of the specified Blob, once finished, the result attribute contains the raw binary data from the file as a string.
FileReader.readAsDataURL()
Starts reading the contents of the specified Blob, once finished, the result attribute contains a data: URL representing the file's data.
FileReader.readAsText()
Starts reading the contents of the specified Blob, once finished, the result attribute contains the contents of the file as a text string.

Browser compatibilityEDIT

FeatureFirefox (Gecko)ChromeInternet Explorer*Opera*Safari
Basic support3.6 (1.9.2)71012.026.0.2


출처 : https://developer.mozilla.org/ko/docs/Web/API/FileReader




TIP ) 


꼭 FileReader를 사용해야하나? 그건 아니다. Uint8Array메서드를 사용하면 버퍼읽기가 가능하고 


버퍼를 읽는다는것은 읽은 버퍼를 모두 join시켜서 바이너리데이터로 가공하는것이 가능함을 이야기한다.


1
2
3
4
5
  /* convert data to binary string */
  var data = new Uint8Array(arraybuffer);
  var arr = new Array();
  for(var i = 0; i != data.length++i) arr[i] = String.fromCharCode(data[i]);
  var bstr = arr.join("");
cs


또한 array buffer 인경우 base64 로 인코딩도 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 /* processing array buffers, only required for readAsArrayBuffer */
function fixdata(data) {
  var o = "", l = 0, w = 10240;
  for(; l<data.byteLength/w; ++l) o+=String.fromCharCode.apply(null,new Uint8Array(data.slice(l*w,l*w+w)));
  o+=String.fromCharCode.apply(nullnew Uint8Array(data.slice(l*w)));
  return o;
}
 
 
var files = e.dataTransfer.files;
var i,f;
for (i = 0; i != files.length++i) {
    f = files[i];
    var reader = new FileReader();
    var name = f.name;
    reader.onload = function(e) {
        var data = e.target.result;
 
        /* if array buffer, convert to base64 */
        var arr = fixdata(data);
    }
}
cs


내용정리 : serpiko


출처 : https://sheetjs.gitbooks.io/docs/#ecmascript-5-compatibility




   Javasscript Library Sheet.js



오픈소스로 제작된 Sheet.js ( http://sheetjs.com/ ) 에 대한 간략한 소개를 하자면


스프레드 시트의 복잡성을 추상화킨후 브라우저에서 재생해준다.


이 스프레드시트라는것이 대략 20가지가 있는데 아래와 같다고 한다. ( 난 그냥 Excel 얘기하는줄 알았는데... )



어쨌든 사용하고있는 주요한 스프레드 시트 형식을 지원한다고 하며


Free your Data! 당신의 데이터를 무료로! ... 제일 마음에 와닿는 말이었다.


기본사용 방법은 : https://github.com/SheetJS/js-xlsx  

                 

                      ( 잘 모르겠더라도 대략만 훑어보면 큰 도움이 된다. 다음 챕터에 샘플 코드를 만들어보았다 )


API 문서는 : https://sheetjs.gitbooks.io/docs/#installation


을 참고하면 된다.


마지막으로 지원하는 브라우저는






   SAMPLE CODE


공식홈에서 소개한 코드와 utils 메서드를 참조한 데이터 얻기를 조합을 사용해서 샘플 코드를 만들었으며, 


호출할 파일인 엑셀파일도 준비해보았다.


sample.xls


( 혹시나 라이브러리를 다운받아 사용해야 한다면 아래의 js를 다운받아서 script src='' 에 지정.. )

jquery-1.11.2.min.js

xlsx.full.min.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <input type="file" id="my_file_input" />
    <div id='my_file_output'></div>
 
 
 
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.10.3/xlsx.full.min.js"></script>
<!-- <script lang="javascript" src="dist/xlsx.full.min.js"></script> -->
<script>
 
var rABS = true// T : 바이너리, F : 어레이 버퍼
 
// 어레이 버퍼를 처리한다 ( 오직 readAsArrayBuffer 데이터만 가능하다 )
function fixdata(data) {
    var o = "", l = 0, w = 10240;
    for(; l<data.byteLength/w; ++l) o+=String.fromCharCode.apply(null,new Uint8Array(data.slice(l*w,l*w+w)));
    o+=String.fromCharCode.apply(nullnew Uint8Array(data.slice(l*w)));
    return o;
}
 
// 데이터를 바이너리 스트링으로 얻는다.
function getConvertDataToBin($data){
    var arraybuffer = $data;
    var data = new Uint8Array(arraybuffer);
    var arr = new Array();
    for(var i = 0; i != data.length++i) arr[i] = String.fromCharCode(data[i]);
    var bstr = arr.join("");
 
    return bstr;
}
function handleFile(e) {
    var files = e.target.files;
    var i,f;
    for (i = 0; i != files.length++i) {
        f = files[i];
        var reader = new FileReader();
        var name = f.name;
 
        reader.onload = function(e) {
            var data = e.target.result;
 
            var workbook;
 
            if(rABS) {
                /* if binary string, read with type 'binary' */
                workbook = XLSX.read(data, {type: 'binary'});
            } else {
                /* if array buffer, convert to base64 */
                var arr = fixdata(data);
                workbook = XLSX.read(btoa(arr), {type: 'base64'});
            }//end. if
 
            /* 워크북 처리 */
            workbook.SheetNames.forEach(function(item, index, array) {
 
                var csv = XLSX.utils.sheet_to_csv(workbook.Sheets[item]);
                var html = XLSX.utils.sheet_to_html(workbook.Sheets[item]);
                var json = XLSX.utils.sheet_to_json(workbook.Sheets[item]);
                var formulae = XLSX.utils.sheet_to_formulae(workbook.Sheets[item]);
 
                console.log(csv);
                console.log(html);
                console.log(json);
                console.log(formulae);
 
                $("#my_file_output").html(csv);
            });//end. forEach
        }; //end onload
 
        if(rABS) reader.readAsBinaryString(f);
        else reader.readAsArrayBuffer(f);
 
    }//end. for
}
 
var input_dom_element;
$(function() {
    input_dom_element = document.getElementById('my_file_input');
    if(input_dom_element.addEventListener) {
        input_dom_element.addEventListener('change', handleFile, false);
    }
});
 
//http://sheetjs.com/
//https://github.com/SheetJS/js-xls
</script>
 
 
</body>
</html>
 
cs



   결과






   내용추가 ( 2017.06.01 ) : sheet 내용 얻는 4가지 방법



위의 SAMPLE_CODE 항목의 workbook.SheetNames.forEach 부분에서 XLSX 객체를 순회하며 


sheets 정보를 가져오는 부분인 43번 라인에서 적용했었던 부분 중,


Export 데이터타입을 전부 알아보고, 이 중 주요한 옵션을 적용해 보았다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// CSV
var csv = XLSX.utils.sheet_to_csv(workbook.Sheets[item]); // default : ","
var csvToFS = XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:"\t"} ); // "Field Separator" delimiter between fields
var csvToFSRS = XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:":",RS:"|"} ); // "\n" "Record Separator" delimiter between rows
 
// html
var html = XLSX.utils.sheet_to_html(workbook.Sheets[item]);
var htmlHF = XLSX.utils.sheet_to_html(workbook.Sheets[item], {header:"<html><title='custom'><body><table>", footer:"</table><body></html>"});
var htmlTable = XLSX.utils.sheet_to_html(workbook.Sheets[item], {header:"<table border='1'>", footer:"</table>"});
 
// json
var json = XLSX.utils.sheet_to_json(workbook.Sheets[item]);
 
//formulae
var formulae = XLSX.utils.sheet_to_formulae(workbook.Sheets[item]);
formulae.filter(function(v,i){return i%13 === 0;});
 
console.group("CSV");
    console.log(csv);
    console.log(csvToFS);
    console.log(csvToFSRS);
console.groupEnd();
 
console.group("html");
    console.log(html);
    console.log(htmlHF);
    console.log(htmlTable);
console.groupEnd();
 
console.group("json");
    console.log(json);
console.groupEnd();
 
console.group("formulae");
    console.log(formulae);
console.groupEnd();
cs


CSV


2번 라인 : csv로 가져오기에서 기본값은 , (콤마) 이다.

    

              ex) A, B, C, 

                  A1_vaule, B1_value, C1_value ....


3번 라인 : csv로 가져오기에서 FS 옵션은 Field Separator로써 필드 사이의 구분 기호를 지정해 줄수있다. 


             여기서 \t 의 경우 수평탭이다. ( log 로 표현되는 탭 공백은 스페이스바 키로 3칸 정도된다 )

    

            ex)  A    B    C

                  A1_vaule    B1_vaule    C1_vaule.....



4번 라인 : csv로 가져오기에서 RS 옵션은 Record Separator 로써 행간의 구분 기호를 지정해 줄수있다.


             FS 로 : 옵션을, RS 로 | 옵션을 사용하였기 때문에 아래와 같이 표현된다.


            ex) A1:B1:C1|A1_value:B1_value:C1_value|A2_value:B2_value:C2_value|....


실무에서는 4번 라인처럼 필드에 : (COLON) 으로 값을 분할하고, 레코드의 행(ROW)에는  | (PIPE) 로 명확하게


구분지어주면 : 와 | 를 Tocken으로 사용하여 보다 명확하고 쉽게 사용할 수 있을것이다. 



HTML


7번 라인 : html로 가져오기에서 옵션이 없는 경우 


             <html><head><title> ~~ <table> 실제 파싱본분 ~~ </html> 까지 완전한 html 문서를 반환한다.


             물론 typeof 를 찍어보면 String 이지만 보통 바로 사용할수 없는 형태이기 때문에 


8번 라인 : 옵션으로 header, footer 를 주면 각각 옵션을 직접 지정할 수 있다.


9번 라인 : 실무에서 바로 사용하기에 제일 적합한 옵션이다. header:"<table>", footer:"</table>" 를 주면

    

              비로소 완전한 테이블로 엘리먼트를 얻을 수 있게되며 여기에 약간의 css만 추가한다면

 

              깔끔한 테이블 결과물이 출력될 것이다.


Export 의 더 자세한 내용은 https://github.com/SheetJS/js-xlsx#formulae-output 부터 참고하면 된다.




   결과



html )


크롬 개발자 도구 Developer Tools )






   내용추가 ( 2017.06.08 ) : 실무에서 사용하는 코드



먼저 위의 XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:":",RS:"|"} ); 부분을 조금 더 


사용하기 쉬운 json 으로 변경하는 함수를 만들었다.


위에서 사용했던 XLSX.utils.sheet_to_json(workbook.Sheets[item]); 구문은 이퀄(=)과 알파벳도 제공하긴 하는데 명확하게 내가 지정한 토큰으로 분리하는것이 


나을것 같아서 함수를 추가하여 사용.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// XLSX.utils.sheet_to_json 가 왠지 쓰기 더 불편하다. 그냥 csv에 FS, RS 토큰을 넣고 분리해내는 함수를 만들었다.
getCsvToJson : function( $csv ){
 
    //var row = $obj.filter(function(v,i){return i%5 === 0;});
    var startRow = this.defaultObj.startRow || 4;
    var csvSP = $csv.split"|" );
    var csvRow = [], csvCell = [];
    //var cellName = ["No", "W_FROM", "TAGNAME", "VAL_V 아직알수없음", "L_TYPE", "L_S_SIZE", "L_S_CNT", "L_CALC", "VL_V", "VL_PCNT", "MC_PCNT", "MC_AF", "MC_AT"];
    var cellName = ["W_FROM""TAGNAME""L_TYPE""L_S_SIZE""L_S_CNT""L_CALC""VL_V""VL_PCNT""MC_PCNT""MC_AF""MC_AT"];
    csvSP.forEach(function(item, index, array){
 
        var patt = new RegExp(":"); // 축약형patt = /:/ 대신...
        var isExistTocken = patt.test( item );
 
        if( isExistTocken && ( startRow - 1 ) <= index ){
            csvRow.push( item );
        }
    });
 
    csvRow.forEach(function(item, index, array){
        var row = item.split(":");
        var obj = {};
        row.forEach(function(item, index, array){
            obj[ cellName[index] ] = item;
        });
 
        csvCell[index] = obj;
    });
 
    return csvCell;
 
    //console.log( csvCell );
},
cs




실무에서 사용한 일부 코드. 


다음과 같이 Excel 클래스와 호출 클래스 분리하고 데이터는 콜백으로 얻어서 사용하면 될듯하다.



ExcelParse.js : Excel 파싱라이브러리를 사용하기위한 처리와 데이터 가공담당


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/* --------------------------------------------------------
*
* ExcelParse
*
-----------------------------------------------------------*/
var ExcelParse = function($Control, $obj){
 
    //private
    var name = "ExcelParse";
    var author = "허정진";
    var ver = "2017.06.01";
 
    //public
    this.name = name;
    this.control = $Control;
    this.elementDefined = this.inherit( ElementDefined, this.elementDefined ); //엘리먼트만 사용하기위해서 객체 상속
    this.model = $Control.model;
 
    //extend
    this.defaultObj = {
        validFileExtensions : [".xlsx"".xls"],
        rABS : true// T : 바이너리, F : 어레이 버퍼
        startRow : 4
    };
    for(var key in $obj) if( $obj.hasOwnProperty(key) ) this.defaultObj[key] = $obj[key];
 
    //build
    this.build();
}//end. ExcelParse
 
ExcelParse.prototype = (function(){
 
    return{
 
        constructor: ExcelParse,
 
        //
        build: function(){
//            if( typeof this.control == "undefined" || this.control.name != "Control"  ){
//                throw new Error( "["+this.name+"] : Control 객체를 상속받지 못했습니다. Control 객체를 먼저 '생성'해 주세요." );
//                return false;
//            }
 
            var $this = this;
 
        },
 
        inherit: function( Parent, Child ){
            Child = function(){
                Parent.call( this );
            }
 
            try{
                if (!Object.create) {
                    Object.create = (function(){
                        function F(){}
                            return function(o){
                            if (arguments.length != 1) {
                                throw new Error('Object.create implementation only accepts one parameter.');
                            }
                            F.prototype = o;
                            return new F();
                        }
                    })();
                }
 
                Child.prototype = Object.create( Parent.prototype );
 
                Child.prototype.constructor = Child;
 
                //override
                //Child.prototype.build = function(){ alert('hi, I am a Child'); };
 
                var child = new Child();
                if(child instanceof Parent === true)
                    return child;
                else
                    return new Parent();
 
            }catch(e){
                throw new Error( "[ inherit Error ] : "+ Parent.name +"객체를 상속받지 못했습니다. "+ Parent.name +" 객체를 '확인'해 주세요." );
            }
        },
 
        init:function(){
 
        },
 
        destroy: function(){
            //$(document).off("click", "");
 
            //주의!!. dispose는 맨 마지막으로 실행한다. 소거.
            this.dispose();
        },
 
        dispose: function(){
 
            var object = this;
            var objectPrototype = object.__proto__;
 
            forvar key in object ){
                if( object.hasOwnProperty(key) ){
                    delete object[key];
                }
            }
            delete this.prototype;
 
            return;
 
            forvar key in objectPrototype ){
                if( objectPrototype.hasOwnProperty(key) ){
                    delete objectPrototype[key];
                }
            }
        }, //end. dispose
 
        addEventListenerfunction(){
            $this = this;
        },
 
        // 어레이 버퍼를 처리한다 ( 오직 readAsArrayBuffer 데이터만 가능하다 )
        fixdata : function(){
            var o = "", l = 0, w = 10240;
            for(; l<data.byteLength/w; ++l) o+=String.fromCharCode.apply(null,new Uint8Array(data.slice(l*w,l*w+w)));
            o+=String.fromCharCode.apply(nullnew Uint8Array(data.slice(l*w)));
            return o;
        },
 
        // 데이터를 바이너리 스트링으로 얻는다.
        getConvertDataToBin : function(){
            var arraybuffer = $data;
            var data = new Uint8Array(arraybuffer);
            var arr = new Array();
            for(var i = 0; i != data.length++i) arr[i] = String.fromCharCode(data[i]);
            var bstr = arr.join("");
 
            return bstr;
        },
 
        handleFile : function(e, $callback){
 
            var $this = this;
 
            //업로드 될 파일 존재유무 검사
            var excel_file_input = $this.control.elementDefined.excel_file_input;
            if0 == excel_file_input[0].files.length ){
                alert("파일을 업로드해주세요");
                excel_file_input.focus();
                return false;
            }
 
            //업로드 될 파일 확장자 검사
            var SearchForm = $this.control.elementDefined.SearchForm;
            if!$this.validate( SearchForm ) ) return false;
 
 
            //이하 로직실행
            var files = excel_file_input[0].files;
            //var files = e.target.files;
            var i,f;
            for (i = 0; i != files.length++i) {
                f = files[i];
                var reader = new FileReader();
                var name = f.name;
 
                reader.onload = function(e) {
                    var data = e.target.result;
 
                    var workbook, htmlTable = null, csvToFSRS = null;
 
                    if( $this.defaultObj.rABS ) {
                        /* if binary string, read with type 'binary' */
                        workbook = XLSX.read(data, {type: 'binary'});
                    } else {
                        /* if array buffer, convert to base64 */
                        var arr = fixdata(data);
                        workbook = XLSX.read(btoa(arr), {type: 'base64'});
                    }//end. if
 
                    /* 워크북 처리 */
                    workbook.SheetNames.forEach(function(item, index, array) {
 
                        /*
                        // CSV
                        var csv = XLSX.utils.sheet_to_csv(workbook.Sheets[item]); // default : ","
                        var csvToFS = XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:"\t"} ); // "Field Separator" delimiter between fields
                        var csvToFSRS = XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:":",RS:"|"} ); // "\n" "Record Separator" delimiter between rows
                        // html
                        var html = XLSX.utils.sheet_to_html(workbook.Sheets[item]);
                        var htmlHF = XLSX.utils.sheet_to_html(workbook.Sheets[item], {header:"<html><title='custom'><body><table>", footer:"</table><body></html>"});
                        var htmlTable = XLSX.utils.sheet_to_html(workbook.Sheets[item], {header:"<table border='1'>", footer:"</table>"});
                        // json
                        var json = XLSX.utils.sheet_to_json(workbook.Sheets[item]);
                        //formulae
                        var formulae = XLSX.utils.sheet_to_formulae(workbook.Sheets[item]);
                        */
 
                        htmlTable = XLSX.utils.sheet_to_html(workbook.Sheets[item], {header:"<table class='table table-striped table-hover table-borderd table-excel-parse'>", footer:"</table>"});
                        csvToFSRS = XLSX.utils.sheet_to_csv(workbook.Sheets[item], {FS:":",RS:"|"} ); // "\n" "Record Separator" delimiter between rows
 
                    });//end. forEach
 
                    var obj = {};
                    obj.json = $this.getCsvToJson( csvToFSRS );
                    obj.table = htmlTable;
                    $callback( obj ); // 비동기함수
 
                }; //end onload
 
                if($this.defaultObj.rABS) reader.readAsBinaryString(f);
                else reader.readAsArrayBuffer(f);
 
                return true;
 
            }//end. for
        },
 
        // XLSX.utils.sheet_to_json 가 왠지 쓰기 더 불편하다. 그냥 csv에 FS, RS 토큰을 넣고 분리해내는 함수를 만들었다.
        getCsvToJson : function( $csv ){
 
            //var row = $obj.filter(function(v,i){return i%5 === 0;});
            var startRow = this.defaultObj.startRow || 4;
            var csvSP = $csv.split"|" );
            var csvRow = [], csvCell = [];
            //var cellName = ["No", "W_FROM", "TAGNAME", "VAL_V 아직알수없음", "L_TYPE", "L_S_SIZE", "L_S_CNT", "L_CALC", "VL_V", "VL_PCNT", "MC_PCNT", "MC_AF", "MC_AT"];
            var cellName = ["W_FROM""TAGNAME""L_TYPE""L_S_SIZE""L_S_CNT""L_CALC""VL_V""VL_PCNT""MC_PCNT""MC_AF""MC_AT"];
            csvSP.forEach(function(item, index, array){
 
                var patt = new RegExp(":"); // 축약형patt = /:/ 대신...
                var isExistTocken = patt.test( item );
 
                if( isExistTocken && ( startRow - 1 ) <= index ){
                    csvRow.push( item );
                }
            });
 
            csvRow.forEach(function(item, index, array){
                var row = item.split(":");
                var obj = {};
                row.forEach(function(item, index, array){
                    obj[ cellName[index] ] = item;
                });
 
                csvCell[index] = obj;
            });
 
            return csvCell;
 
            //console.log( csvCell );
        },
 
        /******************************************************************************************************************
        *
        *    validate
        *
        ******************************************************************************************************************/
        validate: function(oForm){
 
            var _validFileExtensions = this.defaultObj.validFileExtensions || [".jpg"".jpeg"".bmp"".gif"".png"];
            var arrInputs = oForm.find("input");
 
            for (var i = 0; i < arrInputs.length; i++) {
                var oInput = arrInputs[i];
                if (oInput.type == "file") {
                    var sFileName = oInput.value;
                    if (sFileName.length > 0) {
                        var blnValid = false;
                        for (var j = 0; j < _validFileExtensions.length; j++) {
                            var sCurExtension = _validFileExtensions[j];
                            if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) {
                                blnValid = true;
                                break;
                            }
                        }
 
                        if (!blnValid) {
                            alert("경고, " + sFileName + " 는 유효하지않은 파일입니다.\n\n업로드는 다음형식을 지원합니다 : " + _validFileExtensions.join(", "));
                            return false;
                        }
                    }
                }
            }
 
            return true;
        },
 
    };
})();
 
cs



BuildList.js : ExcelParse 를 Cotrol 객체를 통하 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/* --------------------------------------------------------
*
* BuildList
*
-----------------------------------------------------------*/
var BuildList = function($Control, $obj){
 
    //private
    var name = "BuildList";
    var author = "허정진";
    var ver = "2017.06.07";
 
    //public
    this.name = name;
    this.control = $Control;
    this.elementDefined = this.inherit( ElementDefined, this.elementDefined ); //엘리먼트만 사용하기위해서 객체 상속
    this.model = $Control.model;
    this.excelJson = {};
    this.OBJID = null;
 
    //extend
    this.defaultObj = {
        autoGrid : null// 주의. menu_2_2.js 에서 autogrid 는 하나의 객체만 생성후에 참조하여 공유한다.
        sampleExcelFilePath : this.model.sampleExcelFilePath || $Config['Template']+"/download/fems_sample.xlsx",
        sampleExcelImgPath : this.model.sampleExcelImgPath || $Config['Template']+"/images/1_SYMBOL.png",
        sampleExcelICONID : this.model.sampleExcelICONID || "ICONID_1",
        sampleExcelImgW : this.model.sampleExcelImgW || 51,
        sampleExcelImgH : this.model.sampleExcelImgH || 51
    };
    for(var key in $obj) if( $obj.hasOwnProperty(key) ) this.defaultObj[key] = $obj[key];
 
    //build
    this.build();
}//end. BuildList
 
BuildList.prototype = (function(){
 
    return{
 
        constructor: BuildList,
 
        //
        build: function(){
//            if( typeof this.control == "undefined" || this.control.name != "Control"  ){
//                throw new Error( "["+this.name+"] : Control 객체를 상속받지 못했습니다. Control 객체를 먼저 '생성'해 주세요." );
//                return false;
//            }
 
            var $this = this;
 
            $this.elementDefined.display_build_list.show();
 
            $this.sendRequestSearch();
            $this.responseHeight();
            $this.addEventListener();
        },
 
        inherit: function( Parent, Child ){
            Child = function(){
                Parent.call( this );
            }
 
            try{
                if (!Object.create) {
                    Object.create = (function(){
                        function F(){}
                            return function(o){
                            if (arguments.length != 1) {
                                throw new Error('Object.create implementation only accepts one parameter.');
                            }
                            F.prototype = o;
                            return new F();
                        }
                    })();
                }
 
                Child.prototype = Object.create( Parent.prototype );
 
                Child.prototype.constructor = Child;
 
                //override
                //Child.prototype.build = function(){ alert('hi, I am a Child'); };
 
                var child = new Child();
                if(child instanceof Parent === true)
                    return child;
                else
                    return new Parent();
 
            }catch(e){
                throw new Error( "[ inherit Error ] : "+ Parent.name +"객체를 상속받지 못했습니다. "+ Parent.name +" 객체를 '확인'해 주세요." );
            }
        },
 
        init:function(){
            var $this = this;
 
            if( $this.defaultObj.autoGrid != null ) $this.defaultObj.autoGrid.init(); //오토그리드의 x,y를 다시 0,0으로 되돌립니다
        },
 
        destroy: function(){
            //$(document).off("click", "");
 
            //주의!!. dispose는 맨 마지막으로 실행한다. 소거.
            this.dispose();
        },
 
        dispose: function(){
 
            var object = this;
            var objectPrototype = object.__proto__;
 
            forvar key in object ){
                if( object.hasOwnProperty(key) ){
                    delete object[key];
                }
            }
            delete this.prototype;
 
            return;
 
            forvar key in objectPrototype ){
                if( objectPrototype.hasOwnProperty(key) ){
                    delete objectPrototype[key];
                }
            }
        }, //end. dispose
 
        addEventListenerfunction(){
            var $this = this;
            var el = $this.elementDefined;
            var excelParse = $this.control.excelParse;
 
            // 리사이즈되면 엘리먼트 재배치
            $(window).on('resize'function(){
                $this.responseHeight();
            });
 
            // 조회버튼
            $(document).on("click touchstart""#search_btn"function(e){
                $this.sendRequestSearch();
            });
 
            //저장
            $(document).on("click touchstart""#export_btn"function(e){
                excelParse.handleFile(e, $this.excelParseCallback);
            });
 
            // 샘플다운
            $(document).on("click touchstart""#excelDownload_btn"function(e){
                $(this).attr("href", $this.defaultObj.sampleExcelFilePath);
            });
 
            // 모달 => 저장
            $(document).on("click touchstart""#excelParseSave_btn"function(e){
                $this.excelSaveHandler();
            });
        },
 
        excelParseCallback : function( $resObj ){
            // this = window
            var $this = this.control.buildList;
            var el = this.control.buildList.elementDefined;
 
            el.excelParseArea.html( $resObj.table );
            el.excelParseModal.modal('show');
 
            $this.excelJson = $resObj.json;
        },
 
        excelSaveHandler: function(){
            var $this = this;
            var ed = $this.elementDefined;
            var model = $this.model;
            var MAPID = $this.model.MAPID;
 
            /************************************************************************************************
            * 맵아이디 체크
            ************************************************************************************************/
            if0 > MAPID || null === MAPID ){
                alert("맵이 생성되지 않았습니다!\n\n[ 메인 > 수변설비 Singleline > 맵 추가 ] 를 통하여\n\n맵을 먼저 생성해 주세요!");
            }
 
            /************************************************************************************************
            * 속성 초기화
            ************************************************************************************************/
            $this.init();
 
            /************************************************************************************************
            * 엑셀 json 파라메터 가공하기
            ************************************************************************************************/
            var src = $this.defaultObj.sampleExcelImgPath,
                ICONID = $this.defaultObj.sampleExcelICONID,
                w = $this.defaultObj.sampleExcelImgW,
                h = $this.defaultObj.sampleExcelImgH;
 
            var point = {};
            var div = "";
 
            //params
            var json = {};
            json.Record = [];
            var sendToObj = {};
 
            //console.log( $this.excelJson);
 
            $this.excelJson.forEach(function(item, index, array){
 
                point = $this.defaultObj.autoGrid.getPoint(6020); //w, h
 
                var DESCR = item.TAGNAME;
 
                sendToObj = {};
                sendToObj.MAPID = MAPID;
                sendToObj.POBJID = 0;
                sendToObj.TYPE = 1// TYPE 종류 : 0:unknown, 1:ICON, 2:TEXT, 3:HTML, 4:LINE
                sendToObj.DESCR = DESCR;
                sendToObj.TEXT = '';
                sendToObj.ICONID = ICONID.substr( ICONID.indexOf("_"+ 1 );
                sendToObj.ICONX = point.x;
                sendToObj.ICONY = point.y;
                sendToObj.ICONSCALE = 1;
                sendToObj.ICONWAY = 0;
                sendToObj.ICONW = w;
                sendToObj.ICONH = h;
                sendToObj.ICONW2 = w;
                sendToObj.ICONH2 = h;
                sendToObj.ICONDEPTH = ""// for by UNIQUE - API 연산한다
 
                json.Record.push( sendToObj ); //json 오브젝트의 Record 배열에 sendToObj 객체를 넣는다
            });
 
            var xhrClass = new XhrClass();
            var param = "mapapi=1&func=InsertMapObject&rqJobData=";
            param += JSON.stringify(json); // value to JSON
 
            xhrClass.sendRequest(model.JsonURL, param, complete, 'json''get');
            function complete(xhr, res)
            {
                // 저장에 성공하면 => $this.excelJson 에 OBJID 넣어주고 => 화면에 출력한다.
                if0 == res.ErrCode && 0 < res.Record.length ){
 
                    //console.log( res.Record );
                    res.Record.forEach(function(item, index, array){
                        $this.excelJson[index].OBJID = item.OBJID;
                        $this.excelJson[index].COMPANY_ID = model.COMPANYID;
                    });
 
                    // 맵다시 읽기
                    model.MAPID = MAPID;
                    model.init(); // szCurrentMode = view 모드 된다
 
                    $this.control.clickHandler.init( ed.dataView_btn ); //view 버튼이 액티브된다 (초기화)
                    $this.control.dataObjectDisplay_FEMS.start();
 
 
                    //INFO_FLAT_LIST 에 저장
                    $this.insertINFO_FLAT_LIST();
 
                }else{
                    alert("에러 ["+res.ErrMsg+"] 에러코드 ["+res.ErrCode+"]");
                }
 
                //console.log( $this.excelJson );
            }
        },
 
        insertINFO_FLAT_LIST: function(){
 
            var $this = this;
            var model = $this.model;
 
            //params
            var json = {};
            json.Record = [];
            var sendToObj = {};
 
            $this.excelJson.forEach(function(item, index, array){
 
                sendToObj = {};
                for(var key in item) {
                    if( item.hasOwnProperty(key) ) sendToObj[key] = item[key];
                }
 
                json.Record.push( sendToObj ); //json 오브젝트의 Record 배열에 sendToObj 객체를 넣는다
            });
 
            var xhrClass = new XhrClass();
            var param = "Act=InsertINFO_FLAT_LIST&rqJobData=";
            param += JSON.stringify(json); // value to JSON
 
            xhrClass.sendRequest(model.jjheo_api, param, complete, 'json''get');
            function complete(xhr, res)
            {
                // 저장에 성공하면 => 테이블 다시 조회요청한다.
                if0 == res.ErrCode ){
                    $this.elementDefined.excel_file_input.val(''); // 파일의 내용을 업로드 완료 했으므로 초기화
                    $this.sendRequestSearch();
                }else{
                    console.warn( "[ "+$this.name+" ] 에러코드:" + res.ErrCode + " / 에러메세지:"+res.ErrMsg );
                }
            }
        },
 
        sendRequestSearch: function(){
 
            var target_form = document.getElementById('SearchForm');
 
            target_form.Export.value = '0';
            target_form.json.value = '1';
            target_form.id.value = $Config['Template']; // => api/FEMS.php 파일을 호출하기 위해서
            target_form.mode.value = TableID;  // menu_2_2.html 의 php TableID (여기서는 FLAT_MAP )
 
            var url = this.model.JsonURL;
            var COMPANY_ID = this.model.COMPANYID;
            var _nGID = target_form.GID.value;
            var _nNODEID = target_form.NODEID.value;
            var SEARCH_FROM = "";
            var SEARCH_TO = "";
            var SEARCH_FROM = "";
 
            var SrchWord = target_form.SrchWord.value;
 
            var _json_refresh_url = url+'?json=1&id=FEMS&mode='+TableID+'&GID='+_nGID+'&COMPANY_ID='+COMPANY_ID+'&NODEID='+_nNODEID+'&search_from='+SEARCH_FROM+'&search_to='+SEARCH_TO+'&SrchWord='+SrchWord+'&Export=0';
 
            //console.log(_json_refresh_url);
            $('#'+TableID).load(_json_refresh_url);
 
            this.responseHeight();
        },
 
        responseHeight: function(){
            var ed = this.elementDefined;
            var display_build_list = ed.display_build_list;
 
            var screenW=$(window).width();
            var screenH=$(window).height();
 
            var gap = screenH - $("#display_build_list").offset().top - 83;
            //$(".bDiv").css('height', gap + 'px');
            //console.log(gap);
 
            //display_build_list.css('height', gap + 'px');
 
        }
 
    };
})();
 
cs



* 고용량의 엑셀파일 - 6.6MB 가 필요하다면 아래의 엑셀을 다운로드


nature08489-s3.xls


  1. excel-parser : https://www.npmjs.com/package/excel-parser [본문으로]
Posted by SAP (Study And Programming) by serpiko

댓글을 달아 주세요

  1. 비밀댓글입니다

    2017.06.07 13:37 [ ADDR : EDIT/ DEL : REPLY ]
  2. 비밀댓글입니다

    2017.09.27 01:14 [ ADDR : EDIT/ DEL : REPLY ]
    • 퍼포먼스라... 속도와 호환성중에서 둘다
      포함한다는 가정하에 글 남깁니다.

      글을 찬찬히, 천천히 읽고 아래의 프로세스대로 참고하여 보세요.

      # Live Demo를 이용하여 직접 퍼포먼스 체험하기

      1. 퍼포먼스 테스트를 위해서 고용량 Excel 파일(6.6MB)을 아래의 경로에서 다운로드 한다. ( 바이러스나 악성코드 아닙니다. )

      http://www.nature.com/nature/journal/v461/n7265/extref/nature08489-s3.xls

      2. 크롬 브라우저로
      http://oss.sheetjs.com/ 에 접속하면 "Drop a file here" 에 xlsx 파일을 드래그하여 파싱할 수 있는데 파일을 드랍다운 하기전에 미리 개발자도구(F12)를 활성화 시킨다.

      3. Performace 탭에서 ctrl + e 를 눌러
      프로세스 녹화를 시작한다.
      ( 항목은 네트워크, 프레임, GPU, CPU 쓰레드 등등.. )

      4. "Drop a file here" 에 아까 다운받은 xlsx 파일을 드랍다운한다. 프레임워크에서는 고용량에 대한 처리 딜레이 수용 알럿이 나오는데 ok 체크하고 진행한다.

      5. 처리가 완료되면 퍼포먼스 프로세스 녹화를 중지하고 분석한다.


      # sheetjs 는 pro 버전이 따로있다.

      우리가 사용하는 무료버전 말고 pro 버전이 있습니다.

      http://sheetjs.com/pro

      에서 확인할 수 있으며 특이사항은

      - 2~4배 빨라지도록 최적화

      - 국제 데이터 지원 ( 표준규격을 얘기하는것 같은데)

      - XLSX, XLSB암호화 지원

      입니다.

      # 문의및 프로버전에 대한 사항은 언제든 질문하거나 자료를 요청할 수 있다.

      http://sheetjs.com/support

      이정도면 개발하는데 충분히 자료 준비가 될것 같습니다. 클라이언트의 최대 단점은 각 개인의 PC성능의 차이로 인해 동일한 환경과 속도를 제공하지 못하는(서비스)것에 큰 단점이 있지만 DB를 준비할 필요가 없고, 실시간 처리가 가능하고, 불필요한 트랜잭션 없고, 서버에 모듈을 설치하거나 부하가 줄고(ex:고객이 10만명 단위라면?), 통신 실패가 없고 등등 ... 단점대비 많은 장점이 있습니다.

      모쪼록 잘 해결되기를 바랍니다.

      2017.09.28 00:20 신고 [ ADDR : EDIT/ DEL ]
  3. 비밀댓글입니다

    2018.03.24 10:20 [ ADDR : EDIT/ DEL : REPLY ]
    • :) 저같은 경우에 [ excel 양식을 다운받아서 =>
      사용자가 데이터 입력하고 웹에 업로드 하면 => 육안으로 데이터 확인하고 최종 컨펌 되면 => 서버측에 저장하는 용도 ]로 사용했던 기억이 있습니다. (데이터 종류는 전압, 전력, 전류 등.. 에너지 관련 프로젝트였어요) 모쪼록 잘 사용하신다니 다행입니다.

      2018.03.27 15:59 신고 [ ADDR : EDIT/ DEL ]
  4. 비밀댓글입니다

    2018.07.25 15:14 [ ADDR : EDIT/ DEL : REPLY ]
  5. 비밀댓글입니다

    2018.07.25 15:14 [ ADDR : EDIT/ DEL : REPLY ]
  6. 비밀댓글입니다

    2018.11.09 18:40 [ ADDR : EDIT/ DEL : REPLY ]
    • 네 엑셀파서에서 날짜타입이 로컬라이징되어 dd/mm/yyyy 로 출력됩니다. 원하는 날짜형식을 그대로 출력하기 위해서는 [ dateNF ] 라는 옵션을 사용해야 할 것 같습니다.

      # dateNF 란?
      dateNF : If specified, use the string for date code 14 **

      Format 14 (m/d/yy) is localized by Excel: even though the file specifies that number format, it will be drawn differently based on system settings. It makes sense when the producer and consumer of files are in the same locale, but that is not always the case over the Internet. To get around this ambiguity, parse functions accept the dateNF option to override the interpretation of that specific format string.

      # 공식문서 : https://docs.sheetjs.com/#number-formats

      # 사용예
      var workbook = XLSX.readFile(filename, {dateNF:"yyyy-mm-dd"});

      혹은

      var workbook = XLSX.read(data, {dateNF:"yyyy-mm-dd"});

      으로 사용하시면 될거같습니다

      2018.11.13 14:15 신고 [ ADDR : EDIT/ DEL ]