├── .gitignore
├── Readme.md
├── example
├── app.js
├── package.json
└── styles.xml
├── index.js
├── package.json
├── sheet.js
└── test
└── main.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /example/node_modules
2 | /node_modules
3 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # excel-export #
2 |
3 | A simple node.js module for exporting data set to Excel xlsx file.
4 |
5 | ## Using excel-export ##
6 | Setup configs object before passing it into the execute method. If generating multiple sheets, configs object can be an array of worksheet configuration. Or passing in a worksheet configuration to generate single worksheet xlsx file. Within a worksheet configuration uses **name** attribute to specify worksheet name. **cols** is an array for column definition. Column definition should have caption and type properties while width property is not required. The unit for width property is character. **beforeCellWrite** callback is optional. beforeCellWrite is invoked with row, cell data and option object (eOpt detail later) parameters. The return value from beforeCellWrite is what get written into the cell. Supported valid types are string, date, bool and number. **rows** is the data to be exported. It is an Array of Array (row). Each row should be the same length as cols. Styling is optional. However, if you want to style your spreadsheet, a valid excel styles xml file is needed. An easy way to get a styles xml file is to unzip an existing xlsx file which has the desired styles and copy out the styles.xml file. Use **stylesXmlFile** property of configuartion object to specify the relative path and file name of the xml file. Google for "spreadsheetml style" to learn more detail on styling spreadsheet. eOpt in beforeCellWrite callback contains rowNum for current row number. eOpt.styleIndex should be a valid zero based index from cellXfs tag of the selected styles xml file. eOpt.cellType is default to the type value specified in column definition. However, in some scenario you might want to change it for different format.
7 |
8 |
9 |
10 | var express = require('express');
11 | var nodeExcel = require('excel-export');
12 | var app = express();
13 |
14 | app.get('/Excel', function(req, res){
15 | var conf ={};
16 | conf.stylesXmlFile = "styles.xml";
17 | conf.name = "mysheet";
18 | conf.cols = [{
19 | caption:'string',
20 | type:'string',
21 | beforeCellWrite:function(row, cellData){
22 | return cellData.toUpperCase();
23 | },
24 | width:28.7109375
25 | },{
26 | caption:'date',
27 | type:'date',
28 | beforeCellWrite:function(){
29 | var originDate = new Date(Date.UTC(1899,11,30));
30 | return function(row, cellData, eOpt){
31 | if (eOpt.rowNum%2){
32 | eOpt.styleIndex = 1;
33 | }
34 | else{
35 | eOpt.styleIndex = 2;
36 | }
37 | if (cellData === null){
38 | eOpt.cellType = 'string';
39 | return 'N/A';
40 | } else
41 | return (cellData - originDate) / (24 * 60 * 60 * 1000);
42 | }
43 | }()
44 | },{
45 | caption:'bool',
46 | type:'bool'
47 | },{
48 | caption:'number',
49 | type:'number'
50 | }];
51 | conf.rows = [
52 | ['pi', new Date(Date.UTC(2013, 4, 1)), true, 3.14],
53 | ["e", new Date(2012, 4, 1), false, 2.7182],
54 | ["M&M<>'", new Date(Date.UTC(2013, 6, 9)), false, 1.61803],
55 | ["null date", null, true, 1.414]
56 | ];
57 | var result = nodeExcel.execute(conf);
58 | res.setHeader('Content-Type', 'application/vnd.openxmlformats');
59 | res.setHeader("Content-Disposition", "attachment; filename=" + "Report.xlsx");
60 | res.end(result, 'binary');
61 | });
62 |
63 | app.listen(3000);
64 | console.log('Listening on port 3000');
65 |
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express'),
2 | nodeExcel = require('excel-export'),
3 | uuid = require('node-uuid'),
4 | app = express();
5 |
6 | app.get('/Large', function(req, res){
7 | var conf ={};
8 | conf.cols = [];
9 | for (i = 0; i < 100; i++){
10 | conf.cols.push({
11 | caption:'string ' + i,
12 | captionStyleIndex: 1,
13 | type:'string'
14 | });
15 | }
16 | conf.rows = [];
17 | for (j = 0; j < 1000; j++){
18 | var row = [];
19 | for (k = 0; k < 100; k++){
20 | row.push(uuid.v4());
21 | }
22 | conf.rows.push(row);
23 | conf.rows.push(row);
24 | }
25 | var result = nodeExcel.execute(conf);
26 | res.setHeader('Content-Type', 'application/vnd.openxmlformats');
27 | res.setHeader("Content-Disposition", "attachment; filename=" + "Large.xlsx");
28 | res.end(result, 'binary');
29 |
30 | });
31 |
32 | app.get('/Excel', function(req, res){
33 | var conf ={};
34 | // uncomment it for style example
35 | // conf.stylesXmlFile = "styles.xml";
36 | conf.cols = [{
37 | caption:'string',
38 | captionStyleIndex: 1,
39 | type:'string',
40 | beforeCellWrite:function(row, cellData){
41 | return cellData.toUpperCase();
42 | }
43 | , width:15
44 | },{
45 | caption:'date',
46 | type:'date',
47 | beforeCellWrite:function(){
48 | var originDate = new Date(Date.UTC(1899,11,30));
49 | return function(row, cellData, eOpt){
50 | // uncomment it for style example
51 | // if (eOpt.rowNum%2){
52 | // eOpt.styleIndex = 1;
53 | // }
54 | // else{
55 | // eOpt.styleIndex = 2;
56 | // }
57 | if (cellData === null){
58 | eOpt.cellType = 'string';
59 | return 'N/A';
60 | } else
61 | return (cellData - originDate) / (24 * 60 * 60 * 1000);
62 | }
63 | }()
64 | , width:20.85
65 | },{
66 | caption:'bool',
67 | type:'bool'
68 | },{
69 | caption:'number',
70 | type:'number',
71 | width:30
72 | }];
73 | conf.rows = [
74 | ['pi', new Date(Date.UTC(2013, 4, 1)), true, 3.14159],
75 | ["e", new Date(2012, 4, 1), false, 2.7182],
76 | ["M&M<>'", new Date(Date.UTC(2013, 6, 9)), false, 1.61803],
77 | ["null date", null, true, 1.414]
78 | ];
79 | var result = nodeExcel.execute(conf);
80 | res.setHeader('Content-Type', 'application/vnd.openxmlformats');
81 | res.setHeader("Content-Disposition", "attachment; filename=" + "Report.xlsx");
82 | res.end(result, 'binary');
83 | });
84 |
85 | app.listen(3000);
86 | console.log('Listening on port 3000');
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Excel-export",
3 | "description": "Node Excel Export test app",
4 | "version": "0.3.1",
5 | "private": true,
6 | "dependencies": {
7 | "excel-export": "0.3.9",
8 | "express": "3.x",
9 | "node-uuid": "^1.4.1",
10 | "node-zip": "1.x"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('node-zip');
2 | var fs = require('fs'),
3 | Sheet = require('./sheet'),
4 | SortedMap = require('collections/sorted-map');
5 |
6 | Date.prototype.getJulian = function() {
7 | return Math.floor((this / 86400000) - (this.getTimezoneOffset() / 1440) + 2440587.5);
8 | };
9 |
10 | Date.prototype.oaDate = function() {
11 | return (this - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
12 | };
13 |
14 | var templateXLSX = "UEsDBAoAAAAAABN7eUK9Z10uNQQAADUEAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbO+7vzw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+PFR5cGVzIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvcGFja2FnZS8yMDA2L2NvbnRlbnQtdHlwZXMiPjxEZWZhdWx0IEV4dGVuc2lvbj0ieG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnNwcmVhZHNoZWV0bWwuc2hlZXQubWFpbit4bWwiIC8+PERlZmF1bHQgRXh0ZW5zaW9uPSJyZWxzIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLXBhY2thZ2UucmVsYXRpb25zaGlwcyt4bWwiIC8+PERlZmF1bHQgRXh0ZW5zaW9uPSJwc21kY3AiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtcGFja2FnZS5jb3JlLXByb3BlcnRpZXMreG1sIiAvPjxPdmVycmlkZSBQYXJ0TmFtZT0iL2RvY1Byb3BzL2FwcC54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtb2ZmaWNlZG9jdW1lbnQuZXh0ZW5kZWQtcHJvcGVydGllcyt4bWwiIC8+PE92ZXJyaWRlIFBhcnROYW1lPSIveGwvc2hhcmVkU3RyaW5ncy54bWwiIENvbnRlbnRUeXBlPSJhcHBsaWNhdGlvbi92bmQub3BlbnhtbGZvcm1hdHMtb2ZmaWNlZG9jdW1lbnQuc3ByZWFkc2hlZXRtbC5zaGFyZWRTdHJpbmdzK3htbCIgLz48T3ZlcnJpZGUgUGFydE5hbWU9Ii94bC9zdHlsZXMueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnNwcmVhZHNoZWV0bWwuc3R5bGVzK3htbCIgLz48T3ZlcnJpZGUgUGFydE5hbWU9Ii94bC93b3Jrc2hlZXRzL3NoZWV0LnhtbCIgQ29udGVudFR5cGU9ImFwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLndvcmtzaGVldCt4bWwiIC8+PE92ZXJyaWRlIFBhcnROYW1lPSIveGwvdGhlbWUvdGhlbWUueG1sIiBDb250ZW50VHlwZT0iYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LnRoZW1lK3htbCIgLz48L1R5cGVzPlBLAwQKAAAAAACLdTlIAAAAAAAAAAAAAAAABgAAAF9yZWxzL1BLAwQKAAAAAAATe3lCdJmAA5wCAACcAgAACwAAAF9yZWxzLy5yZWxz77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVsYXRpb25zaGlwcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3BhY2thZ2UvMjAwNi9yZWxhdGlvbnNoaXBzIj48UmVsYXRpb25zaGlwIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMvb2ZmaWNlRG9jdW1lbnQiIFRhcmdldD0iL3hsL3dvcmtib29rLnhtbCIgSWQ9IlI0YWEyMmIzMWExYTc0MjkxIiAvPjxSZWxhdGlvbnNoaXAgVHlwZT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcy9leHRlbmRlZC1wcm9wZXJ0aWVzIiBUYXJnZXQ9Ii9kb2NQcm9wcy9hcHAueG1sIiBJZD0icklkMSIgLz48UmVsYXRpb25zaGlwIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvcmVsYXRpb25zaGlwcy9tZXRhZGF0YS9jb3JlLXByb3BlcnRpZXMiIFRhcmdldD0iL3BhY2thZ2Uvc2VydmljZXMvbWV0YWRhdGEvY29yZS1wcm9wZXJ0aWVzL2VjZmRkMzE0M2YyMTQ4OTA5NWE0NGM3MTExNWI3MjNiLnBzbWRjcCIgSWQ9IlJlZjQ4N2MzZTBjNzQ0YTg3IiAvPjwvUmVsYXRpb25zaGlwcz5QSwMECgAAAAAAi3U5SAAAAAAAAAAAAAAAAAkAAABkb2NQcm9wcy9QSwMECgAAAAAAE3t5Qu9e3149AwAAPQMAABAAAABkb2NQcm9wcy9hcHAueG1s77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48YXA6UHJvcGVydGllcyB4bWxuczp2dD0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvZG9jUHJvcHNWVHlwZXMiIHhtbG5zOmFwPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9leHRlbmRlZC1wcm9wZXJ0aWVzIj48YXA6QXBwbGljYXRpb24+TWljcm9zb2Z0IEV4Y2VsPC9hcDpBcHBsaWNhdGlvbj48YXA6RG9jU2VjdXJpdHk+MDwvYXA6RG9jU2VjdXJpdHk+PGFwOlNjYWxlQ3JvcD5mYWxzZTwvYXA6U2NhbGVDcm9wPjxhcDpIZWFkaW5nUGFpcnM+PHZ0OnZlY3RvciBiYXNlVHlwZT0idmFyaWFudCIgc2l6ZT0iNCI+PHZ0OnZhcmlhbnQ+PHZ0Omxwc3RyPldvcmtzaGVldHM8L3Z0Omxwc3RyPjwvdnQ6dmFyaWFudD48dnQ6dmFyaWFudD48dnQ6aTQ+MTwvdnQ6aTQ+PC92dDp2YXJpYW50Pjx2dDp2YXJpYW50Pjx2dDpscHN0cj5OYW1lZCBSYW5nZXM8L3Z0Omxwc3RyPjwvdnQ6dmFyaWFudD48dnQ6dmFyaWFudD48dnQ6aTQ+MjwvdnQ6aTQ+PC92dDp2YXJpYW50PjwvdnQ6dmVjdG9yPjwvYXA6SGVhZGluZ1BhaXJzPjxhcDpUaXRsZXNPZlBhcnRzPjx2dDp2ZWN0b3IgYmFzZVR5cGU9Imxwc3RyIiBzaXplPSIzIj48dnQ6bHBzdHI+U2hlZXQgMTwvdnQ6bHBzdHI+PHZ0Omxwc3RyPlNoZWV0IDEhUHJpbnRfQXJlYTwvdnQ6bHBzdHI+PHZ0Omxwc3RyPlNoZWV0IDEhUHJpbnRfVGl0bGVzPC92dDpscHN0cj48L3Z0OnZlY3Rvcj48L2FwOlRpdGxlc09mUGFydHM+PC9hcDpQcm9wZXJ0aWVzPlBLAwQKAAAAAACLdTlIAAAAAAAAAAAAAAAACAAAAHBhY2thZ2UvUEsDBAoAAAAAAMWFeUIAAAAAAAAAAAAAAAARAAAAcGFja2FnZS9zZXJ2aWNlcy9QSwMECgAAAAAAxYV5QgAAAAAAAAAAAAAAABoAAABwYWNrYWdlL3NlcnZpY2VzL21ldGFkYXRhL1BLAwQKAAAAAADFhXlCAAAAAAAAAAAAAAAAKgAAAHBhY2thZ2Uvc2VydmljZXMvbWV0YWRhdGEvY29yZS1wcm9wZXJ0aWVzL1BLAwQKAAAAAAATe3lCc4c2yNoBAADaAQAAUQAAAHBhY2thZ2Uvc2VydmljZXMvbWV0YWRhdGEvY29yZS1wcm9wZXJ0aWVzL2VjZmRkMzE0M2YyMTQ4OTA5NWE0NGM3MTExNWI3MjNiLnBzbWRjcO+7vzw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+PGNvcmVQcm9wZXJ0aWVzIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6ZGN0ZXJtcz0iaHR0cDovL3B1cmwub3JnL2RjL3Rlcm1zLyIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9wYWNrYWdlLzIwMDYvbWV0YWRhdGEvY29yZS1wcm9wZXJ0aWVzIj48ZGN0ZXJtczpjcmVhdGVkIHhzaTp0eXBlPSJkY3Rlcm1zOlczQ0RURiI+MjAxMy0wMy0yNVQxOToyNDozOS44NjYxOTY5WjwvZGN0ZXJtczpjcmVhdGVkPjxkY3Rlcm1zOm1vZGlmaWVkIHhzaTp0eXBlPSJkY3Rlcm1zOlczQ0RURiI+MjAxMy0wMy0yNVQxOToyNDozOS44NjYxOTY5WjwvZGN0ZXJtczptb2RpZmllZD48L2NvcmVQcm9wZXJ0aWVzPlBLAwQKAAAAAACLdTlIAAAAAAAAAAAAAAAAAwAAAHhsL1BLAwQKAAAAAADFhXlCAAAAAAAAAAAAAAAACQAAAHhsL19yZWxzL1BLAwQKAAAAAAATe3lCJ0p8MrwCAAC8AgAAGgAAAHhsL19yZWxzL3dvcmtib29rLnhtbC5yZWxz77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVsYXRpb25zaGlwcyB4bWxucz0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL3BhY2thZ2UvMjAwNi9yZWxhdGlvbnNoaXBzIj48UmVsYXRpb25zaGlwIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMvc2hhcmVkU3RyaW5ncyIgVGFyZ2V0PSIveGwvc2hhcmVkU3RyaW5ncy54bWwiIElkPSJySWQzIiAvPjxSZWxhdGlvbnNoaXAgVHlwZT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcy9zdHlsZXMiIFRhcmdldD0iL3hsL3N0eWxlcy54bWwiIElkPSJySWQ0IiAvPjxSZWxhdGlvbnNoaXAgVHlwZT0iaHR0cDovL3NjaGVtYXMub3BlbnhtbGZvcm1hdHMub3JnL29mZmljZURvY3VtZW50LzIwMDYvcmVsYXRpb25zaGlwcy93b3Jrc2hlZXQiIFRhcmdldD0iL3hsL3dvcmtzaGVldHMvc2hlZXQueG1sIiBJZD0icklkMiIgLz48UmVsYXRpb25zaGlwIFR5cGU9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9vZmZpY2VEb2N1bWVudC8yMDA2L3JlbGF0aW9uc2hpcHMvdGhlbWUiIFRhcmdldD0iL3hsL3RoZW1lL3RoZW1lLnhtbCIgSWQ9InJJZDYiIC8+PC9SZWxhdGlvbnNoaXBzPlBLAwQKAAAAAAATe3lCflKRBZAAAACQAAAAFAAAAHhsL3NoYXJlZFN0cmluZ3MueG1s77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48eDpzc3QgY291bnQ9IjAiIHVuaXF1ZUNvdW50PSIwIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iIC8+UEsDBAoAAAAAABN7eUIi2lsreggAAHoIAAANAAAAeGwvc3R5bGVzLnhtbO+7vzw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+PHg6c3R5bGVTaGVldCB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iPjx4Om51bUZtdHMgY291bnQ9IjEiPjx4Om51bUZtdCBudW1GbXRJZD0iMCIgZm9ybWF0Q29kZT0iIiAvPjwveDpudW1GbXRzPjx4OmZvbnRzIGNvdW50PSIxIj48eDpmb250Pjx4OnZlcnRBbGlnbiB2YWw9ImJhc2VsaW5lIiAvPjx4OnN6IHZhbD0iMTEiIC8+PHg6Y29sb3IgcmdiPSJGRjAwMDAwMCIgLz48eDpuYW1lIHZhbD0iQ2FsaWJyaSIgLz48eDpmYW1pbHkgdmFsPSIyIiAvPjwveDpmb250PjwveDpmb250cz48eDpmaWxscyBjb3VudD0iMiI+PHg6ZmlsbD48eDpwYXR0ZXJuRmlsbCBwYXR0ZXJuVHlwZT0ibm9uZSIgLz48L3g6ZmlsbD48eDpmaWxsPjx4OnBhdHRlcm5GaWxsIHBhdHRlcm5UeXBlPSJncmF5MTI1IiAvPjwveDpmaWxsPjwveDpmaWxscz48eDpib3JkZXJzIGNvdW50PSIxIj48eDpib3JkZXIgZGlhZ29uYWxVcD0iMCIgZGlhZ29uYWxEb3duPSIwIj48eDpsZWZ0IHN0eWxlPSJub25lIj48eDpjb2xvciByZ2I9IkZGMDAwMDAwIiAvPjwveDpsZWZ0Pjx4OnJpZ2h0IHN0eWxlPSJub25lIj48eDpjb2xvciByZ2I9IkZGMDAwMDAwIiAvPjwveDpyaWdodD48eDp0b3Agc3R5bGU9Im5vbmUiPjx4OmNvbG9yIHJnYj0iRkYwMDAwMDAiIC8+PC94OnRvcD48eDpib3R0b20gc3R5bGU9Im5vbmUiPjx4OmNvbG9yIHJnYj0iRkYwMDAwMDAiIC8+PC94OmJvdHRvbT48eDpkaWFnb25hbCBzdHlsZT0ibm9uZSI+PHg6Y29sb3IgcmdiPSJGRjAwMDAwMCIgLz48L3g6ZGlhZ29uYWw+PC94OmJvcmRlcj48L3g6Ym9yZGVycz48eDpjZWxsU3R5bGVYZnMgY291bnQ9IjIiPjx4OnhmIG51bUZtdElkPSIwIiBmb250SWQ9IjAiIGZpbGxJZD0iMCIgYm9yZGVySWQ9IjAiIGFwcGx5TnVtYmVyRm9ybWF0PSIwIiBhcHBseUZpbGw9IjEiIGFwcGx5Qm9yZGVyPSIwIiBhcHBseUFsaWdubWVudD0iMCIgYXBwbHlQcm90ZWN0aW9uPSIxIj48eDpwcm90ZWN0aW9uIGxvY2tlZD0iMSIgaGlkZGVuPSIwIiAvPjwveDp4Zj48eDp4ZiBudW1GbXRJZD0iMTQiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIgYXBwbHlOdW1iZXJGb3JtYXQ9IjAiIGFwcGx5RmlsbD0iMSIgYXBwbHlCb3JkZXI9IjAiIGFwcGx5QWxpZ25tZW50PSIwIiBhcHBseVByb3RlY3Rpb249IjEiPjx4OnByb3RlY3Rpb24gbG9ja2VkPSIxIiBoaWRkZW49IjAiIC8+PC94OnhmPjwveDpjZWxsU3R5bGVYZnM+PHg6Y2VsbFhmcyBjb3VudD0iMiI+PHg6eGYgbnVtRm10SWQ9IjAiIGZvbnRJZD0iMCIgZmlsbElkPSIwIiBib3JkZXJJZD0iMCIgeGZJZD0iMCIgYXBwbHlOdW1iZXJGb3JtYXQ9IjAiIGFwcGx5RmlsbD0iMSIgYXBwbHlCb3JkZXI9IjAiIGFwcGx5QWxpZ25tZW50PSIwIiBhcHBseVByb3RlY3Rpb249IjEiPjx4OmFsaWdubWVudCBob3Jpem9udGFsPSJnZW5lcmFsIiB2ZXJ0aWNhbD0iYm90dG9tIiB0ZXh0Um90YXRpb249IjAiIHdyYXBUZXh0PSIwIiBpbmRlbnQ9IjAiIHJlbGF0aXZlSW5kZW50PSIwIiBqdXN0aWZ5TGFzdExpbmU9IjAiIHNocmlua1RvRml0PSIwIiByZWFkaW5nT3JkZXI9IjAiIC8+PHg6cHJvdGVjdGlvbiBsb2NrZWQ9IjEiIGhpZGRlbj0iMCIgLz48L3g6eGY+PHg6eGYgbnVtRm10SWQ9IjE0IiBmb250SWQ9IjAiIGZpbGxJZD0iMCIgYm9yZGVySWQ9IjAiIHhmSWQ9IjAiIGFwcGx5TnVtYmVyRm9ybWF0PSIwIiBhcHBseUZpbGw9IjEiIGFwcGx5Qm9yZGVyPSIwIiBhcHBseUFsaWdubWVudD0iMCIgYXBwbHlQcm90ZWN0aW9uPSIxIj48eDphbGlnbm1lbnQgaG9yaXpvbnRhbD0iZ2VuZXJhbCIgdmVydGljYWw9ImJvdHRvbSIgdGV4dFJvdGF0aW9uPSIwIiB3cmFwVGV4dD0iMCIgaW5kZW50PSIwIiByZWxhdGl2ZUluZGVudD0iMCIganVzdGlmeUxhc3RMaW5lPSIwIiBzaHJpbmtUb0ZpdD0iMCIgcmVhZGluZ09yZGVyPSIwIiAvPjx4OnByb3RlY3Rpb24gbG9ja2VkPSIxIiBoaWRkZW49IjAiIC8+PC94OnhmPjwveDpjZWxsWGZzPjx4OmNlbGxTdHlsZXMgY291bnQ9IjEiPjx4OmNlbGxTdHlsZSBuYW1lPSJOb3JtYWwiIHhmSWQ9IjAiIGJ1aWx0aW5JZD0iMCIgLz48L3g6Y2VsbFN0eWxlcz48L3g6c3R5bGVTaGVldD5QSwMECgAAAAAAxYV5QgAAAAAAAAAAAAAAAAkAAAB4bC90aGVtZS9QSwMECgAAAAAAE3t5QnWxkV67GwAAuxsAABIAAAB4bC90aGVtZS90aGVtZS54bWzvu788P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtOCI/PjxhOnRoZW1lIHhtbG5zOmE9Imh0dHA6Ly9zY2hlbWFzLm9wZW54bWxmb3JtYXRzLm9yZy9kcmF3aW5nbWwvMjAwNi9tYWluIiBuYW1lPSJPZmZpY2UgVGhlbWUiPjxhOnRoZW1lRWxlbWVudHM+PGE6Y2xyU2NoZW1lIG5hbWU9Ik9mZmljZSI+PGE6ZGsxPjxhOnN5c0NsciB2YWw9IndpbmRvd1RleHQiIGxhc3RDbHI9IjAwMDAwMCIgLz48L2E6ZGsxPjxhOmx0MT48YTpzeXNDbHIgdmFsPSJ3aW5kb3ciIGxhc3RDbHI9IkZGRkZGRiIgLz48L2E6bHQxPjxhOmRrMj48YTpzcmdiQ2xyIHZhbD0iMUY0OTdEIiAvPjwvYTpkazI+PGE6bHQyPjxhOnNyZ2JDbHIgdmFsPSJFRUVDRTEiIC8+PC9hOmx0Mj48YTphY2NlbnQxPjxhOnNyZ2JDbHIgdmFsPSI0RjgxQkQiIC8+PC9hOmFjY2VudDE+PGE6YWNjZW50Mj48YTpzcmdiQ2xyIHZhbD0iQzA1MDREIiAvPjwvYTphY2NlbnQyPjxhOmFjY2VudDM+PGE6c3JnYkNsciB2YWw9IjlCQkI1OSIgLz48L2E6YWNjZW50Mz48YTphY2NlbnQ0PjxhOnNyZ2JDbHIgdmFsPSI4MDY0QTIiIC8+PC9hOmFjY2VudDQ+PGE6YWNjZW50NT48YTpzcmdiQ2xyIHZhbD0iNEJBQ0M2IiAvPjwvYTphY2NlbnQ1PjxhOmFjY2VudDY+PGE6c3JnYkNsciB2YWw9IkY3OTY0NiIgLz48L2E6YWNjZW50Nj48YTpobGluaz48YTpzcmdiQ2xyIHZhbD0iMDAwMEZGIiAvPjwvYTpobGluaz48YTpmb2xIbGluaz48YTpzcmdiQ2xyIHZhbD0iODAwMDgwIiAvPjwvYTpmb2xIbGluaz48L2E6Y2xyU2NoZW1lPjxhOmZvbnRTY2hlbWUgbmFtZT0iT2ZmaWNlIj48YTptYWpvckZvbnQ+PGE6bGF0aW4gdHlwZWZhY2U9IkNhbWJyaWEiIC8+PGE6ZWEgdHlwZWZhY2U9IiIgLz48YTpjcyB0eXBlZmFjZT0iIiAvPjxhOmZvbnQgc2NyaXB0PSJKcGFuIiB0eXBlZmFjZT0i77yt77yzIO+8sOOCtOOCt+ODg+OCryIgLz48YTpmb250IHNjcmlwdD0iSGFuZyIgdHlwZWZhY2U9IuunkeydgCDqs6DrlJUiIC8+PGE6Zm9udCBzY3JpcHQ9IkhhbnMiIHR5cGVmYWNlPSLlrovkvZMiIC8+PGE6Zm9udCBzY3JpcHQ9IkhhbnQiIHR5cGVmYWNlPSLmlrDntLDmmI7pq5QiIC8+PGE6Zm9udCBzY3JpcHQ9IkFyYWIiIHR5cGVmYWNlPSJUaW1lcyBOZXcgUm9tYW4iIC8+PGE6Zm9udCBzY3JpcHQ9IkhlYnIiIHR5cGVmYWNlPSJUaW1lcyBOZXcgUm9tYW4iIC8+PGE6Zm9udCBzY3JpcHQ9IlRoYWkiIHR5cGVmYWNlPSJUYWhvbWEiIC8+PGE6Zm9udCBzY3JpcHQ9IkV0aGkiIHR5cGVmYWNlPSJOeWFsYSIgLz48YTpmb250IHNjcmlwdD0iQmVuZyIgdHlwZWZhY2U9IlZyaW5kYSIgLz48YTpmb250IHNjcmlwdD0iR3VqciIgdHlwZWZhY2U9IlNocnV0aSIgLz48YTpmb250IHNjcmlwdD0iS2htciIgdHlwZWZhY2U9Ik1vb2xCb3JhbiIgLz48YTpmb250IHNjcmlwdD0iS25kYSIgdHlwZWZhY2U9IlR1bmdhIiAvPjxhOmZvbnQgc2NyaXB0PSJHdXJ1IiB0eXBlZmFjZT0iUmFhdmkiIC8+PGE6Zm9udCBzY3JpcHQ9IkNhbnMiIHR5cGVmYWNlPSJFdXBoZW1pYSIgLz48YTpmb250IHNjcmlwdD0iQ2hlciIgdHlwZWZhY2U9IlBsYW50YWdlbmV0IENoZXJva2VlIiAvPjxhOmZvbnQgc2NyaXB0PSJZaWlpIiB0eXBlZmFjZT0iTWljcm9zb2Z0IFlpIEJhaXRpIiAvPjxhOmZvbnQgc2NyaXB0PSJUaWJ0IiB0eXBlZmFjZT0iTWljcm9zb2Z0IEhpbWFsYXlhIiAvPjxhOmZvbnQgc2NyaXB0PSJUaGFhIiB0eXBlZmFjZT0iTVYgQm9saSIgLz48YTpmb250IHNjcmlwdD0iRGV2YSIgdHlwZWZhY2U9Ik1hbmdhbCIgLz48YTpmb250IHNjcmlwdD0iVGVsdSIgdHlwZWZhY2U9IkdhdXRhbWkiIC8+PGE6Zm9udCBzY3JpcHQ9IlRhbWwiIHR5cGVmYWNlPSJMYXRoYSIgLz48YTpmb250IHNjcmlwdD0iU3lyYyIgdHlwZWZhY2U9IkVzdHJhbmdlbG8gRWRlc3NhIiAvPjxhOmZvbnQgc2NyaXB0PSJPcnlhIiB0eXBlZmFjZT0iS2FsaW5nYSIgLz48YTpmb250IHNjcmlwdD0iTWx5bSIgdHlwZWZhY2U9IkthcnRpa2EiIC8+PGE6Zm9udCBzY3JpcHQ9Ikxhb28iIHR5cGVmYWNlPSJEb2tDaGFtcGEiIC8+PGE6Zm9udCBzY3JpcHQ9IlNpbmgiIHR5cGVmYWNlPSJJc2tvb2xhIFBvdGEiIC8+PGE6Zm9udCBzY3JpcHQ9Ik1vbmciIHR5cGVmYWNlPSJNb25nb2xpYW4gQmFpdGkiIC8+PGE6Zm9udCBzY3JpcHQ9IlZpZXQiIHR5cGVmYWNlPSJUaW1lcyBOZXcgUm9tYW4iIC8+PGE6Zm9udCBzY3JpcHQ9IlVpZ2giIHR5cGVmYWNlPSJNaWNyb3NvZnQgVWlnaHVyIiAvPjwvYTptYWpvckZvbnQ+PGE6bWlub3JGb250PjxhOmxhdGluIHR5cGVmYWNlPSJDYWxpYnJpIiAvPjxhOmVhIHR5cGVmYWNlPSIiIC8+PGE6Y3MgdHlwZWZhY2U9IiIgLz48YTpmb250IHNjcmlwdD0iSnBhbiIgdHlwZWZhY2U9Iu+8re+8syDvvLDjgrTjgrfjg4Pjgq8iIC8+PGE6Zm9udCBzY3JpcHQ9IkhhbmciIHR5cGVmYWNlPSLrp5HsnYAg6rOg65SVIiAvPjxhOmZvbnQgc2NyaXB0PSJIYW5zIiB0eXBlZmFjZT0i5a6L5L2TIiAvPjxhOmZvbnQgc2NyaXB0PSJIYW50IiB0eXBlZmFjZT0i5paw57Sw5piO6auUIiAvPjxhOmZvbnQgc2NyaXB0PSJBcmFiIiB0eXBlZmFjZT0iQXJpYWwiIC8+PGE6Zm9udCBzY3JpcHQ9IkhlYnIiIHR5cGVmYWNlPSJBcmlhbCIgLz48YTpmb250IHNjcmlwdD0iVGhhaSIgdHlwZWZhY2U9IlRhaG9tYSIgLz48YTpmb250IHNjcmlwdD0iRXRoaSIgdHlwZWZhY2U9Ik55YWxhIiAvPjxhOmZvbnQgc2NyaXB0PSJCZW5nIiB0eXBlZmFjZT0iVnJpbmRhIiAvPjxhOmZvbnQgc2NyaXB0PSJHdWpyIiB0eXBlZmFjZT0iU2hydXRpIiAvPjxhOmZvbnQgc2NyaXB0PSJLaG1yIiB0eXBlZmFjZT0iRGF1blBlbmgiIC8+PGE6Zm9udCBzY3JpcHQ9IktuZGEiIHR5cGVmYWNlPSJUdW5nYSIgLz48YTpmb250IHNjcmlwdD0iR3VydSIgdHlwZWZhY2U9IlJhYXZpIiAvPjxhOmZvbnQgc2NyaXB0PSJDYW5zIiB0eXBlZmFjZT0iRXVwaGVtaWEiIC8+PGE6Zm9udCBzY3JpcHQ9IkNoZXIiIHR5cGVmYWNlPSJQbGFudGFnZW5ldCBDaGVyb2tlZSIgLz48YTpmb250IHNjcmlwdD0iWWlpaSIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBZaSBCYWl0aSIgLz48YTpmb250IHNjcmlwdD0iVGlidCIgdHlwZWZhY2U9Ik1pY3Jvc29mdCBIaW1hbGF5YSIgLz48YTpmb250IHNjcmlwdD0iVGhhYSIgdHlwZWZhY2U9Ik1WIEJvbGkiIC8+PGE6Zm9udCBzY3JpcHQ9IkRldmEiIHR5cGVmYWNlPSJNYW5nYWwiIC8+PGE6Zm9udCBzY3JpcHQ9IlRlbHUiIHR5cGVmYWNlPSJHYXV0YW1pIiAvPjxhOmZvbnQgc2NyaXB0PSJUYW1sIiB0eXBlZmFjZT0iTGF0aGEiIC8+PGE6Zm9udCBzY3JpcHQ9IlN5cmMiIHR5cGVmYWNlPSJFc3RyYW5nZWxvIEVkZXNzYSIgLz48YTpmb250IHNjcmlwdD0iT3J5YSIgdHlwZWZhY2U9IkthbGluZ2EiIC8+PGE6Zm9udCBzY3JpcHQ9Ik1seW0iIHR5cGVmYWNlPSJLYXJ0aWthIiAvPjxhOmZvbnQgc2NyaXB0PSJMYW9vIiB0eXBlZmFjZT0iRG9rQ2hhbXBhIiAvPjxhOmZvbnQgc2NyaXB0PSJTaW5oIiB0eXBlZmFjZT0iSXNrb29sYSBQb3RhIiAvPjxhOmZvbnQgc2NyaXB0PSJNb25nIiB0eXBlZmFjZT0iTW9uZ29saWFuIEJhaXRpIiAvPjxhOmZvbnQgc2NyaXB0PSJWaWV0IiB0eXBlZmFjZT0iQXJpYWwiIC8+PGE6Zm9udCBzY3JpcHQ9IlVpZ2giIHR5cGVmYWNlPSJNaWNyb3NvZnQgVWlnaHVyIiAvPjwvYTptaW5vckZvbnQ+PC9hOmZvbnRTY2hlbWU+PGE6Zm10U2NoZW1lIG5hbWU9Ik9mZmljZSI+PGE6ZmlsbFN0eWxlTHN0PjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciIgLz48L2E6c29saWRGaWxsPjxhOmdyYWRGaWxsIHJvdFdpdGhTaGFwZT0iMSI+PGE6Z3NMc3Q+PGE6Z3MgcG9zPSIwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjUwMDAwIiAvPjxhOnNhdE1vZCB2YWw9IjMwMDAwMCIgLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjM1MDAwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6dGludCB2YWw9IjM3MDAwIiAvPjxhOnNhdE1vZCB2YWw9IjMwMDAwMCIgLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSIxNTAwMCIgLz48YTpzYXRNb2QgdmFsPSIzNTAwMDAiIC8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjEiIC8+PC9hOmdyYWRGaWxsPjxhOmdyYWRGaWxsIHJvdFdpdGhTaGFwZT0iMSI+PGE6Z3NMc3Q+PGE6Z3MgcG9zPSIwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6c2hhZGUgdmFsPSI1MTAwMCIgLz48YTpzYXRNb2QgdmFsPSIxMzAwMDAiIC8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSI4MDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnNoYWRlIHZhbD0iOTMwMDAiIC8+PGE6c2F0TW9kIHZhbD0iMTMwMDAwIiAvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjxhOmdzIHBvcz0iMTAwMDAwIj48YTpzY2hlbWVDbHIgdmFsPSJwaENsciI+PGE6c2hhZGUgdmFsPSI5NDAwMCIgLz48YTpzYXRNb2QgdmFsPSIxMzUwMDAiIC8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PC9hOmdzTHN0PjxhOmxpbiBhbmc9IjE2MjAwMDAwIiBzY2FsZWQ9IjAiIC8+PC9hOmdyYWRGaWxsPjwvYTpmaWxsU3R5bGVMc3Q+PGE6bG5TdHlsZUxzdD48YTpsbiB3PSI5NTI1IiBjYXA9ImZsYXQiIGNtcGQ9InNuZyIgYWxnbj0iY3RyIj48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnNoYWRlIHZhbD0iOTUwMDAiIC8+PGE6c2F0TW9kIHZhbD0iMTA1MDAwIiAvPjwvYTpzY2hlbWVDbHI+PC9hOnNvbGlkRmlsbD48YTpwcnN0RGFzaCB2YWw9InNvbGlkIiAvPjwvYTpsbj48YTpsbiB3PSIyNTQwMCIgY2FwPSJmbGF0IiBjbXBkPSJzbmciIGFsZ249ImN0ciI+PGE6c29saWRGaWxsPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIiAvPjwvYTpzb2xpZEZpbGw+PGE6cHJzdERhc2ggdmFsPSJzb2xpZCIgLz48L2E6bG4+PGE6bG4gdz0iMzgxMDAiIGNhcD0iZmxhdCIgY21wZD0ic25nIiBhbGduPSJjdHIiPjxhOnNvbGlkRmlsbD48YTpzY2hlbWVDbHIgdmFsPSJwaENsciIgLz48L2E6c29saWRGaWxsPjxhOnByc3REYXNoIHZhbD0ic29saWQiIC8+PC9hOmxuPjwvYTpsblN0eWxlTHN0PjxhOmVmZmVjdFN0eWxlTHN0PjxhOmVmZmVjdFN0eWxlPjxhOmVmZmVjdExzdD48YTpvdXRlclNoZHcgYmx1clJhZD0iNDAwMDAiIGRpc3Q9IjIwMDAwIiBkaXI9IjU0MDAwMDAiIHJvdFdpdGhTaGFwZT0iMCI+PGE6c3JnYkNsciB2YWw9IjAwMDAwMCI+PGE6YWxwaGEgdmFsPSIzODAwMCIgLz48L2E6c3JnYkNscj48L2E6b3V0ZXJTaGR3PjwvYTplZmZlY3RMc3Q+PC9hOmVmZmVjdFN0eWxlPjxhOmVmZmVjdFN0eWxlPjxhOmVmZmVjdExzdD48YTpvdXRlclNoZHcgYmx1clJhZD0iNDAwMDAiIGRpc3Q9IjIzMDAwIiBkaXI9IjU0MDAwMDAiIHJvdFdpdGhTaGFwZT0iMCI+PGE6c3JnYkNsciB2YWw9IjAwMDAwMCI+PGE6YWxwaGEgdmFsPSIzNTAwMCIgLz48L2E6c3JnYkNscj48L2E6b3V0ZXJTaGR3PjwvYTplZmZlY3RMc3Q+PC9hOmVmZmVjdFN0eWxlPjxhOmVmZmVjdFN0eWxlPjxhOmVmZmVjdExzdD48YTpvdXRlclNoZHcgYmx1clJhZD0iNDAwMDAiIGRpc3Q9IjIzMDAwIiBkaXI9IjU0MDAwMDAiIHJvdFdpdGhTaGFwZT0iMCI+PGE6c3JnYkNsciB2YWw9IjAwMDAwMCI+PGE6YWxwaGEgdmFsPSIzNTAwMCIgLz48L2E6c3JnYkNscj48L2E6b3V0ZXJTaGR3PjwvYTplZmZlY3RMc3Q+PGE6c2NlbmUzZD48YTpjYW1lcmEgcHJzdD0ib3J0aG9ncmFwaGljRnJvbnQiPjxhOnJvdCBsYXQ9IjAiIGxvbj0iMCIgcmV2PSIwIiAvPjwvYTpjYW1lcmE+PGE6bGlnaHRSaWcgcmlnPSJ0aHJlZVB0IiBkaXI9InQiPjxhOnJvdCBsYXQ9IjAiIGxvbj0iMCIgcmV2PSIxMjAwMDAwIiAvPjwvYTpsaWdodFJpZz48L2E6c2NlbmUzZD48YTpzcDNkPjxhOmJldmVsVCB3PSI2MzUwMCIgaD0iMjU0MDAiIC8+PC9hOnNwM2Q+PC9hOmVmZmVjdFN0eWxlPjwvYTplZmZlY3RTdHlsZUxzdD48YTpiZ0ZpbGxTdHlsZUxzdD48YTpzb2xpZEZpbGw+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiIC8+PC9hOnNvbGlkRmlsbD48YTpncmFkRmlsbCByb3RXaXRoU2hhcGU9IjEiPjxhOmdzTHN0PjxhOmdzIHBvcz0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI0MDAwMCIgLz48YTpzYXRNb2QgdmFsPSIzNTAwMDAiIC8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSI0MDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI0NTAwMCIgLz48YTpzaGFkZSB2YWw9Ijk5MDAwIiAvPjxhOnNhdE1vZCB2YWw9IjM1MDAwMCIgLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48YTpncyBwb3M9IjEwMDAwMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnNoYWRlIHZhbD0iMjAwMDAiIC8+PGE6c2F0TW9kIHZhbD0iMjU1MDAwIiAvPjwvYTpzY2hlbWVDbHI+PC9hOmdzPjwvYTpnc0xzdD48YTpwYXRoIHBhdGg9ImNpcmNsZSI+PGE6ZmlsbFRvUmVjdCBsPSI1MDAwMCIgdD0iLTgwMDAwIiByPSI1MDAwMCIgYj0iMTgwMDAwIiAvPjwvYTpwYXRoPjwvYTpncmFkRmlsbD48YTpncmFkRmlsbCByb3RXaXRoU2hhcGU9IjEiPjxhOmdzTHN0PjxhOmdzIHBvcz0iMCI+PGE6c2NoZW1lQ2xyIHZhbD0icGhDbHIiPjxhOnRpbnQgdmFsPSI4MDAwMCIgLz48YTpzYXRNb2QgdmFsPSIzMDAwMDAiIC8+PC9hOnNjaGVtZUNscj48L2E6Z3M+PGE6Z3MgcG9zPSIxMDAwMDAiPjxhOnNjaGVtZUNsciB2YWw9InBoQ2xyIj48YTpzaGFkZSB2YWw9IjMwMDAwIiAvPjxhOnNhdE1vZCB2YWw9IjIwMDAwMCIgLz48L2E6c2NoZW1lQ2xyPjwvYTpncz48L2E6Z3NMc3Q+PGE6cGF0aCBwYXRoPSJjaXJjbGUiPjxhOmZpbGxUb1JlY3QgbD0iNTAwMDAiIHQ9IjUwMDAwIiByPSI1MDAwMCIgYj0iNTAwMDAiIC8+PC9hOnBhdGg+PC9hOmdyYWRGaWxsPjwvYTpiZ0ZpbGxTdHlsZUxzdD48L2E6Zm10U2NoZW1lPjwvYTp0aGVtZUVsZW1lbnRzPjxhOm9iamVjdERlZmF1bHRzIC8+PGE6ZXh0cmFDbHJTY2hlbWVMc3QgLz48L2E6dGhlbWU+UEsDBAoAAAAAABN7eUKJ3nBGuwEAALsBAAAPAAAAeGwvd29ya2Jvb2sueG1s77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48eDp3b3JrYm9vayB4bWxuczpyPSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvb2ZmaWNlRG9jdW1lbnQvMjAwNi9yZWxhdGlvbnNoaXBzIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5vcGVueG1sZm9ybWF0cy5vcmcvc3ByZWFkc2hlZXRtbC8yMDA2L21haW4iPjx4Ondvcmtib29rUHIgY29kZU5hbWU9IlRoaXNXb3JrYm9vayIgLz48eDpib29rVmlld3M+PHg6d29ya2Jvb2tWaWV3IGZpcnN0U2hlZXQ9IjAiIGFjdGl2ZVRhYj0iMCIgLz48L3g6Ym9va1ZpZXdzPjx4OnNoZWV0cz48eDpzaGVldCBuYW1lPSJTaGVldCAxIiBzaGVldElkPSIyIiByOmlkPSJySWQyIiAvPjwveDpzaGVldHM+PHg6ZGVmaW5lZE5hbWVzIC8+PHg6Y2FsY1ByIGNhbGNJZD0iMTI1NzI1IiAvPjwveDp3b3JrYm9vaz5QSwMECgAAAAAAxYV5QgAAAAAAAAAAAAAAAA4AAAB4bC93b3Jrc2hlZXRzL1BLAQIUAAoAAAAAABN7eUK9Z10uNQQAADUEAAATAAAAAAAAAAAAAAAAAAAAAABbQ29udGVudF9UeXBlc10ueG1sUEsBAhQACgAAAAAAi3U5SAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAQAAAAZgQAAF9yZWxzL1BLAQIUAAoAAAAAABN7eUJ0mYADnAIAAJwCAAALAAAAAAAAAAAAAAAAAIoEAABfcmVscy8ucmVsc1BLAQIUAAoAAAAAAIt1OUgAAAAAAAAAAAAAAAAJAAAAAAAAAAAAEAAAAE8HAABkb2NQcm9wcy9QSwECFAAKAAAAAAATe3lC717fXj0DAAA9AwAAEAAAAAAAAAAAAAAAAAB2BwAAZG9jUHJvcHMvYXBwLnhtbFBLAQIUAAoAAAAAAIt1OUgAAAAAAAAAAAAAAAAIAAAAAAAAAAAAEAAAAOEKAABwYWNrYWdlL1BLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAARAAAAAAAAAAAAEAAAAAcLAABwYWNrYWdlL3NlcnZpY2VzL1BLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAAaAAAAAAAAAAAAEAAAADYLAABwYWNrYWdlL3NlcnZpY2VzL21ldGFkYXRhL1BLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAAqAAAAAAAAAAAAEAAAAG4LAABwYWNrYWdlL3NlcnZpY2VzL21ldGFkYXRhL2NvcmUtcHJvcGVydGllcy9QSwECFAAKAAAAAAATe3lCc4c2yNoBAADaAQAAUQAAAAAAAAAAAAAAAAC2CwAAcGFja2FnZS9zZXJ2aWNlcy9tZXRhZGF0YS9jb3JlLXByb3BlcnRpZXMvZWNmZGQzMTQzZjIxNDg5MDk1YTQ0YzcxMTE1YjcyM2IucHNtZGNwUEsBAhQACgAAAAAAi3U5SAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAQAAAA/w0AAHhsL1BLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAAJAAAAAAAAAAAAEAAAACAOAAB4bC9fcmVscy9QSwECFAAKAAAAAAATe3lCJ0p8MrwCAAC8AgAAGgAAAAAAAAAAAAAAAABHDgAAeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHNQSwECFAAKAAAAAAATe3lCflKRBZAAAACQAAAAFAAAAAAAAAAAAAAAAAA7EQAAeGwvc2hhcmVkU3RyaW5ncy54bWxQSwECFAAKAAAAAAATe3lCItpbK3oIAAB6CAAADQAAAAAAAAAAAAAAAAD9EQAAeGwvc3R5bGVzLnhtbFBLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAAJAAAAAAAAAAAAEAAAAKIaAAB4bC90aGVtZS9QSwECFAAKAAAAAAATe3lCdbGRXrsbAAC7GwAAEgAAAAAAAAAAAAAAAADJGgAAeGwvdGhlbWUvdGhlbWUueG1sUEsBAhQACgAAAAAAE3t5QonecEa7AQAAuwEAAA8AAAAAAAAAAAAAAAAAtDYAAHhsL3dvcmtib29rLnhtbFBLAQIUAAoAAAAAAMWFeUIAAAAAAAAAAAAAAAAOAAAAAAAAAAAAEAAAAJw4AAB4bC93b3Jrc2hlZXRzL1BLBQYAAAAAEwATANQEAADIOAAAAAA=";
15 | var sheetsFront = ''
16 | + ''
17 | + '';
18 | var sheetsBack = '';
19 |
20 | var relFront = '';
21 |
22 | var relBack = '';
23 | var contentTypeFront = ''
24 | + ''
25 | + ''
26 | + ''
27 | + ''
28 | + ''
29 | + '';
30 | var contentTypeBack = '';
31 | var sharedStringsFront = '';
32 | var sharedStringsBack = '';
33 | var shareStrings, convertedShareStrings;
34 |
35 | function generateMultiSheets(configs, xlsx) {
36 | var i = 1;
37 | configs.forEach(function(config) {
38 | config.name = config.name ? config.name : ('sheet'+i);
39 | i++;
40 | var sheet = new Sheet(config, xlsx, shareStrings, convertedShareStrings);
41 | sheet.generate();
42 | convertedShareStrings = sheet.convertedShareStrings
43 | });
44 | }
45 |
46 | function generateContentType(configs, xlsx) {
47 | var workbook = contentTypeFront;
48 | configs.forEach( function(config) {
49 | workbook += '';
50 | });
51 | workbook += contentTypeBack;
52 | xlsx.file('[Content_Types].xml', workbook);
53 | }
54 |
55 | function generateRel(configs,xlsx) {
56 | var workbook = relFront;
57 | var i = 1;
58 | configs.forEach( function(config) {
59 | workbook += '';
60 | i++;
61 | });
62 | workbook += relBack;
63 | xlsx.file('xl/_rels/workbook.xml.rels', workbook);
64 | xlsx.file('_rels/.rels', ''
65 | + ''
66 | + ''
67 | + '');
68 | }
69 |
70 | function generateWorkbook(configs,xlsx) {
71 | var workbook = sheetsFront;
72 | var i = 1;
73 | configs.forEach( function(config) {
74 | workbook += '';
75 | i++;
76 | });
77 | workbook += sheetsBack;
78 | xlsx.file('xl/workbook.xml', workbook);
79 | }
80 |
81 | function generateSharedStringsFile(xlsx){
82 | if (shareStrings.length > 0) {
83 | var sharedStringsFrontTmp = sharedStringsFront.replace(/\$count/g, shareStrings.length);
84 | xlsx.file("xl/sharedStrings.xml", (sharedStringsFrontTmp + convertedShareStrings + sharedStringsBack));
85 | }
86 | convertedShareStrings = "";
87 | }
88 |
89 | exports.executeAsync = function(config, callBack) {
90 | return process.nextTick(function() {
91 | var r = exports.execute(config);
92 | callBack(r);
93 | });
94 | };
95 |
96 | exports.execute = function(config) {
97 | var xlsx = new JSZip(templateXLSX, {
98 | base64: true,
99 | checkCRC32: false
100 | });
101 | shareStrings = new SortedMap();
102 | convertedShareStrings = "";
103 |
104 | var configs = [];
105 | if (config instanceof Array) {
106 | configs = config;
107 | }else{
108 | configs.push(config);
109 | }
110 | generateMultiSheets(configs, xlsx);
111 | generateWorkbook(configs, xlsx);
112 | generateRel(configs,xlsx) ;
113 | generateContentType(configs, xlsx);
114 | generateSharedStringsFile(xlsx);
115 | var results = xlsx.generate({
116 | base64: false,
117 | compression: "DEFLATE"
118 | });
119 | delete shareStrings;
120 | delete xlsx;
121 | return results;
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "excel-export",
3 | "version": "0.5.1",
4 | "description": "Simple data set export to Excel xlsx file",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha test/main"
8 | },
9 | "repository": "https://github.com/functionscope/Node-Excel-Export",
10 | "keywords": [
11 | "Excel",
12 | "xlsx"
13 | ],
14 | "author": "Ber-Lin Lai ",
15 | "license": "BSD",
16 | "dependencies": {
17 | "collections": "^3.0.0",
18 | "node-zip": "1.x"
19 | },
20 | "devDependencies": {
21 | "mocha": "",
22 | "should": ""
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/sheet.js:
--------------------------------------------------------------------------------
1 | var sheetFront = ''
2 | + ' '
3 | + ' ';
4 | var sheetBack =' '
5 | + ' ';
6 |
7 | var fs = require('fs');
8 |
9 | function Sheet(config, xlsx, shareStrings, convertedShareStrings){
10 | this.config = config;
11 | this.xlsx = xlsx;
12 | this.shareStrings = shareStrings;
13 | this.convertedShareStrings = convertedShareStrings;
14 | }
15 |
16 | Sheet.prototype.generate = function(){
17 | var config = this.config, xlsx = this.xlsx;
18 | var cols = config.cols,
19 | data = config.rows,
20 | colsLength = cols.length,
21 | rows = "",
22 | row = "",
23 | colsWidth = "",
24 | styleIndex,
25 | self = this,
26 | k;
27 | config.fileName = 'xl/worksheets/' + (config.name || "sheet").replace(/[*?\]\[\/\/]/g, '') + '.xml';
28 | if (config.stylesXmlFile) {
29 | var path = config.stylesXmlFile;
30 | var styles = null;
31 | styles = fs.readFileSync(path, 'utf8');
32 | if (styles) {
33 | xlsx.file("xl/styles.xml", styles);
34 | }
35 | }
36 |
37 | //first row for column caption
38 | row = '';
39 | var colStyleIndex;
40 | for (k = 0; k < colsLength; k++) {
41 | colStyleIndex = cols[k].captionStyleIndex || 0;
42 | row += addStringCell(self, getColumnLetter(k + 1) + 1, cols[k].caption, colStyleIndex);
43 | if (cols[k].width) {
44 | colsWidth += '';
45 | }
46 | }
47 | row += '';
48 | rows += row;
49 |
50 | //fill in data
51 | var i, j, r, cellData, currRow, cellType, dataLength = data.length;
52 |
53 | for (i = 0; i < dataLength; i++) {
54 | r = data[i],
55 | currRow = i + 2;
56 | row = '';
57 | for (j = 0; j < colsLength; j++) {
58 | styleIndex = null;
59 | cellData = r[j];
60 | cellType = cols[j].type;
61 | if (typeof cols[j].beforeCellWrite === 'function') {
62 | var e = {
63 | rowNum: currRow,
64 | styleIndex: null,
65 | cellType: cellType
66 | };
67 | cellData = cols[j].beforeCellWrite(r, cellData, e);
68 | styleIndex = e.styleIndex || styleIndex;
69 | cellType = e.cellType;
70 | delete e;
71 | }
72 | switch (cellType) {
73 | case 'number':
74 | row += addNumberCell(getColumnLetter(j + 1) + currRow, cellData, styleIndex);
75 | break;
76 | case 'date':
77 | row += addDateCell(getColumnLetter(j + 1) + currRow, cellData, styleIndex);
78 | break;
79 | case 'bool':
80 | row += addBoolCell(getColumnLetter(j + 1) + currRow, cellData, styleIndex);
81 | break;
82 | default:
83 | row += addStringCell(self, getColumnLetter(j + 1) + currRow, cellData, styleIndex);
84 | }
85 | }
86 | row += '';
87 | rows += row;
88 | }
89 | if (colsWidth !== "") {
90 | sheetFront += '' + colsWidth + '';
91 | }
92 | xlsx.file(config.fileName, sheetFront + '' + rows + '' + sheetBack);
93 | }
94 |
95 | module.exports = Sheet;
96 |
97 | var startTag = function (obj, tagName, closed){
98 | var result = "<" + tagName, p;
99 | for (p in obj){
100 | result += " " + p + "=" + obj[p];
101 | }
102 | if (!closed)
103 | result += ">";
104 | else
105 | result += "/>";
106 | return result;
107 | };
108 |
109 | var endTag = function(tagName){
110 | return "" + tagName + ">";
111 | };
112 |
113 | var addNumberCell = function(cellRef, value, styleIndex){
114 | styleIndex = styleIndex || 0;
115 | if (value===null)
116 | return "";
117 | else
118 | return ''+value+'';
119 | };
120 |
121 | var addDateCell = function(cellRef, value, styleIndex){
122 | styleIndex = styleIndex || 1;
123 | if (value===null)
124 | return "";
125 | else
126 | return ''+value+'';
127 | };
128 |
129 | var addBoolCell = function(cellRef, value, styleIndex){
130 | styleIndex = styleIndex || 0;
131 | if (value===null)
132 | return "";
133 | if (value){
134 | value = 1;
135 | } else
136 | value = 0;
137 | return ''+value+'';
138 | };
139 |
140 |
141 | var addStringCell = function(sheet, cellRef, value, styleIndex){
142 | styleIndex = styleIndex || 0;
143 | if (value===null)
144 | return "";
145 | if (typeof value ==='string'){
146 | value = value.replace(/&/g, "&").replace(/'/g, "'").replace(/>/g, ">").replace(/"+value+"";
153 | }
154 | return ''+i+'';
155 | };
156 |
157 |
158 | var getColumnLetter = function(col){
159 | if (col <= 0)
160 | throw "col must be more than 0";
161 | var array = new Array();
162 | while (col > 0)
163 | {
164 | var remainder = col % 26;
165 | col /= 26;
166 | col = Math.floor(col);
167 | if(remainder ===0)
168 | {
169 | remainder = 26;
170 | col--;
171 | }
172 | array.push(64 + remainder);
173 | }
174 | return String.fromCharCode.apply(null, array.reverse());
175 | };
176 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | // test/main.js
2 | var should = require('should');
3 | var nodeExcel = require('../index');
4 |
5 | describe('Simple Excel xlsx Export', function() {
6 | describe('Export', function() {
7 | it('returns xlsx', function() {
8 | var conf = {};
9 | conf.name = 'xxxxxx';
10 | conf.cols = [{
11 | caption: 'string',
12 | type: 'string'
13 | },
14 | {
15 | caption: 'date',
16 | type: 'date'
17 | },
18 | {
19 | caption: 'bool',
20 | type: 'bool'
21 | },
22 | {
23 | caption: 'number 2',
24 | type: 'number'
25 | }];
26 | conf.rows = [['pi', (new Date(Date.UTC(2013, 4, 1))).oaDate(), true, 3.14], ["e", (new Date(2012, 4, 1)).oaDate(), false, 2.7182], ["M&M<>'", (new Date(Date.UTC(2013, 6, 9))).oaDate(), false, 1.2], ["null", null, null, null]];
27 |
28 | var result = nodeExcel.execute(conf);
29 | //console.log(result);
30 |
31 | var fs = require('fs');
32 | fs.writeFileSync('single.xlsx', result, 'binary');
33 | });
34 | it('returns multisheet xlsx', function() {
35 | var confs = [];
36 | var conf = {};
37 | conf.cols = [{
38 | caption: 'string',
39 | type: 'string'
40 | },
41 | {
42 | caption: 'date',
43 | type: 'date'
44 | },
45 | {
46 | caption: 'bool',
47 | type: 'bool'
48 | },
49 | {
50 | caption: 'number 2',
51 | type: 'number'
52 | }];
53 | conf.rows = [['hahai', (new Date(Date.UTC(2013, 4, 1))).oaDate(), true, 3.14], ["e", (new Date(2012, 4, 1)).oaDate(), false, 2.7182], ["M&M<>'", (new Date(Date.UTC(2013, 6, 9))).oaDate(), false, 1.2], ["null", null, null, null]];
54 | for (var i = 0; i < 3; i++) {
55 | conf = JSON.parse(JSON.stringify(conf)); //clone
56 | conf.name = 'sheet'+i;
57 | confs.push(conf);
58 | }
59 | var result = nodeExcel.execute(confs),
60 | fs = require('fs');
61 | fs.writeFileSync('multi.xlsx', result, 'binary');
62 | })
63 | });
64 | });
65 |
66 |
--------------------------------------------------------------------------------