├── .npmrc ├── index.js ├── test ├── base.xlsx └── test.js ├── .travis.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── package.json ├── lib ├── conversion.js ├── utils.js ├── stylesMap.js ├── scripts │ └── conversionScript.js └── tableToXlsx.js └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('./lib/conversion.js') 4 | -------------------------------------------------------------------------------- /test/base.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pofider/html-to-xlsx/HEAD/test/base.xlsx -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.9" 4 | sudo: required 5 | dist: trusty 6 | addons: 7 | apt: 8 | packages: 9 | # This is required to run new chrome on old trusty 10 | - libnss3 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # Recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.py] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | [*.js] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | [*.css] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | [*.jade] 32 | indent_style = space 33 | indent_size = 2 34 | 35 | [*.md] 36 | trim_trailing_whitespace = false 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | # Logs 3 | logs 4 | *.log 5 | .DS_Store 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Commenting this out is preferred by some people, see 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Users Environment Variables 30 | .lock-wscript 31 | 32 | test/temp/* 33 | 34 | lib/scripts/serverScript.js 35 | lib/scripts/standaloneScript.js 36 | 37 | try.js 38 | tryOther.js 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jan Blaha 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 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-to-xlsx", 3 | "version": "3.0.1", 4 | "description": "Convert html to xlsx", 5 | "keywords": [ 6 | "html", 7 | "xlsx", 8 | "conversion" 9 | ], 10 | "homepage": "https://github.com/pofider/html-to-xlsx", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:pofider/html-to-xlsx.git" 14 | }, 15 | "license": "MIT", 16 | "author": { 17 | "name": "Jan Blaha", 18 | "email": "jan.blaha@hotmail.com" 19 | }, 20 | "contributors": [ 21 | "BJR Matos (https://github.com/bjrmatos)" 22 | ], 23 | "main": "index.js", 24 | "scripts": { 25 | "test": "mocha --timeout 15000 test/test.js && standard" 26 | }, 27 | "dependencies": { 28 | "jsreport-exceljs": "4.0.1", 29 | "moment": "2.29.4", 30 | "tinycolor2": "1.4.2", 31 | "uuid": "8.3.2" 32 | }, 33 | "devDependencies": { 34 | "chrome-page-eval": "1.3.2", 35 | "mocha": "10.1.0", 36 | "phantom-page-eval": "2.0.1", 37 | "phantomjs": "2.1.7", 38 | "puppeteer": "22.5.0", 39 | "should": "13.2.3", 40 | "standard": "17.0.0", 41 | "xlsx": "0.18.5" 42 | }, 43 | "engines": { 44 | "node": ">=18.15.0" 45 | }, 46 | "standard": { 47 | "env": [ 48 | "node", 49 | "mocha" 50 | ], 51 | "ignore": [ 52 | "lib/scripts/conversionScript.js" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/conversion.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const { v4: uuidv4 } = require('uuid') 6 | const tmpDir = require('os').tmpdir() 7 | const stylesMap = require('./stylesMap') 8 | const tableToXlsx = require('./tableToXlsx') 9 | 10 | const extractTableScriptFn = fs.readFileSync( 11 | path.join(__dirname, './scripts/conversionScript.js') 12 | ).toString() 13 | 14 | module.exports = (opt = {}) => { 15 | const options = { ...opt } 16 | 17 | options.timeout = options.timeout || 10000 18 | options.tmpDir = options.tmpDir || tmpDir 19 | 20 | if (typeof options.extract !== 'function') { 21 | throw new Error('`extract` option must be a function') 22 | } 23 | 24 | if (typeof options.timeout !== 'number') { 25 | throw new Error('`timeout` option must be a number') 26 | } 27 | 28 | const timeout = options.timeout 29 | const currentExtractFn = options.extract 30 | 31 | async function convert (html, extractOptions = {}, xlsxTemplateBuf) { 32 | const id = uuidv4() 33 | 34 | if (html == null) { 35 | throw new Error('required `html` option not specified') 36 | } 37 | 38 | let tables = await currentExtractFn({ 39 | ...extractOptions, 40 | uuid: id, 41 | html, 42 | timeout 43 | }) 44 | 45 | if (tables != null && !Array.isArray(tables)) { 46 | tables = [tables] 47 | } 48 | 49 | if (!tables || tables.length === 0) { 50 | throw new Error('No table element(s) found in html') 51 | } 52 | 53 | const stream = await tableToXlsx(options, tables, xlsxTemplateBuf, id) 54 | 55 | return stream 56 | } 57 | 58 | return convert 59 | } 60 | 61 | module.exports.getScriptFn = () => extractTableScriptFn 62 | 63 | module.exports.getXlsxStyleNames = () => { 64 | return Object.keys(stylesMap) 65 | } 66 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const color = require('tinycolor2') 2 | 3 | const numFmtMap = Object.create(null) 4 | 5 | Object.entries({ 6 | 0: 'general', 7 | 1: '0', 8 | 2: '0.00', 9 | 3: '#,##0', 10 | 4: '#,##0.00', 11 | 9: '0%', 12 | 10: '0.00%', 13 | 11: '0.00e+00', 14 | 12: '# ?/?', 15 | 13: '# ??/??', 16 | 14: 'mm-dd-yy', 17 | 15: 'd-mmm-yy', 18 | 16: 'd-mmm', 19 | 17: 'mmm-yy', 20 | 18: 'h:mm am/pm', 21 | 19: 'h:mm:ss am/pm', 22 | 20: 'h:mm', 23 | 21: 'h:mm:ss', 24 | 22: 'm/d/yy h:mm', 25 | 37: '#,##0 ;(#,##0)', 26 | 38: '#,##0 ;[red](#,##0)', 27 | 39: '#,##0.00;(#,##0.00)', 28 | 40: '#,##0.00;[red](#,##0.00)', 29 | 41: '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)', 30 | 42: '_("$"* #,##0_);_("$* (#,##0);_("$"* "-"_);_(@_)', 31 | 43: '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', 32 | 44: '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)', 33 | 45: 'mm:ss', 34 | 46: '[h]:mm:ss', 35 | 47: 'mmss.0', 36 | 48: '##0.0e+0', 37 | 49: '@' 38 | }).forEach(([key, value]) => { 39 | numFmtMap[key] = value 40 | }) 41 | 42 | function parsePx (value) { 43 | if (!value) { 44 | return 0 45 | } 46 | 47 | if (typeof value === 'number') { 48 | return value 49 | } 50 | 51 | const px = value.match(/([.\d]+)px/i) 52 | 53 | if (px && px.length === 2) { 54 | return parseFloat(px[1], 10) 55 | } 56 | 57 | return 0 58 | } 59 | 60 | function sizeToPx (value) { 61 | if (!value) { 62 | return 0 63 | } 64 | 65 | if (typeof value === 'number') { 66 | return value 67 | } 68 | 69 | const pt = value.match(/([.\d]+)pt/i) 70 | 71 | if (pt && pt.length === 2) { 72 | return parseFloat(pt[1], 10) * 96 / 72 73 | } 74 | 75 | const em = value.match(/([.\d]+)em/i) 76 | 77 | if (em && em.length === 2) { 78 | return parseFloat(em[1], 10) * 16 79 | } 80 | 81 | const px = value.match(/([.\d]+)px/i) 82 | 83 | if (px && px.length === 2) { 84 | return parseFloat(px[1], 10) 85 | } 86 | 87 | const pe = value.match(/([.\d]+)%/i) 88 | 89 | if (pe && pe.length === 2) { 90 | return (parseFloat(pe[1], 10) / 100) * 16 91 | } 92 | 93 | return 0 94 | } 95 | 96 | function sizePxToPt (value) { 97 | const numPx = sizeToPx(value) 98 | 99 | if (numPx > 0) { 100 | return numPx * 72 / 96 101 | } 102 | 103 | return 12 104 | } 105 | 106 | function isColorDefined (c) { 107 | const total = c.length 108 | let result = false 109 | 110 | for (let i = 0; i < total; i++) { 111 | result = c[i] !== '0' 112 | 113 | if (result) { 114 | break 115 | } 116 | } 117 | 118 | return result 119 | } 120 | 121 | function colorToArgb (c) { 122 | const input = Array.isArray(c) 123 | ? { r: c[0], g: c[1], b: c[2], a: c[3] } 124 | : c 125 | 126 | const rgba = color(input).toHex8() 127 | return rgba.substr(6) + rgba.substr(0, 6) 128 | } 129 | 130 | function getBorder (cellInfo, type) { 131 | let color = cellInfo.border[`${type}Color`] 132 | let style = cellInfo.border[`${type}Style`] 133 | let width = cellInfo.border[`${type}Width`] 134 | 135 | if (!color) { 136 | return null 137 | } 138 | 139 | width = sizeToPx(width) 140 | 141 | if (width <= 0) { 142 | return null 143 | } 144 | 145 | color = colorToArgb(color) 146 | 147 | if (style === 'none' || style === 'hidden') { 148 | return { style: null, color } 149 | } 150 | 151 | if (style === 'dashed' || style === 'dotted' || style === 'double') { 152 | return { style, color } 153 | } 154 | 155 | style = 'thin' 156 | 157 | if (width >= 3 && width < 5) { 158 | style = 'medium' 159 | } 160 | 161 | if (width >= 5) { 162 | style = 'thick' 163 | } 164 | 165 | return { style, color } 166 | } 167 | 168 | module.exports.sizePxToPt = sizePxToPt 169 | module.exports.parsePx = parsePx 170 | module.exports.isColorDefined = isColorDefined 171 | module.exports.numFmtMap = numFmtMap 172 | module.exports.colorToArgb = colorToArgb 173 | module.exports.getBorder = getBorder 174 | -------------------------------------------------------------------------------- /lib/stylesMap.js: -------------------------------------------------------------------------------- 1 | const utils = require('./utils') 2 | 3 | module.exports = { 4 | numFmt: (cellInfo) => { 5 | if (cellInfo.formatStr != null) { 6 | return cellInfo.formatStr 7 | } else if (cellInfo.formatEnum != null && utils.numFmtMap[cellInfo.formatEnum] != null) { 8 | return utils.numFmtMap[cellInfo.formatEnum] 9 | } 10 | }, 11 | alignment: (cellInfo) => { 12 | const hMap = { 13 | left: 'left', 14 | right: 'right', 15 | center: 'center', 16 | justify: 'justify' 17 | } 18 | 19 | const vMap = { 20 | top: 'top', 21 | bottom: 'bottom', 22 | middle: 'middle' 23 | } 24 | 25 | const props = [] 26 | 27 | if (cellInfo.horizontalAlign && hMap[cellInfo.horizontalAlign]) { 28 | props.push({ 29 | key: 'horizontal', 30 | value: hMap[cellInfo.horizontalAlign] 31 | }) 32 | } 33 | 34 | if (cellInfo.verticalAlign && vMap[cellInfo.verticalAlign]) { 35 | props.push({ 36 | key: 'vertical', 37 | value: vMap[cellInfo.verticalAlign] 38 | }) 39 | } 40 | 41 | if (cellInfo.wrapText === 'scroll' || cellInfo.wrapText === 'auto') { 42 | props.push({ 43 | key: 'wrapText', 44 | value: true 45 | }) 46 | } 47 | 48 | if (props.length === 0) { 49 | return 50 | } 51 | 52 | return props.reduce((acu, prop) => { 53 | acu[prop.key] = prop.value 54 | return acu 55 | }, {}) 56 | }, 57 | fill: (cellInfo) => { 58 | if (!utils.isColorDefined(cellInfo.backgroundColor)) { 59 | return 60 | } 61 | 62 | return { 63 | type: 'pattern', 64 | pattern: 'solid', 65 | fgColor: { 66 | argb: utils.colorToArgb(cellInfo.backgroundColor) 67 | }, 68 | bgColor: { 69 | argb: utils.colorToArgb(cellInfo.backgroundColor) 70 | } 71 | } 72 | }, 73 | font: (cellInfo) => { 74 | const props = [] 75 | 76 | if (utils.isColorDefined(cellInfo.foregroundColor)) { 77 | props.push({ 78 | key: 'color', 79 | value: { 80 | argb: utils.colorToArgb(cellInfo.foregroundColor) 81 | } 82 | }) 83 | } 84 | 85 | props.push({ 86 | key: 'size', 87 | value: utils.sizePxToPt(cellInfo.fontSize) 88 | }) 89 | 90 | props.push({ 91 | key: 'name', 92 | value: cellInfo.fontFamily != null ? cellInfo.fontFamily : 'Calibri' 93 | }) 94 | 95 | props.push({ 96 | key: 'bold', 97 | value: cellInfo.fontWeight === 'bold' || parseInt(cellInfo.fontWeight, 10) >= 700 98 | }) 99 | 100 | props.push({ 101 | key: 'italic', 102 | value: cellInfo.fontStyle === 'italic' 103 | }) 104 | 105 | if (cellInfo.textDecoration) { 106 | props.push({ 107 | key: 'underline', 108 | value: cellInfo.textDecoration.line === 'underline' 109 | }) 110 | } 111 | 112 | if (cellInfo.textDecoration) { 113 | props.push({ 114 | key: 'strike', 115 | value: cellInfo.textDecoration.line === 'line-through' 116 | }) 117 | } 118 | 119 | if (props.length === 0) { 120 | return 121 | } 122 | 123 | return props.reduce((acu, prop) => { 124 | acu[prop.key] = prop.value 125 | return acu 126 | }, {}) 127 | }, 128 | border: (cellInfo) => { 129 | const props = [] 130 | const borders = ['left', 'right', 'top', 'bottom'] 131 | 132 | borders.forEach((border) => { 133 | const value = utils.getBorder(cellInfo, border) 134 | 135 | if (value) { 136 | props.push({ 137 | key: border, 138 | value: { 139 | ...(value.style != null ? { style: value.style } : {}), 140 | color: { 141 | argb: value.color 142 | } 143 | } 144 | }) 145 | } 146 | }) 147 | 148 | if (props.length === 0) { 149 | return 150 | } 151 | 152 | return props.reduce((acu, prop) => { 153 | acu[prop.key] = prop.value 154 | return acu 155 | }, {}) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/scripts/conversionScript.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | function conversion () { 3 | var tables = document.querySelectorAll('table') 4 | var tablesOutput = [] 5 | var i 6 | 7 | if (tables.length === 0) { 8 | return tablesOutput 9 | } 10 | 11 | var tableNames = [] 12 | var tablesWithoutName = [] 13 | 14 | function evaluateRow (row) { 15 | var rowResult = [] 16 | var isPlaceholder = row.dataset != null ? row.dataset.rowsPlaceholder != null : false 17 | 18 | if (isPlaceholder) { 19 | return { id: row.id, files: row.dataset.files.split(',') } 20 | } 21 | 22 | for (var c = 0, m = row.cells.length; c < m; c++) { 23 | var cell = row.cells[c] 24 | var cs = document.defaultView.getComputedStyle(cell, null) 25 | var type = cell.dataset.cellType != null && cell.dataset.cellType !== '' ? cell.dataset.cellType.toLowerCase() : undefined 26 | var formatStr = cell.dataset.cellFormatStr != null ? cell.dataset.cellFormatStr : undefined 27 | var formatEnum = cell.dataset.cellFormatEnum != null && !isNaN(parseInt(cell.dataset.cellFormatEnum, 10)) ? parseInt(cell.dataset.cellFormatEnum, 10) : undefined 28 | 29 | rowResult.push({ 30 | type: type, 31 | // returns the html inside the td element with special html characters like "&" escaped to & 32 | value: cell.innerHTML, 33 | // returns just the real text inside the td element with special html characters like "&" left as it is 34 | valueText: cell.innerText, 35 | formatStr: formatStr, 36 | formatEnum: formatEnum, 37 | backgroundColor: cs.getPropertyValue('background-color').match(/\d+/g), 38 | foregroundColor: cs.getPropertyValue('color').match(/\d+/g), 39 | fontFamily: cs.getPropertyValue('font-family').replace(/["']/g, ''), 40 | fontSize: cs.getPropertyValue('font-size'), 41 | fontStyle: cs.getPropertyValue('font-style'), 42 | fontWeight: cs.getPropertyValue('font-weight'), 43 | textDecoration: { 44 | line: cs.getPropertyValue('text-decoration').split(' ')[0] 45 | }, 46 | verticalAlign: cs.getPropertyValue('vertical-align'), 47 | horizontalAlign: cs.getPropertyValue('text-align'), 48 | wrapText: cs.getPropertyValue('overflow'), 49 | width: cell.clientWidth, 50 | height: cell.clientHeight, 51 | rowspan: cell.rowSpan, 52 | colspan: cell.colSpan, 53 | border: { 54 | top: cs.getPropertyValue('border-top-style'), 55 | topColor: cs.getPropertyValue('border-top-color').match(/\d+/g), 56 | topStyle: cs.getPropertyValue('border-top-style'), 57 | topWidth: cs.getPropertyValue('border-top-width'), 58 | right: cs.getPropertyValue('border-right-style'), 59 | rightColor: cs.getPropertyValue('border-right-color').match(/\d+/g), 60 | rightStyle: cs.getPropertyValue('border-right-style'), 61 | rightWidth: cs.getPropertyValue('border-right-width'), 62 | bottom: cs.getPropertyValue('border-bottom-style'), 63 | bottomColor: cs.getPropertyValue('border-bottom-color').match(/\d+/g), 64 | bottomStyle: cs.getPropertyValue('border-bottom-style'), 65 | bottomWidth: cs.getPropertyValue('border-bottom-width'), 66 | left: cs.getPropertyValue('border-left-style'), 67 | leftColor: cs.getPropertyValue('border-left-color').match(/\d+/g), 68 | leftStyle: cs.getPropertyValue('border-left-style'), 69 | leftWidth: cs.getPropertyValue('border-left-width') 70 | } 71 | }) 72 | } 73 | 74 | return rowResult 75 | } 76 | 77 | for (i = 0; i < tables.length; i++) { 78 | var table = tables[i] 79 | var tableOut = { rows: [] } 80 | var nameAttr = table.getAttribute('name') 81 | var dataSheetName = table.dataset.sheetName 82 | var dataIgnoreName = table.dataset.ignoreSheetName 83 | 84 | if (dataIgnoreName == null) { 85 | if (dataSheetName != null) { 86 | tableOut.name = dataSheetName 87 | } else if (nameAttr != null) { 88 | tableOut.name = nameAttr 89 | } 90 | } 91 | 92 | if (tableOut.name == null) { 93 | tablesWithoutName.push(tableOut) 94 | } else { 95 | tableNames.push(tableOut.name) 96 | } 97 | 98 | for (var r = 0, n = table.rows.length; r < n; r++) { 99 | tableOut.rows.push(evaluateRow(table.rows[r])) 100 | table.evaluateRow = evaluateRow 101 | } 102 | 103 | tablesOutput.push(tableOut) 104 | } 105 | 106 | var currentIndex = 0 107 | var targetTableName 108 | 109 | for (i = 0; i < tablesWithoutName.length; i++) { 110 | do { 111 | currentIndex++ 112 | targetTableName = 'Sheet' + currentIndex 113 | } while (tableNames.indexOf(targetTableName) !== -1) 114 | 115 | tablesWithoutName[i].name = targetTableName 116 | } 117 | 118 | if (tablesOutput.length === 1) { 119 | return tablesOutput[0] 120 | } 121 | 122 | return tablesOutput 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **⚠️ This repository has been moved to the monorepo [jsreport/jsreport](https://github.com/jsreport/jsreport)** 2 | -- 3 | 4 | # html-to-xlsx 5 | [![NPM Version](http://img.shields.io/npm/v/html-to-xlsx.svg?style=flat-square)](https://npmjs.com/package/html-to-xlsx) 6 | [![License](http://img.shields.io/npm/l/html-to-xlsx.svg?style=flat-square)](http://opensource.org/licenses/MIT) 7 | [![Build Status](https://travis-ci.org/pofider/html-to-xlsx.png?branch=master)](https://travis-ci.org/pofider/html-to-xlsx) 8 | 9 | > **node.js html to xlsx transformation** 10 | 11 | Transformation only supports html table and several basic style properties. No images or charts are currently supported. 12 | 13 | ## Usage 14 | 15 | ```js 16 | const util = require('util') 17 | const fs = require('fs') 18 | const conversionFactory = require('html-to-xlsx') 19 | const puppeteer = require('puppeteer') 20 | const chromeEval = require('chrome-page-eval')({ puppeteer }) 21 | const writeFileAsync = util.promisify(fs.writeFile) 22 | 23 | const conversion = conversionFactory({ 24 | extract: async ({ html, ...restOptions }) => { 25 | const tmpHtmlPath = path.join('/path/to/temp', 'input.html') 26 | 27 | await writeFileAsync(tmpHtmlPath, html) 28 | 29 | const result = await chromeEval({ 30 | ...restOptions, 31 | html: tmpHtmlPath, 32 | scriptFn: conversionFactory.getScriptFn() 33 | }) 34 | 35 | const tables = Array.isArray(result) ? result : [result] 36 | 37 | return tables.map((table) => ({ 38 | name: table.name, 39 | getRows: async (rowCb) => { 40 | table.rows.forEach((row) => { 41 | rowCb(row) 42 | }) 43 | }, 44 | rowsCount: table.rows.length 45 | })) 46 | } 47 | }) 48 | 49 | async function run () { 50 | const stream = await conversion(`
cell value
`) 51 | 52 | stream.pipe(fs.createWriteStream('/path/to/output.xlsx')) 53 | } 54 | 55 | run() 56 | ``` 57 | 58 | ## Supported properties 59 | - `background-color` - cell background color 60 | - `color` - cell foreground color 61 | - `border-left-style` - as well as positions will be transformed into excel cells borders 62 | - `text-align` - text horizontal align in the excel cell 63 | - `vertical-align` - vertical align in the excel cell 64 | - `width` - the excel column will get the highest width, it can be little bit inaccurate because of pixel to excel points conversion 65 | - `height` - the excel row will get the highest height 66 | - `font-size` - font size 67 | - `colspan` - numeric value that merges current column with columns to the right 68 | - `rowspan` - numeric value that merges current row with rows below. 69 | - `overflow` - the excel cell will have text wrap enabled if this is set to `scroll` or `auto`. 70 | 71 | ## Constructor options 72 | 73 | ```js 74 | const conversionFactory = require('html-to-xlsx') 75 | const puppeteer = require('puppeteer') 76 | const chromeEval = require('chrome-page-eval')({ puppeteer }) 77 | const conversion = conversionFactory({ /*[constructor options here]*/}) 78 | ``` 79 | 80 | - `extract` **function** **[required]** - a function that receives some input (an html file path and a script) and should return some data after been evaluated the html passed. the input that the function receives is: 81 | ```js 82 | { 83 | html: , 84 | scriptFn: , 85 | timeout: