├── .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 ""; 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(/"; 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 | --------------------------------------------------------------------------------