├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── index.html ├── index.js ├── index.js.map ├── index.map ├── tableToExcel.517b1af9.js ├── tableToExcel.517b1af9.js.map ├── tableToExcel.517b1af9.map ├── tableToExcel.js ├── tableToExcel.js.map └── tableToExcel.map ├── package-lock.json ├── package.json ├── sample └── index.html ├── src ├── parser.js └── tableToExcel.js └── test ├── parser.test.js ├── samples ├── colAndRowSpan.html ├── colSpan.html ├── multipleMerges.html ├── simpleTable.html └── styles.html └── utils └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless/ 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | #DynamoDB Local files 79 | .dynamodb/ 80 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Linways Technologies Pvt. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table to Excel 2 2 | 3 | [![Build Status](https://travis-ci.org/linways/table-to-excel.svg?branch=master)](https://travis-ci.org/linways/table-to-excel) 4 | 5 | Export HTML table to valid excel file effortlessly. 6 | This library uses [exceljs/exceljs](https://github.com/exceljs/exceljs) under the hood to create the excel. 7 | (Initial version of this library was using [protobi/js-xlsx](https://github.com/linways/table-to-excel/tree/V0.2.1), it can be found [here](https://github.com/linways/table-to-excel/tree/V0.2.1)) 8 | 9 | # Installation 10 | 11 | ## Browser 12 | 13 | Just add a script tag: 14 | 15 | ```html 16 | 17 | ``` 18 | 19 | ## Node 20 | 21 | ```bash 22 | npm install @linways/table-to-excel --save 23 | ``` 24 | 25 | ```javascript 26 | import TableToExcel from "@linways/table-to-excel"; 27 | ``` 28 | 29 | # Usage 30 | 31 | Create your HTML table as normal. 32 | To export content of table `#table1` run: 33 | 34 | ```javascript 35 | TableToExcel.convert(document.getElementById("table1")); 36 | ``` 37 | 38 | or 39 | 40 | ```javascript 41 | TableToExcel.convert(document.getElementById("table1"), { 42 | name: "table1.xlsx", 43 | sheet: { 44 | name: "Sheet 1" 45 | } 46 | }); 47 | ``` 48 | Check [this pen](https://codepen.io/rohithb/pen/YdjVbb) for working example. 49 | 50 | 51 | 52 | 53 | # Cell Types 54 | 55 | Cell types can be set using the following data attributes: 56 | 57 | | Attribute | Description | Possible Values | 58 | | ---------------- | ---------------------------------- | -------------------------------------------------------------------------- | 59 | | `data-t` | To specify the data type of a cell | `s` : String (Default)
`n` : Number
`b` : Boolean
`d` : Date | 60 | | `data-hyperlink` | To add hyper link to cell | External URL or hyperlink to another sheet | 61 | | `data-error` | To add value of a cell as error | | 62 | 63 | Example: 64 | 65 | ```html 66 | 67 | 2500 68 | 69 | 05-23-2018 70 | 71 | true 72 | 73 | 0 74 | 75 | Google 76 | ``` 77 | 78 | # Cell Styling 79 | 80 | All styles are set using `data` attributes on `td` tags. 81 | There are 5 types of attributes: `data-f-*`, `data-a-*`, `data-b-*`, `data-fill-*` and `data-num-fmt` which corresponds to five top-level attributes `font`, `alignment`, `border`, `fill` and `numFmt`. 82 | 83 | | Category | Attribute | Description | Values | 84 | | --------- | ---------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- | 85 | | font | `data-f-name` | Font name | "Calibri" ,"Arial" etc. | 86 | | | `data-f-sz` | Font size | "11" // font size in points | 87 | | | `data-f-color` | Font color | A hex ARGB value. Eg: FFFFOOOO for opaque red. | 88 | | | `data-f-bold` | Bold | `true` or `false` | 89 | | | `data-f-italic` | Italic | `true` or `false` | 90 | | | `data-underline` | Underline | `true` or `false` | 91 | | | `data-f-strike` | Strike | `true` or `false` | 92 | | Alignment | `data-a-h` | Horizontal alignment | `left`, `center`, `right`, `fill`, `justify`, `centerContinuous`, `distributed` | 93 | | | `data-a-v` | Vertical alignment | `bottom`, `middle`, `top`, `distributed`, `justify` | 94 | | | `data-a-wrap` | Wrap text | `true` or `false` | 95 | | | `data-a-indent` | Indent | Integer | 96 | | | `data-a-rtl` | Text direction: Right to Left | `true` or `false` | 97 | | | `data-a-text-rotation` | Text rotation | 0 to 90 | 98 | | | | | -1 to -90 | 99 | | | | | vertical | 100 | | Border | `data-b-a-s` | Border style (all borders) | Refer `BORDER_STYLES` | 101 | | | `data-b-t-s` | Border top style | Refer `BORDER_STYLES` | 102 | | | `data-b-b-s` | Border bottom style | Refer `BORDER_STYLES` | 103 | | | `data-b-l-s` | Border left style | Refer `BORDER_STYLES` | 104 | | | `data-b-r-s` | Border right style | Refer `BORDER_STYLES` | 105 | | | `data-b-a-c` | Border color (all borders) | A hex ARGB value. Eg: FFFFOOOO for opaque red. | 106 | | | `data-b-t-c` | Border top color | A hex ARGB value. | 107 | | | `data-b-b-c` | Border bottom color | A hex ARGB value. | 108 | | | `data-b-l-c` | Border left color | A hex ARGB value. | 109 | | | `data-b-r-c` | Border right color | A hex ARGB value. | 110 | | Fill | `data-fill-color` | Cell background color | A hex ARGB value. | 111 | | numFmt | `data-num-fmt` | Number Format | "0" | 112 | | | | | "0.00%" | 113 | | | | | "0.0%" // string specifying a custom format | 114 | | | | | "0.00%;\\(0.00%\\);\\-;@" // string specifying a custom format, escaping special characters | 115 | 116 | **`BORDER_STYLES:`** `thin`, `dotted`, `dashDot`, `hair`, `dashDotDot`, `slantDashDot`, `mediumDashed`, `mediumDashDotDot`, `mediumDashDot`, `medium`, `double`, `thick` 117 | 118 | # Exclude Cells and rows 119 | 120 | To exclude a cell or a row from the exported excel add `data-exclude="true"` to the corresponding `td` or `tr`. 121 | Example: 122 | 123 | ```html 124 | 125 | 126 | Excluded row 127 | Something 128 | 129 | 130 | 131 | 132 | Included Cell 133 | Excluded Cell 134 | Included Cell 135 | 136 | ``` 137 | 138 | # Column Width 139 | 140 | Column width's can be set by specifying `data-cols-width` in the `` tag. 141 | `data-cols-width` accepts comma separated column widths specified in character count . 142 | `data-cols-width="10,20"` will set width of first coulmn as width of 10 charaters and second column as 20 characters wide. 143 | Example: 144 | 145 | ```html 146 |
147 | ... 148 |
149 | ``` 150 | 151 | # Row Height 152 | 153 | Row Height can be set by specifying `data-height` in the `` tag. 154 | Example: 155 | 156 | ```html 157 | 158 | Cell 1 159 | Cell 2 160 | 161 | ``` 162 | 163 | # Release Changelog 164 | 165 | ## 1.0.0 166 | 167 | [Migration Guide](https://github.com/linways/table-to-excel/wiki/Migration-guide-for-V0.2.1-to-V1.0.0) for migrating from V0.2.1 to V1.0.0 168 | 169 | - Changed the backend to Exce[exceljs/exceljs](https://github.com/exceljs/exceljs)lJS 170 | - Added border color 171 | - Option to set style and color for all borders 172 | - Exclude row 173 | - Added text underline 174 | - Added support for hyperlinks 175 | - Text intent 176 | - RTL support 177 | - Extra alignment options 178 | - String "true/false" will be accepted as Boolean 179 | - Changed border style values 180 | - Text rotation values changed 181 | 182 | ## 1.0.2 183 | 184 | - Fixed bug in handling multiple columns merges in a sheet 185 | 186 | ## 1.0.3 187 | 188 | - Option to specify row height 189 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 63 | 64 | 65 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
16 | Sample Excel 17 |
21 | Italic and horizontal center in Arial 22 |
Col 1 (number)Col 2Wrapped TextCol 4 (date)Col 5
1 34 | ABC1 35 | Striked Text20-05-20182210.00
2 44 | ABC 2 45 | Merged cell05-21-2018230.00
05-22-20182493.00
58 | Hyperlink 59 | 61 | 4933.00 62 |
66 | مرحبا 67 | 69 | 2009.00 70 |
All borders
truefalse10Value Error
84 | All borders separately 85 |
Excluded rowSomething
Included CellExcluded CellIncluded Cell
Row height 42
101 | 102 | 103 | 104 | 105 | 106 | 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 |
107 | Things 108 |
LettersNumbersSp chars
AB12*#
Y34$%
CB12*#
Y34$%
DB12*#
Y34$%
161 | 162 |
163 | 164 |
165 | 166 | 167 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requires ] 3 | // 4 | // map of requires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the require for previous bundles 8 | parcelRequire = (function (modules, cache, entry, globalName) { 9 | // Save the require from previous bundle to this closure if any 10 | var previousRequire = typeof parcelRequire === 'function' && parcelRequire; 11 | var nodeRequire = typeof require === 'function' && require; 12 | 13 | function newRequire(name, jumped) { 14 | if (!cache[name]) { 15 | if (!modules[name]) { 16 | // if we cannot find the module within our internal map or 17 | // cache jump to the current global require ie. the last bundle 18 | // that was added to the page. 19 | var currentRequire = typeof parcelRequire === 'function' && parcelRequire; 20 | if (!jumped && currentRequire) { 21 | return currentRequire(name, true); 22 | } 23 | 24 | // If there are other bundles on this page the require from the 25 | // previous one is saved to 'previousRequire'. Repeat this as 26 | // many times as there are bundles until the module is found or 27 | // we exhaust the require chain. 28 | if (previousRequire) { 29 | return previousRequire(name, true); 30 | } 31 | 32 | // Try the node require function if it exists. 33 | if (nodeRequire && typeof name === 'string') { 34 | return nodeRequire(name); 35 | } 36 | 37 | var err = new Error('Cannot find module \'' + name + '\''); 38 | err.code = 'MODULE_NOT_FOUND'; 39 | throw err; 40 | } 41 | 42 | localRequire.resolve = resolve; 43 | localRequire.cache = {}; 44 | 45 | var module = cache[name] = new newRequire.Module(name); 46 | 47 | modules[name][0].call(module.exports, localRequire, module, module.exports, this); 48 | } 49 | 50 | return cache[name].exports; 51 | 52 | function localRequire(x){ 53 | return newRequire(localRequire.resolve(x)); 54 | } 55 | 56 | function resolve(x){ 57 | return modules[name][1][x] || x; 58 | } 59 | } 60 | 61 | function Module(moduleName) { 62 | this.id = moduleName; 63 | this.bundle = newRequire; 64 | this.exports = {}; 65 | } 66 | 67 | newRequire.isParcelRequire = true; 68 | newRequire.Module = Module; 69 | newRequire.modules = modules; 70 | newRequire.cache = cache; 71 | newRequire.parent = previousRequire; 72 | newRequire.register = function (id, exports) { 73 | modules[id] = [function (require, module) { 74 | module.exports = exports; 75 | }, {}]; 76 | }; 77 | 78 | var error; 79 | for (var i = 0; i < entry.length; i++) { 80 | try { 81 | newRequire(entry[i]); 82 | } catch (e) { 83 | // Save first error but execute all entries 84 | if (!error) { 85 | error = e; 86 | } 87 | } 88 | } 89 | 90 | if (entry.length) { 91 | // Expose entry point to Node, AMD or browser globals 92 | // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js 93 | var mainExports = newRequire(entry[entry.length - 1]); 94 | 95 | // CommonJS 96 | if (typeof exports === "object" && typeof module !== "undefined") { 97 | module.exports = mainExports; 98 | 99 | // RequireJS 100 | } else if (typeof define === "function" && define.amd) { 101 | define(function () { 102 | return mainExports; 103 | }); 104 | 105 | // 218 | 219 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | const TTEParser = (function() { 2 | let methods = {}; 3 | 4 | /** 5 | * Parse HTML table to excel worksheet 6 | * @param {object} ws The worksheet object 7 | * @param {HTML entity} table The table to be converted to excel sheet 8 | */ 9 | methods.parseDomToTable = function(ws, table, opts) { 10 | let _r, _c, cs, rs, r, c; 11 | let rows = [...table.rows]; 12 | let widths = table.getAttribute("data-cols-width"); 13 | if (widths) 14 | widths = widths.split(",").map(function(item) { 15 | return parseInt(item); 16 | }); 17 | let merges = []; 18 | for (_r = 0; _r < rows.length; ++_r) { 19 | let row = rows[_r]; 20 | r = _r + 1; // Actual excel row number 21 | c = 1; // Actual excel col number 22 | if (row.getAttribute("data-exclude") === "true") { 23 | rows.splice(_r, 1); 24 | _r--; 25 | continue; 26 | } 27 | if (row.getAttribute("data-height")) { 28 | let exRow = ws.getRow(r); 29 | exRow.height = parseFloat(row.getAttribute("data-height")); 30 | } 31 | 32 | let tds = [...row.children]; 33 | for (_c = 0; _c < tds.length; ++_c) { 34 | let td = tds[_c]; 35 | if (td.getAttribute("data-exclude") === "true") { 36 | tds.splice(_c, 1); 37 | _c--; 38 | continue; 39 | } 40 | for (let _m = 0; _m < merges.length; ++_m) { 41 | var m = merges[_m]; 42 | if (m.s.c == c && m.s.r <= r && r <= m.e.r) { 43 | c = m.e.c + 1; 44 | _m = -1; 45 | } 46 | } 47 | let exCell = ws.getCell(getColumnAddress(c, r)); 48 | // calculate merges 49 | cs = parseInt(td.getAttribute("colspan")) || 1; 50 | rs = parseInt(td.getAttribute("rowspan")) || 1; 51 | if (cs > 1 || rs > 1) { 52 | merges.push({ 53 | s: { c: c, r: r }, 54 | e: { c: c + cs - 1, r: r + rs - 1 } 55 | }); 56 | } 57 | c += cs; 58 | exCell.value = getValue(td); 59 | if (!opts.autoStyle) { 60 | let styles = getStylesDataAttr(td); 61 | exCell.font = styles.font || null; 62 | exCell.alignment = styles.alignment || null; 63 | exCell.border = styles.border || null; 64 | exCell.fill = styles.fill || null; 65 | exCell.numFmt = styles.numFmt || null; 66 | } 67 | } 68 | } 69 | //Setting column width 70 | if (widths) 71 | widths.forEach((width, _i) => { 72 | ws.columns[_i].width = width; 73 | }); 74 | applyMerges(ws, merges); 75 | return ws; 76 | }; 77 | 78 | /** 79 | * To apply merges on the sheet 80 | * @param {object} ws The worksheet object 81 | * @param {object[]} merges array of merges 82 | */ 83 | let applyMerges = function(ws, merges) { 84 | merges.forEach(m => { 85 | ws.mergeCells( 86 | getExcelColumnName(m.s.c) + 87 | m.s.r + 88 | ":" + 89 | getExcelColumnName(m.e.c) + 90 | m.e.r 91 | ); 92 | }); 93 | }; 94 | 95 | /** 96 | * Convert HTML to plain text 97 | */ 98 | let htmldecode = (function() { 99 | let entities = [ 100 | ["nbsp", " "], 101 | ["middot", "·"], 102 | ["quot", '"'], 103 | ["apos", "'"], 104 | ["gt", ">"], 105 | ["lt", "<"], 106 | ["amp", "&"] 107 | ].map(function(x) { 108 | return [new RegExp("&" + x[0] + ";", "g"), x[1]]; 109 | }); 110 | return function htmldecode(str) { 111 | let o = str 112 | .trim() 113 | .replace(/\s+/g, " ") 114 | .replace(/<\s*[bB][rR]\s*\/?>/g, "\n") 115 | .replace(/<[^>]*>/g, ""); 116 | for (let i = 0; i < entities.length; ++i) 117 | o = o.replace(entities[i][0], entities[i][1]); 118 | return o; 119 | }; 120 | })(); 121 | 122 | /** 123 | * Takes a positive integer and returns the corresponding column name. 124 | * @param {number} num The positive integer to convert to a column name. 125 | * @return {string} The column name. 126 | */ 127 | let getExcelColumnName = function(num) { 128 | for (var ret = "", a = 1, b = 26; (num -= a) >= 0; a = b, b *= 26) { 129 | ret = String.fromCharCode(parseInt((num % b) / a) + 65) + ret; 130 | } 131 | return ret; 132 | }; 133 | 134 | let getColumnAddress = function(col, row) { 135 | return getExcelColumnName(col) + row; 136 | }; 137 | 138 | /** 139 | * Checks the data type specified and conerts the value to it. 140 | * @param {HTML entity} td 141 | */ 142 | let getValue = function(td) { 143 | let dataType = td.getAttribute("data-t"); 144 | let rawVal = htmldecode(td.innerHTML); 145 | if (dataType) { 146 | let val; 147 | switch (dataType) { 148 | case "n": //number 149 | val = Number(rawVal); 150 | break; 151 | case "d": //date 152 | let date = new Date(rawVal); 153 | // To fix the timezone issue 154 | val = new Date( 155 | Date.UTC( 156 | date.getFullYear(), 157 | date.getMonth(), 158 | date.getDate(), 159 | date.getHours(), 160 | date.getMinutes(), 161 | date.getSeconds() 162 | ) 163 | ); 164 | break; 165 | case "b": //boolean 166 | val = 167 | rawVal.toLowerCase() === "true" 168 | ? true 169 | : rawVal.toLowerCase() === "false" 170 | ? false 171 | : Boolean(parseInt(rawVal)); 172 | break; 173 | default: 174 | val = rawVal; 175 | } 176 | return val; 177 | } else if (td.getAttribute("data-hyperlink")) { 178 | return { 179 | text: rawVal, 180 | hyperlink: td.getAttribute("data-hyperlink") 181 | }; 182 | } else if (td.getAttribute("data-error")) { 183 | return { error: td.getAttribute("data-error") }; 184 | } 185 | return rawVal; 186 | }; 187 | 188 | /** 189 | * Prepares the style object for a cell using the data attributes 190 | * @param {HTML entity} td 191 | */ 192 | let getStylesDataAttr = function(td) { 193 | //Font attrs 194 | let font = {}; 195 | if (td.getAttribute("data-f-name")) 196 | font.name = td.getAttribute("data-f-name"); 197 | if (td.getAttribute("data-f-sz")) font.size = td.getAttribute("data-f-sz"); 198 | if (td.getAttribute("data-f-color")) 199 | font.color = { argb: td.getAttribute("data-f-color") }; 200 | if (td.getAttribute("data-f-bold") === "true") font.bold = true; 201 | if (td.getAttribute("data-f-italic") === "true") font.italic = true; 202 | if (td.getAttribute("data-f-underline") === "true") font.underline = true; 203 | if (td.getAttribute("data-f-strike") === "true") font.strike = true; 204 | 205 | // Alignment attrs 206 | let alignment = {}; 207 | if (td.getAttribute("data-a-h")) 208 | alignment.horizontal = td.getAttribute("data-a-h"); 209 | if (td.getAttribute("data-a-v")) 210 | alignment.vertical = td.getAttribute("data-a-v"); 211 | if (td.getAttribute("data-a-wrap") === "true") alignment.wrapText = true; 212 | if (td.getAttribute("data-a-text-rotation")) 213 | alignment.textRotation = td.getAttribute("data-a-text-rotation"); 214 | if (td.getAttribute("data-a-indent")) 215 | alignment.indent = td.getAttribute("data-a-indent"); 216 | if (td.getAttribute("data-a-rtl") === "true") 217 | alignment.readingOrder = "rtl"; 218 | 219 | // Border attrs 220 | let border = { 221 | top: {}, 222 | left: {}, 223 | bottom: {}, 224 | right: {} 225 | }; 226 | 227 | if (td.getAttribute("data-b-a-s")) { 228 | let style = td.getAttribute("data-b-a-s"); 229 | border.top.style = style; 230 | border.left.style = style; 231 | border.bottom.style = style; 232 | border.right.style = style; 233 | } 234 | if (td.getAttribute("data-b-a-c")) { 235 | let color = { argb: td.getAttribute("data-b-a-c") }; 236 | border.top.color = color; 237 | border.left.color = color; 238 | border.bottom.color = color; 239 | border.right.color = color; 240 | } 241 | if (td.getAttribute("data-b-t-s")) { 242 | border.top.style = td.getAttribute("data-b-t-s"); 243 | if (td.getAttribute("data-b-t-c")) 244 | border.top.color = { argb: td.getAttribute("data-b-t-c") }; 245 | } 246 | if (td.getAttribute("data-b-l-s")) { 247 | border.left.style = td.getAttribute("data-b-l-s"); 248 | if (td.getAttribute("data-b-l-c")) 249 | border.left.color = { argb: td.getAttribute("data-b-t-c") }; 250 | } 251 | if (td.getAttribute("data-b-b-s")) { 252 | border.bottom.style = td.getAttribute("data-b-b-s"); 253 | if (td.getAttribute("data-b-b-c")) 254 | border.bottom.color = { argb: td.getAttribute("data-b-t-c") }; 255 | } 256 | if (td.getAttribute("data-b-r-s")) { 257 | border.right.style = td.getAttribute("data-b-r-s"); 258 | if (td.getAttribute("data-b-r-c")) 259 | border.right.color = { argb: td.getAttribute("data-b-t-c") }; 260 | } 261 | 262 | //Fill 263 | let fill; 264 | if (td.getAttribute("data-fill-color")) { 265 | fill = { 266 | type: "pattern", 267 | pattern: "solid", 268 | fgColor: { argb: td.getAttribute("data-fill-color") } 269 | }; 270 | } 271 | //number format 272 | let numFmt; 273 | if (td.getAttribute("data-num-fmt")) 274 | numFmt = td.getAttribute("data-num-fmt"); 275 | 276 | return { 277 | font, 278 | alignment, 279 | border, 280 | fill, 281 | numFmt 282 | }; 283 | }; 284 | 285 | return methods; 286 | })(); 287 | 288 | export default TTEParser; 289 | -------------------------------------------------------------------------------- /src/tableToExcel.js: -------------------------------------------------------------------------------- 1 | import Parser from "./parser"; 2 | import saveAs from "file-saver"; 3 | import ExcelJS from "../node_modules/exceljs/dist/es5/exceljs.browser"; 4 | 5 | const TableToExcel = (function(Parser) { 6 | let methods = {}; 7 | 8 | methods.initWorkBook = function() { 9 | let wb = new ExcelJS.Workbook(); 10 | return wb; 11 | }; 12 | 13 | methods.initSheet = function(wb, sheetName) { 14 | let ws = wb.addWorksheet(sheetName); 15 | return ws; 16 | }; 17 | 18 | methods.save = function(wb, fileName) { 19 | wb.xlsx.writeBuffer().then(function(buffer) { 20 | saveAs( 21 | new Blob([buffer], { type: "application/octet-stream" }), 22 | fileName 23 | ); 24 | }); 25 | }; 26 | 27 | methods.tableToSheet = function(wb, table, opts) { 28 | let ws = this.initSheet(wb, opts.sheet.name); 29 | ws = Parser.parseDomToTable(ws, table, opts); 30 | return wb; 31 | }; 32 | 33 | methods.tableToBook = function(table, opts) { 34 | let wb = this.initWorkBook(); 35 | wb = this.tableToSheet(wb, table, opts); 36 | return wb; 37 | }; 38 | 39 | methods.convert = function(table, opts = {}) { 40 | let defaultOpts = { 41 | name: "export.xlsx", 42 | autoStyle: false, 43 | sheet: { 44 | name: "Sheet 1" 45 | } 46 | }; 47 | opts = { ...defaultOpts, ...opts }; 48 | let wb = this.tableToBook(table, opts); 49 | this.save(wb, opts.name); 50 | }; 51 | 52 | return methods; 53 | })(Parser); 54 | 55 | export default TableToExcel; 56 | window.TableToExcel = TableToExcel; 57 | -------------------------------------------------------------------------------- /test/parser.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import Parser from "../src/parser"; 3 | import { getWorkSheet, getTable, defaultOpts as _opts } from "./utils/utils"; 4 | 5 | describe("Parser", function() { 6 | describe("Basic conversion", function() { 7 | it("should convert simple html table to worksheet", function() { 8 | let table = getTable("simpleTable"); 9 | let ws = getWorkSheet(); 10 | ws = Parser.parseDomToTable(ws, table, _opts); 11 | expect(ws).to.not.be.null; 12 | expect(ws.getCell("A1").value).to.equals("#"); 13 | expect(ws.getCell("B1").value).to.equals("City"); 14 | expect(ws.rowCount).to.equals(4); 15 | }); 16 | 17 | it("should successfully handle colspan", function() { 18 | let table = getTable("colSpan"); 19 | let ws = getWorkSheet(); 20 | ws = Parser.parseDomToTable(ws, table, _opts); 21 | expect(ws.getCell("B2").value).to.equals(ws.getCell("C2").value); 22 | expect(ws.getCell("C2").master).to.equals(ws.getCell("B2")); 23 | expect(ws.getCell("A4").value).to.equals(ws.getCell("C4").value); 24 | expect(ws.getCell("C4").master).to.equals(ws.getCell("A4")); 25 | }); 26 | 27 | it("should successfully handle both colspan and rowspan", function() { 28 | let table = getTable("colAndRowSpan"); 29 | let ws = getWorkSheet(); 30 | ws = Parser.parseDomToTable(ws, table, _opts); 31 | expect(ws.getCell("B2").value).to.equals(ws.getCell("C2").value); 32 | expect(ws.getCell("B2").value).to.equals(ws.getCell("B3").value); 33 | expect(ws.getCell("B2").value).to.equals(ws.getCell("C3").value); 34 | expect(ws.getCell("C5").value).to.equals(ws.getCell("C6").value); 35 | }); 36 | }); 37 | 38 | describe("Styles", function() { 39 | var ws; 40 | beforeEach(function() { 41 | let table = getTable("styles"); 42 | ws = getWorkSheet(); 43 | ws = Parser.parseDomToTable(ws, table, _opts); 44 | }); 45 | describe("Font attributes", function() { 46 | it("should handle font name properly", function() { 47 | expect(ws.getCell("A1").font.name).to.be.undefined; 48 | expect(ws.getCell("A2").font.name).to.equals("Arial"); 49 | }); 50 | 51 | it("should handle font size properly", function() { 52 | expect(parseInt(ws.getCell("A1").font.size)).to.equals(25); 53 | expect(ws.getCell("A2").font.size).to.be.undefined; 54 | }); 55 | 56 | it("should handle font color properly", function() { 57 | expect(ws.getCell("A1").font.color).to.deep.equals({ 58 | argb: "FFFFAA00" 59 | }); 60 | expect(ws.getCell("A2").font.color).to.be.undefined; 61 | }); 62 | 63 | it("should handle bold cells properly", function() { 64 | expect(ws.getCell("A7").font.bold).to.equals(true); 65 | expect(ws.getCell("A2").font.bold).to.be.undefined; 66 | }); 67 | 68 | it("should handle italics cells properly", function() { 69 | expect(ws.getCell("A2").font.italic).to.equals(true); 70 | expect(ws.getCell("A1").font.italic).to.be.undefined; 71 | }); 72 | }); 73 | 74 | describe("Alignment Attributes", function() { 75 | it("should handle horizontal alignment", function() { 76 | expect(ws.getCell("A2").alignment.horizontal).to.equals("center"); 77 | }); 78 | it("should handle vertical alignment", function() { 79 | expect(ws.getCell("A1").alignment.vertical).to.equals("middle"); 80 | expect(ws.getCell("A2").alignment.vertical).to.equals("top"); 81 | }); 82 | it("should handle wrap text properly", function() { 83 | expect(ws.getCell("C3").alignment.wrapText).to.equals(true); 84 | expect(ws.getCell("C4").alignment.wrapText).to.be.undefined; 85 | }); 86 | it("should handle text rotation properly", function() { 87 | expect(ws.getCell("A3").alignment.textRotation).to.equals("90"); 88 | expect(ws.getCell("B3").alignment.textRotation).to.equals("vertical"); 89 | expect(ws.getCell("D3").alignment.textRotation).to.equals("-45"); 90 | expect(ws.getCell("E3").alignment.textRotation).to.equals("-90"); 91 | expect(ws.getCell("C4").alignment.wrapText).to.be.undefined; 92 | }); 93 | it("should handle indent properly", function() { 94 | expect(ws.getCell("C5").alignment.indent).to.equals("3"); 95 | expect(ws.getCell("C4").alignment.wrapText).to.be.undefined; 96 | }); 97 | it("should handle text direction properly", function() { 98 | expect(ws.getCell("A8").alignment.readingOrder).to.equals("rtl"); 99 | }); 100 | }); 101 | 102 | describe("Border Attributes", function() { 103 | it("handle all border set by b-a-s", function() { 104 | expect(ws.getCell("A9").border.top.style).to.equals("dashed"); 105 | expect(ws.getCell("A9").border.left.style).to.equals("dashed"); 106 | expect(ws.getCell("A9").border.bottom.style).to.equals("dashed"); 107 | expect(ws.getCell("A9").border.right.style).to.equals("dashed"); 108 | }); 109 | it("handle border set independently", function() { 110 | expect(ws.getCell("A11").border.top.style).to.equals("thick"); 111 | expect(ws.getCell("A11").border.left.style).to.equals("thick"); 112 | expect(ws.getCell("A11").border.bottom.style).to.equals("thick"); 113 | expect(ws.getCell("A11").border.right.style).to.equals("thick"); 114 | }); 115 | it("handle border color set by b-a-c", function() { 116 | let color = { argb: "FFFF0000" }; 117 | expect(ws.getCell("A9").border.top.color).to.deep.equals(color); 118 | expect(ws.getCell("A9").border.left.color).to.deep.equals(color); 119 | expect(ws.getCell("A9").border.bottom.color).to.deep.equals(color); 120 | expect(ws.getCell("A9").border.right.color).to.deep.equals(color); 121 | }); 122 | it("handle border color set independently", function() { 123 | let color = { argb: "FF00FF00" }; 124 | expect(ws.getCell("A11").border.top.color).to.deep.equals(color); 125 | expect(ws.getCell("A11").border.left.color).to.deep.equals(color); 126 | expect(ws.getCell("A11").border.bottom.color).to.deep.equals(color); 127 | expect(ws.getCell("A11").border.right.color).to.deep.equals(color); 128 | }); 129 | }); 130 | 131 | describe("Fill Attributes", function() { 132 | it("should handle fill color properly", function() { 133 | expect(ws.getCell("B5").fill.pattern).to.equals("solid"); 134 | expect(ws.getCell("B5").fill.fgColor).to.deep.equals({ 135 | argb: "FFFF0000" 136 | }); 137 | }); 138 | }); 139 | }); 140 | 141 | describe("Data Type", function() { 142 | var ws; 143 | beforeEach(function() { 144 | let table = getTable("styles"); 145 | ws = getWorkSheet(); 146 | ws = Parser.parseDomToTable(ws, table, _opts); 147 | }); 148 | it("should handle number", function() { 149 | expect(ws.getCell("A4").value).to.be.a("number"); 150 | expect(ws.getCell("B4").value).to.be.a("string"); 151 | }); 152 | it("should handle date", function() { 153 | let expectedDate = new Date("05-20-2018"); 154 | let actualDate = ws.getCell("D4").value; 155 | expect(actualDate).to.be.a("date"); 156 | expect(actualDate.getDate()).to.equals(expectedDate.getDate()); 157 | expect(actualDate.getMonth()).to.equals(expectedDate.getMonth()); 158 | expect(actualDate.getFullYear()).to.equals(expectedDate.getFullYear()); 159 | }); 160 | it("should handle boolean", function() { 161 | expect(ws.getCell("A10").value).to.be.a("boolean"); 162 | expect(ws.getCell("A10").value).to.equals(true); 163 | expect(ws.getCell("B10").value).to.be.a("boolean"); 164 | expect(ws.getCell("B10").value).to.equals(false); 165 | expect(ws.getCell("C10").value).to.be.a("boolean"); 166 | expect(ws.getCell("C10").value).to.equals(true); 167 | expect(ws.getCell("D10").value).to.be.a("boolean"); 168 | expect(ws.getCell("D10").value).to.equals(false); 169 | }); 170 | it("should handle hyperlink", function() { 171 | expect(ws.getCell("A7").value.text).to.exist; 172 | expect(ws.getCell("A7").value.hyperlink).to.exist; 173 | }); 174 | it("should handle error", function() { 175 | expect(ws.getCell("E10").value.error).to.exist; 176 | }); 177 | }); 178 | 179 | describe("Exclude row and cell", function() { 180 | it("should handle exclude row", function() { 181 | let table = getTable("styles"); 182 | let ws = getWorkSheet(); 183 | ws = Parser.parseDomToTable(ws, table, _opts); 184 | let actualsRows = [...table.getElementsByTagName("tr")]; 185 | expect(ws.rowCount).to.equals(actualsRows.length - 1); 186 | }); 187 | it("should handle exclude cell", function() { 188 | let table = getTable("styles"); 189 | let ws = getWorkSheet(); 190 | ws = Parser.parseDomToTable(ws, table, _opts); 191 | let actualsRows = [...table.getElementsByTagName("tr")]; 192 | let actualCells = [...actualsRows[12].children]; 193 | expect(ws.getRow(12).cellCount).to.equals(actualCells.length - 1); 194 | }); 195 | }); 196 | describe("Column widths", function() { 197 | it("Should handle the col widths", function() { 198 | let table = getTable("styles"); 199 | let ws = getWorkSheet(); 200 | ws = Parser.parseDomToTable(ws, table, _opts); 201 | expect(ws.columns[0].width).to.equals(70); 202 | }); 203 | }); 204 | 205 | describe("Row height", function() { 206 | it("Should handle the row height", function() { 207 | let table = getTable("styles"); 208 | let ws = getWorkSheet(); 209 | ws = Parser.parseDomToTable(ws, table, _opts); 210 | expect(ws.getRow(13).height).to.equals(45); 211 | }); 212 | }); 213 | 214 | describe("Multiple merges", function() { 215 | var ws; 216 | it("should handle multiple merges properly", function() { 217 | let table = getTable("multipleMerges"); 218 | ws = getWorkSheet(); 219 | ws = Parser.parseDomToTable(ws, table, _opts); 220 | expect(ws.getCell("A1").value).to.equals(ws.getCell("F1").value); 221 | expect(ws.getCell("A2").value).to.equals(ws.getCell("B2").value); 222 | expect(ws.getCell("C2").value).to.equals(ws.getCell("D2").value); 223 | expect(ws.getCell("E2").value).to.equals(ws.getCell("F2").value); 224 | expect(ws.getCell("A3").value).to.equals(ws.getCell("A4").value); 225 | expect(ws.getCell("B3").value).to.equals("B"); 226 | expect(ws.getCell("B4").value).to.equals("Y"); 227 | expect(ws.getCell("A5").value).to.equals(ws.getCell("A6").value); 228 | expect(ws.getCell("B5").value).to.equals("B"); 229 | expect(ws.getCell("B6").value).to.equals("Y"); 230 | }); 231 | }); 232 | }); 233 | -------------------------------------------------------------------------------- /test/samples/colAndRowSpan.html: -------------------------------------------------------------------------------- 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 |
#CityCountry
1Jinshanpu
2
Russia
3LuguChina
3Guanting
31 | -------------------------------------------------------------------------------- /test/samples/colSpan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
#CityCountry
1Jinshanpu
2ChantepieFrance
Russia
24 | -------------------------------------------------------------------------------- /test/samples/multipleMerges.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 |
4 | Things 5 |
LettersNumbersSp chars
AB12*#
Y34$%
CB12*#
Y34$%
DB12*#
Y34$%
58 | -------------------------------------------------------------------------------- /test/samples/simpleTable.html: -------------------------------------------------------------------------------- 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 |
#CityCountry
1JinshanpuChina
2ChantepieFrance
3KasimovRussia
27 | -------------------------------------------------------------------------------- /test/samples/styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 68 | 77 | 78 | 79 | 82 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
12 | Sample Excel 13 |
23 | Italic and horizontal center in Arial 24 |
Col 1 (number)Col 2Wrapped TextCol 4 (date)Col 5
1 36 | ABC1 37 | Striked Text05-20-20182210.00
2 46 | ABC 2 47 | Merged cell05-21-2018230.00
05-22-20182493.00
66 | Hyperlink 67 | 75 | 4933.00 76 |
80 | مرحبا 81 | 89 | 2009.00 90 |
All borders
truefalse10Value Error
113 | All borders separately 114 |
Excluded rowSomething
Included CellExcluded CellIncluded Cell
Row height 45
130 | -------------------------------------------------------------------------------- /test/utils/utils.js: -------------------------------------------------------------------------------- 1 | import TTE from "../../src/tableToExcel"; 2 | import jsdom from "jsdom"; 3 | import fs from "fs"; 4 | 5 | export const defaultOpts = { 6 | name: "export.xlsx", 7 | autoStyle: false, 8 | sheet: { 9 | name: "Sheet 1" 10 | } 11 | }; 12 | 13 | /** 14 | * Returns an empty worksheet for testing. 15 | * @param {object} opts 16 | */ 17 | export function getWorkSheet(opts = {}) { 18 | let _opts = defaultOpts; 19 | opts = { ..._opts, ...opts }; 20 | let wb = TTE.initWorkBook(); 21 | return TTE.initSheet(wb, opts.sheet.name); 22 | } 23 | /** 24 | * Read the conents of the a given file and returns the parsed DOM element 25 | * corresponding to the first table in the document. 26 | * @param {string} filename name of the html file where the table to test is written 27 | * @returns {HTMLTableElement} 28 | */ 29 | export function getTable(filename) { 30 | if (filename) { 31 | let path = __dirname + "/../samples/" + filename + ".html"; 32 | let htmlString = fs.readFileSync(path, "utf8"); 33 | const { JSDOM } = jsdom; 34 | const dom = new JSDOM(htmlString); 35 | return dom.window.document.querySelector("table"); 36 | } 37 | } 38 | --------------------------------------------------------------------------------