html-to-xlsx
Advanced tools
Comparing version
@@ -8,337 +8,8 @@ 'use strict' | ||
const tmpDir = require('os').tmpdir() | ||
const excelbuilder = require('msexcel-builder-extended') | ||
const XlsxPopulate = require('xlsx-populate') | ||
const stylesMap = require('./stylesMap') | ||
const tableToXlsx = require('./tableToXlsx') | ||
const readFileAsync = util.promisify(fs.readFile) | ||
const writeFileAsync = util.promisify(fs.writeFile) | ||
function componentToHex (c) { | ||
const hex = parseInt(c).toString(16) | ||
return hex.length === 1 ? '0' + hex : hex | ||
} | ||
function rgbToHex (c) { | ||
return componentToHex(c[0]) + componentToHex(c[1]) + componentToHex(c[2]) | ||
} | ||
function isColorDefined (c) { | ||
return c[0] !== '0' || c[1] !== '0' || c[2] !== '0' || c[3] !== '0' | ||
} | ||
function getTotalCells (rows) { | ||
let max = 0 | ||
rows.forEach((row, idx) => { | ||
let cellsCount = row.length | ||
let maxRowSpan = 0 | ||
const allCellsAreRowSpan = row.filter(c => c.rowspan > 1).length === row.length | ||
maxRowSpan = row.reduce((acu, cell) => { | ||
if (cell.rowspan && cell.rowspan > acu) { | ||
acu = cell.rowspan | ||
} | ||
return acu | ||
}, 0) | ||
row.forEach((cell) => { | ||
if (cell.colspan && cell.colspan > 1) { | ||
cellsCount += (cell.colspan) - 1 | ||
} | ||
}) | ||
if (maxRowSpan > 1 && (row.length === 1 || allCellsAreRowSpan)) { | ||
const rowsToMerge = maxRowSpan - 1 | ||
let mergedMaxCells = 0 | ||
for (let i = 0; i < rowsToMerge; i++) { | ||
const nextRowIndex = idx + (i + 1) | ||
if (rows[nextRowIndex] == null) { | ||
continue | ||
} | ||
if (rows[nextRowIndex].length > mergedMaxCells) { | ||
mergedMaxCells = rows[nextRowIndex].length | ||
} | ||
} | ||
cellsCount += mergedMaxCells | ||
} | ||
if (cellsCount > max) { | ||
max = cellsCount | ||
} | ||
}) | ||
return max | ||
} | ||
function getTotalRows (rows) { | ||
let rowCount = 0 | ||
let rowsToMerge = 0 | ||
rows.forEach((row) => { | ||
let maxRowSpan = 0 | ||
const allCellsAreRowSpan = row.filter(c => c.rowspan > 1).length === row.length | ||
maxRowSpan = row.reduce((acu, cell) => { | ||
if (cell.rowspan && cell.rowspan > acu) { | ||
acu = cell.rowspan | ||
} | ||
return acu | ||
}, 0) | ||
if (rowsToMerge > 0) { | ||
rowsToMerge-- | ||
} else { | ||
if (maxRowSpan > 1) { | ||
rowsToMerge += maxRowSpan - 1 | ||
rowCount += rowsToMerge | ||
if (row.length > 1 && !allCellsAreRowSpan) { | ||
rowCount++ | ||
} | ||
} else { | ||
rowCount++ | ||
} | ||
} | ||
}) | ||
return rowCount | ||
} | ||
function getBorderStyle (border) { | ||
if (border === 'none') return undefined | ||
if (border === 'solid') return 'thin' | ||
if (border === 'double') return 'double' | ||
return undefined | ||
} | ||
function tableToXlsx (options, table, id, cb) { | ||
const workbook = excelbuilder.createWorkbook( | ||
options.tmpDir, | ||
`${id}.xlsx`, | ||
options | ||
) | ||
const totalCells = getTotalCells(table.rows) | ||
const totalRows = getTotalRows(table.rows) | ||
const sheet1 = workbook.createSheet( | ||
'sheet1', | ||
totalCells, | ||
totalRows | ||
) | ||
const maxWidths = [] | ||
let currRow = 0 | ||
let rowsToMerge = 0 | ||
let rowOffset = [] | ||
for (let i = 0; i < table.rows.length; i++) { | ||
// set at the row level column offsets and column position | ||
let maxHeight = 0 | ||
let maxRowSpan = 0 | ||
const tmpOffsets = [] | ||
let currCol = 0 | ||
let colOffset = 0 | ||
let tmpCol = 0 | ||
let mergedRow = false | ||
const cellsCount = table.rows[i].length | ||
const allCellsAreRowSpan = table.rows[i].filter(c => c.rowspan > 1).length === cellsCount | ||
if (rowsToMerge > 0) { | ||
mergedRow = true | ||
currRow-- | ||
rowsToMerge-- | ||
} | ||
// clean out offsets that are no longer valid | ||
rowOffset = rowOffset.filter((offset) => { | ||
// eslint-disable-next-line no-unneeded-ternary | ||
return offset.stop <= currRow ? false : true | ||
}) | ||
// Is the current column in the offset list? | ||
rowOffset.forEach((item) => { | ||
// if so add an offset to shift the column start | ||
if (currRow <= item.stop) { | ||
colOffset += item.colOffset || 1 | ||
} | ||
}) | ||
let cell | ||
// column iterator | ||
for (let j = 0; j < table.rows[i].length; j++) { | ||
cell = table.rows[i][j] | ||
// On the start of each row, reset the column counters | ||
// Use tmpCol to manipulate the column value | ||
currCol = j === 0 ? 1 : currCol + 1 | ||
tmpCol = currCol + colOffset | ||
// don't take into account height of merged cells (rowspan) for max height. | ||
// this is the behavior of browsers too when rendering tables. | ||
// see issue #20 for more info | ||
if (cell.height > maxHeight && (cell.rowspan == null || cell.rowspan <= 1)) { | ||
maxHeight = cell.height | ||
} | ||
// don't take into account width of merged cells for max width, | ||
// and don't apply width for merged cells. | ||
// this is the behavior of browsers too when rendering tables. | ||
// see issue #19, #24 for more info | ||
if (cell.colspan == null || cell.colspan <= 1) { | ||
if (cell.width > (maxWidths[j] || 0)) { | ||
sheet1.width(currCol, cell.width / 7) | ||
maxWidths[j] = cell.width | ||
} | ||
} | ||
sheet1.set( | ||
tmpCol, | ||
currRow + 1, | ||
cell.value | ||
? cell.value.replace(/&(?!amp;)/g, '&').replace(/&(?!amp;)/g, '&') | ||
: cell.value | ||
) | ||
sheet1.align(tmpCol, currRow + 1, cell.horizontalAlign) | ||
sheet1.valign( | ||
tmpCol, | ||
currRow + 1, | ||
cell.verticalAlign === 'middle' ? 'center' : cell.verticalAlign | ||
) | ||
sheet1.wrap(tmpCol, currRow + 1, cell.wrapText === 'scroll') | ||
if (isColorDefined(cell.backgroundColor)) { | ||
sheet1.fill(tmpCol, currRow + 1, { | ||
type: 'solid', | ||
fgColor: 'FF' + rgbToHex(cell.backgroundColor), | ||
bgColor: '64' | ||
}) | ||
} | ||
sheet1.font(tmpCol, currRow + 1, { | ||
family: '3', | ||
scheme: 'minor', | ||
sz: parseInt(cell.fontSize.replace('px', '')) * 18 / 24, | ||
bold: | ||
cell.fontWeight === 'bold' || parseInt(cell.fontWeight, 10) >= 700, | ||
color: isColorDefined(cell.foregroundColor) | ||
? 'FF' + rgbToHex(cell.foregroundColor) | ||
: undefined | ||
}) | ||
sheet1.border(tmpCol, currRow + 1, { | ||
left: getBorderStyle(cell.border.left), | ||
top: getBorderStyle(cell.border.top), | ||
right: getBorderStyle(cell.border.right), | ||
bottom: getBorderStyle(cell.border.bottom) | ||
}) | ||
// Now that we have done all of the formatting to the cell, see if the row needs merged. | ||
// Note that calling merge twice on the same cell causes Excel to be unreadable. | ||
if (cell.rowspan > 1) { | ||
// address colspan at the same time as rowspan | ||
let coloffset = cell.colspan > 1 ? cell.colspan - 1 : 0 | ||
let endRow = (currRow + cell.rowspan) - ((cellsCount === 1 || allCellsAreRowSpan) ? 1 : 0) | ||
sheet1.merge( | ||
{ col: tmpCol, row: currRow + 1 }, | ||
{ col: tmpCol + coloffset, row: endRow } | ||
) | ||
// store the rowspan for later use to shift over the column starting point | ||
tmpOffsets.push({ | ||
col: tmpCol, | ||
stop: endRow, | ||
colOffset: cell.colspan | ||
}) | ||
currCol += cell.colspan - 1 | ||
for (let k = currRow + 1; k <= cell.rowspan; k++) { | ||
sheet1.border(k + 1, currRow + cell.colspan, { | ||
left: getBorderStyle(cell.border.left), | ||
top: getBorderStyle(cell.border.top), | ||
right: getBorderStyle(cell.border.right), | ||
bottom: getBorderStyle(cell.border.bottom) | ||
}) | ||
} | ||
if (cell.rowspan > maxRowSpan) { | ||
maxRowSpan = cell.rowspan | ||
} | ||
} | ||
// If we already did rowspan, we did the colspan at the same time so this only does colspan. | ||
// No need to store the colspan as that doesn't carry over to another row | ||
if (cell.colspan > 1 && cell.rowspan === 1) { | ||
let coloffset = cell.colspan > 1 ? cell.colspan - 1 : 0 | ||
sheet1.merge( | ||
{ col: tmpCol, row: currRow + 1 }, | ||
{ col: tmpCol + coloffset, row: currRow + 1 } | ||
) | ||
currCol += cell.colspan - 1 | ||
for (let k = tmpCol; k <= cell.colspan; k++) { | ||
sheet1.border(k + 1, currRow + 1, { | ||
left: getBorderStyle(cell.border.left), | ||
top: getBorderStyle(cell.border.top), | ||
right: getBorderStyle(cell.border.right), | ||
bottom: getBorderStyle(cell.border.bottom) | ||
}) | ||
} | ||
} | ||
} | ||
sheet1.height(currRow + 1, maxHeight * 18 / 24) | ||
if (!cell) { | ||
throw new Error( | ||
'Cell not found, make sure there are td elements inside tr' | ||
) | ||
} | ||
if (mergedRow && rowsToMerge > 0) { | ||
// if row was merged one we restore its index | ||
currRow += 1 | ||
} | ||
currRow += 1 | ||
if (maxRowSpan > 1) { | ||
rowsToMerge = maxRowSpan - 1 | ||
if (cellsCount > 1 && !allCellsAreRowSpan) { | ||
currRow++ | ||
} | ||
} | ||
rowOffset = rowOffset.concat(tmpOffsets) | ||
} | ||
return new Promise((resolve, reject) => { | ||
workbook.save((err) => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
resolve( | ||
fs.createReadStream(path.join(options.tmpDir, `${id}.xlsx`)) | ||
) | ||
}) | ||
}) | ||
} | ||
module.exports = (opt = {}) => { | ||
@@ -377,3 +48,3 @@ const options = { ...opt } | ||
const table = await currentExtractFn({ | ||
const tables = await currentExtractFn({ | ||
...convertOptions, | ||
@@ -385,4 +56,10 @@ html: htmlPath, | ||
const stream = await tableToXlsx(options, table, id) | ||
let stream | ||
if (!tables || (Array.isArray(tables) && tables.length === 0)) { | ||
throw new Error('No table element(s) found in html') | ||
} | ||
stream = await tableToXlsx(options, tables, id) | ||
return stream | ||
@@ -393,1 +70,7 @@ } | ||
} | ||
module.exports.getXlsxStyleNames = () => { | ||
return Object.keys(stylesMap) | ||
} | ||
module.exports.XlsxPopulate = XlsxPopulate |
// eslint-disable-next-line no-unused-vars | ||
function conversion () { | ||
var tableOut = { rows: [] } | ||
var table = document.querySelector('table') | ||
var tables = document.querySelectorAll('table') | ||
var tablesOutput = [] | ||
var i | ||
if (!table) { | ||
return tableOut | ||
if (tables.length === 0) { | ||
return tablesOutput | ||
} | ||
for (var r = 0, n = table.rows.length; r < n; r++) { | ||
var row = [] | ||
tableOut.rows.push(row) | ||
for (i = 0; i < tables.length; i++) { | ||
var table = tables[i] | ||
var tableOut = { rows: [] } | ||
var nameAttr = table.getAttribute('name') | ||
var dataSheetName = table.dataset.sheetName | ||
var dataIgnoreName = table.dataset.ignoreSheetName | ||
for (var c = 0, m = table.rows[r].cells.length; c < m; c++) { | ||
var cell = table.rows[r].cells[c] | ||
var cs = document.defaultView.getComputedStyle(cell, null) | ||
if (dataIgnoreName == null) { | ||
if (dataSheetName != null) { | ||
tableOut.name = dataSheetName | ||
} else if (nameAttr != null) { | ||
tableOut.name = nameAttr | ||
} | ||
} | ||
row.push({ | ||
value: cell.innerHTML, | ||
backgroundColor: cs.getPropertyValue('background-color').match(/\d+/g), | ||
foregroundColor: cs.getPropertyValue('color').match(/\d+/g), | ||
fontSize: cs.getPropertyValue('font-size'), | ||
fontWeight: cs.getPropertyValue('font-weight'), | ||
verticalAlign: cs.getPropertyValue('vertical-align'), | ||
horizontalAlign: cs.getPropertyValue('text-align'), | ||
wrapText: cs.getPropertyValue('overflow'), | ||
width: cell.clientWidth, | ||
height: cell.clientHeight, | ||
rowspan: cell.rowSpan, | ||
colspan: cell.colSpan, | ||
border: { | ||
top: cs.getPropertyValue('border-top-style'), | ||
right: cs.getPropertyValue('border-right-style'), | ||
bottom: cs.getPropertyValue('border-bottom-style'), | ||
left: cs.getPropertyValue('border-left-style'), | ||
width: cs.getPropertyValue('border-width-style') | ||
} | ||
}) | ||
for (var r = 0, n = table.rows.length; r < n; r++) { | ||
var row = [] | ||
tableOut.rows.push(row) | ||
for (var c = 0, m = table.rows[r].cells.length; c < m; c++) { | ||
var cell = table.rows[r].cells[c] | ||
var cs = document.defaultView.getComputedStyle(cell, null) | ||
var type = cell.dataset.cellType != null && cell.dataset.cellType !== '' ? cell.dataset.cellType.toLowerCase() : undefined | ||
var formatStr = cell.dataset.cellFormatStr != null ? cell.dataset.cellFormatStr : undefined | ||
var formatEnum = cell.dataset.cellFormatEnum != null && !isNaN(parseInt(cell.dataset.cellFormatEnum, 10)) ? parseInt(cell.dataset.cellFormatEnum, 10) : undefined | ||
row.push({ | ||
type: type, | ||
// returns the html inside the td element with special html characters like "&" escaped to & | ||
value: cell.innerHTML, | ||
// returns just the real text inside the td element with special html characters like "&" left as it is | ||
valueText: cell.innerText, | ||
formatStr: formatStr, | ||
formatEnum: formatEnum, | ||
backgroundColor: cs.getPropertyValue('background-color').match(/\d+/g), | ||
foregroundColor: cs.getPropertyValue('color').match(/\d+/g), | ||
fontFamily: cs.getPropertyValue('font-family').replace(/["']/g, ''), | ||
fontSize: cs.getPropertyValue('font-size'), | ||
fontStyle: cs.getPropertyValue('font-style'), | ||
fontWeight: cs.getPropertyValue('font-weight'), | ||
textDecoration: { | ||
line: cs.getPropertyValue('text-decoration').split(' ')[0] | ||
}, | ||
verticalAlign: cs.getPropertyValue('vertical-align'), | ||
horizontalAlign: cs.getPropertyValue('text-align'), | ||
wrapText: cs.getPropertyValue('overflow'), | ||
width: cell.clientWidth, | ||
height: cell.clientHeight, | ||
rowspan: cell.rowSpan, | ||
colspan: cell.colSpan, | ||
border: { | ||
top: cs.getPropertyValue('border-top-style'), | ||
topColor: cs.getPropertyValue('border-top-color').match(/\d+/g), | ||
topStyle: cs.getPropertyValue('border-top-style'), | ||
topWidth: cs.getPropertyValue('border-top-width'), | ||
right: cs.getPropertyValue('border-right-style'), | ||
rightColor: cs.getPropertyValue('border-right-color').match(/\d+/g), | ||
rightStyle: cs.getPropertyValue('border-right-style'), | ||
rightWidth: cs.getPropertyValue('border-right-width'), | ||
bottom: cs.getPropertyValue('border-bottom-style'), | ||
bottomColor: cs.getPropertyValue('border-bottom-color').match(/\d+/g), | ||
bottomStyle: cs.getPropertyValue('border-bottom-style'), | ||
bottomWidth: cs.getPropertyValue('border-bottom-width'), | ||
left: cs.getPropertyValue('border-left-style'), | ||
leftColor: cs.getPropertyValue('border-left-color').match(/\d+/g), | ||
leftStyle: cs.getPropertyValue('border-left-style'), | ||
leftWidth: cs.getPropertyValue('border-left-width') | ||
} | ||
}) | ||
} | ||
} | ||
tablesOutput.push(tableOut) | ||
} | ||
return tableOut | ||
if (tablesOutput.length === 1) { | ||
return tablesOutput[0] | ||
} | ||
return tablesOutput | ||
} |
{ | ||
"name": "html-to-xlsx", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"author": { | ||
@@ -35,14 +35,16 @@ "name": "Jan Blaha", | ||
"dependencies": { | ||
"msexcel-builder-extended": "0.0.8", | ||
"uuid": "3.2.1" | ||
"moment": "2.22.1", | ||
"tinycolor2": "1.4.1", | ||
"uuid": "3.2.1", | ||
"xlsx-populate": "1.18.0" | ||
}, | ||
"devDependencies": { | ||
"babel-eslint": "8.2.2", | ||
"chrome-page-eval": "1.0.0", | ||
"chrome-page-eval": "1.2.0", | ||
"eslint": "4.18.2", | ||
"eslint-plugin-babel": "4.1.2", | ||
"mocha": "5.0.2", | ||
"phantom-page-eval": "1.0.1", | ||
"phantom-page-eval": "1.2.0", | ||
"phantomjs": "1.9.17", | ||
"puppeteer": "1.0.0", | ||
"puppeteer": "1.11.0", | ||
"should": "13.2.1", | ||
@@ -53,4 +55,4 @@ "standard": "11.0.0", | ||
"scripts": { | ||
"test": "mocha --timeout 10000 test/test.js && standard" | ||
"test": "mocha --timeout 15000 test/test.js && standard" | ||
} | ||
} |
822
test/test.js
@@ -29,36 +29,2 @@ const should = require('should') | ||
async function createHtmlFile (html) { | ||
const outputPath = path.join(tmpDir, `${uuid()}.html`) | ||
await writeFileAsync(outputPath, html) | ||
return outputPath | ||
} | ||
function rmDir (dirPath) { | ||
if (!fs.existsSync(dirPath)) { | ||
fs.mkdirSync(dirPath) | ||
} | ||
let files | ||
try { | ||
files = fs.readdirSync(dirPath) | ||
} catch (e) { | ||
return | ||
} | ||
if (files.length > 0) { | ||
for (let i = 0; i < files.length; i++) { | ||
let filePath = `${dirPath}/${files[i]}` | ||
try { | ||
if (fs.statSync(filePath).isFile()) { | ||
fs.unlinkSync(filePath) | ||
} | ||
} catch (e) { } | ||
} | ||
} | ||
} | ||
describe('html extraction', () => { | ||
@@ -78,5 +44,11 @@ beforeEach(() => { | ||
function common (pageEval) { | ||
it('should build simple table', async () => { | ||
it('should parse simple table', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(`<table><tr><td>1</td></tr></table>`), | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td>1</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
@@ -90,2 +62,79 @@ }) | ||
it('should parse value', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td>node.js & javascript</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows.should.have.length(1) | ||
table.rows[0].should.have.length(1) | ||
table.rows[0][0].value.should.be.eql('node.js & javascript') | ||
}) | ||
it('should parse un-escaped value', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td>node.js & javascript</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows.should.have.length(1) | ||
table.rows[0].should.have.length(1) | ||
table.rows[0][0].valueText.should.be.eql('node.js & javascript') | ||
}) | ||
it('should parse cell data type', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td data-cell-type="number">10</td> | ||
<td data-cell-type="boolean">1</td> | ||
<td data-cell-type="date">2019-01-22</td> | ||
<td data-cell-type="datetime">2019-01-22T17:31:36.242Z</td> | ||
<td data-cell-type="formula">=SUM(A1, B1)</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows.should.have.length(1) | ||
table.rows[0].should.have.length(5) | ||
table.rows[0][0].type = 'number' | ||
table.rows[0][1].type = 'boolean' | ||
table.rows[0][2].type = 'date' | ||
table.rows[0][3].type = 'datetime' | ||
table.rows[0][4].type = 'formula' | ||
}) | ||
it('should parse format str and enum', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td data-cell-type="number" data-cell-format-str="0.00">10</td> | ||
<td data-cell-type="number" data-cell-format-enum="3">100000</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows.should.have.length(1) | ||
table.rows[0][0].formatStr = '0.00' | ||
table.rows[0][1].formatEnum = 3 | ||
}) | ||
it('should parse background color', async () => { | ||
@@ -109,2 +158,19 @@ const table = await pageEval({ | ||
it('should parse fontFamily', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td style='font-family:Calibri'>1</td> | ||
<td style='font-family: "Times New Roman"'>2</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows[0][0].fontFamily.should.be.eql('Calibri') | ||
table.rows[0][1].fontFamily.should.be.eql('Times New Roman') | ||
}) | ||
it('should parse fontsize', async () => { | ||
@@ -157,3 +223,9 @@ const table = await pageEval({ | ||
const table = await pageEval({ | ||
html: await createHtmlFile(`<table><tr><td style='border-style:solid;'>1</td></tr></table>`), | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td style='border-style:solid;'>1</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
@@ -168,2 +240,19 @@ }) | ||
it('should parse complex border', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td style='border-left: 1px solid red;'>1</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows[0][0].border.leftColor.should.be.eql(['255', '0', '0']) | ||
table.rows[0][0].border.leftWidth.should.be.eql('1px') | ||
table.rows[0][0].border.leftStyle.should.be.eql('solid') | ||
}) | ||
it('should parse overflow', async () => { | ||
@@ -178,2 +267,19 @@ const table = await pageEval({ | ||
it('should parse textDecoration', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<td style='text-decoration: underline'> | ||
1234 | ||
</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows[0][0].textDecoration.line.should.be.eql('underline') | ||
}) | ||
it('should parse backgroud color from styles with line endings', async () => { | ||
@@ -189,4 +295,2 @@ const table = await pageEval({ | ||
it('should work for long tables', async function () { | ||
this.timeout(7000) | ||
let rows = '' | ||
@@ -232,8 +336,11 @@ | ||
<tr> | ||
<td rowspan='3'>Row 1 Col 1</td><td>Row 1 Col 2</td> | ||
<td>Row 1 Col 3</td><td>Row 1 Col 4</td> | ||
<td rowspan='3'>Row 1 Col 1</td> | ||
<td>Row 1 Col 2</td> | ||
<td>Row 1 Col 3</td> | ||
<td>Row 1 Col 4</td> | ||
</tr> | ||
<tr> | ||
<td rowspan='2'>Row 2 Col 1</td> | ||
<td rowspan='2'>Row 2 Col 2</td><td>Row 2 Col 3</td> | ||
<td rowspan='2'>Row 2 Col 2</td> | ||
<td>Row 2 Col 3</td> | ||
</tr> | ||
@@ -252,2 +359,26 @@ <tr> | ||
}) | ||
it('should parse th elements', async () => { | ||
const table = await pageEval({ | ||
html: await createHtmlFile(` | ||
<table> | ||
<tr> | ||
<th>col1</th> | ||
<th>col2</th> | ||
</tr> | ||
<tr> | ||
<td>1</td> | ||
<td>2</td> | ||
</tr> | ||
</table> | ||
`), | ||
scriptFn: extractTableScriptFn | ||
}) | ||
table.rows.should.have.length(2) | ||
table.rows[0][0].value.should.be.eql('col1') | ||
table.rows[0][1].value.should.be.eql('col2') | ||
table.rows[1][0].value.should.be.eql('1') | ||
table.rows[1][1].value.should.be.eql('2') | ||
}) | ||
} | ||
@@ -277,3 +408,9 @@ }) | ||
it('should not fail', async () => { | ||
const stream = await conversion('<table><tr><td>hello</td></tr>') | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td>hello</td> | ||
</tr> | ||
</table | ||
`) | ||
@@ -283,4 +420,170 @@ stream.should.have.property('readable') | ||
it('default sheet name should be Sheet1', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td>hello</td> | ||
</tr> | ||
</table | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.SheetNames[0]).be.eql('Sheet1') | ||
}) | ||
it('shoule be able to set custom sheet name', async () => { | ||
const parseXlsx = (xlsxStream) => { | ||
return new Promise((resolve, reject) => { | ||
const bufs = [] | ||
xlsxStream.on('error', reject) | ||
xlsxStream.on('data', (d) => { bufs.push(d) }) | ||
xlsxStream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
} | ||
let stream = await conversion(` | ||
<table name="custom"> | ||
<tr> | ||
<td>1</td> | ||
</tr> | ||
</table> | ||
`) | ||
let parsedXlsx = await parseXlsx(stream) | ||
should(parsedXlsx.SheetNames[0]).be.eql('custom') | ||
stream = await conversion(` | ||
<table data-sheet-name="custom2"> | ||
<tr> | ||
<td>1</td> | ||
</tr> | ||
</table> | ||
`) | ||
parsedXlsx = await parseXlsx(stream) | ||
should(parsedXlsx.SheetNames[0]).be.eql('custom2') | ||
}) | ||
it('should be able to set cell with datatypes', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td data-cell-type="number">10</td> | ||
<td data-cell-type="number">10</td> | ||
<td data-cell-type="boolean">1</td> | ||
<td data-cell-type="date">2019-01-22</td> | ||
<td data-cell-type="datetime">2019-01-22T17:31:36.000-05:00</td> | ||
<td data-cell-type="formula">=SUM(A1, B1)</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql(10) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql(10) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C1'].v).be.eql(true) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D1'].v).be.Number() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['E1'].v).be.Number() | ||
}) | ||
it('should be able to set cell format', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td data-cell-type="number" data-cell-format-str="0.00">10</td> | ||
<td data-cell-type="number" data-cell-format-enum="3">100000</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql(10) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].w).be.eql('10.00') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql(100000) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].w).be.eql('100,000') | ||
}) | ||
it('should work with th elements', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<th>col1</th> | ||
<th>col2</th> | ||
</tr> | ||
<tr> | ||
<td>1</td> | ||
<td>2</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('col1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql('col2') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A2'].v).be.eql('1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B2'].v).be.eql('2') | ||
}) | ||
it('should not fail when last cell of a row has rowspan', async () => { | ||
const stream = await conversion(`<table><tr><td rowspan="2">Cell RowSpan</td></tr><tr><td>Foo</td></tr></table>`) | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td rowspan="2">Cell RowSpan</td> | ||
</tr> | ||
<tr> | ||
<td>Foo</td> | ||
</tr> | ||
</table> | ||
`) | ||
@@ -290,3 +593,3 @@ stream.should.have.property('readable') | ||
it('should work when using special rowspan layout #1', async () => { | ||
it('should work when using special rowspan layout #1 (row with just one cell)', async () => { | ||
const stream = await conversion(` | ||
@@ -326,5 +629,10 @@ <table> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C2'].v).be.eql('World') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
}) | ||
it('should work when using special rowspan layout #2', async () => { | ||
it('should work when using special rowspan layout #2 (row with just one cell, more rows)', async () => { | ||
const stream = await conversion(` | ||
@@ -334,2 +642,50 @@ <table> | ||
<td rowspan="3">ROWSPAN 3</td> | ||
</tr> | ||
<tr> | ||
<td>Ipsum</td> | ||
<td>Data</td> | ||
</tr> | ||
<tr> | ||
<td>Hello</td> | ||
<td>World</td> | ||
</tr> | ||
<tr> | ||
<td>Something</td> | ||
<td>Else</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('ROWSPAN 3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A2']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql('Ipsum') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C1'].v).be.eql('Data') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B2'].v).be.eql('Hello') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C2'].v).be.eql('World') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A3'].v).be.eql('Something') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B3'].v).be.eql('Else') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
}) | ||
it('should work when using special rowspan layout #3 (row with normal cells and first cell with rowspan)', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td rowspan="3">ROWSPAN 3</td> | ||
<td>Header 2</td> | ||
@@ -370,5 +726,10 @@ <td>Header 3</td> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C3'].v).be.eql('World') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
}) | ||
it('should work when using special rowspan layout #3', async () => { | ||
it('should work when using special rowspan layout #4 (row with all cells using rowspan)', async () => { | ||
const stream = await conversion(` | ||
@@ -413,5 +774,25 @@ <table> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['E2'].v).be.eql('Doc2.') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.c).be.eql(3) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.c).be.eql(3) | ||
}) | ||
it('should work when using special rowspan layout #4', async () => { | ||
it('should work when using special rowspan layout #5 (row with lot of cells using rowspan but last one without it)', async () => { | ||
const stream = await conversion(` | ||
@@ -458,5 +839,20 @@ <table> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D3'].v).be.eql('Doc2.') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.c).be.eql(2) | ||
}) | ||
it('should work when using special rowspan layout #5', async () => { | ||
it('should work when using special rowspan layout #6 (row with lot of cells using rowspan but last one using colspan)', async () => { | ||
const stream = await conversion(` | ||
@@ -508,5 +904,30 @@ <table> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D3'].v).be.eql('Text3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.c).be.eql(3) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.c).be.eql(5) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].s.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].s.c).be.eql(4) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].e.c).be.eql(5) | ||
}) | ||
it('should work when using special rowspan layout #6', async () => { | ||
it('should work when using special rowspan layout #7 (row with only one cell that uses rowspan greater than available rows)', async () => { | ||
const stream = await conversion(` | ||
@@ -534,5 +955,6 @@ <table> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A2']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges']).be.undefined() | ||
}) | ||
it('should work when using special rowspan layout #7', async () => { | ||
it('should work when using special rowspan layout #8 (complex calendar like layout)', async () => { | ||
const stream = await conversion(` | ||
@@ -683,7 +1105,245 @@ <table border="1" style="border-collapse:collapse;"> | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['Q4'].v).be.eql('22') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(6) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].s.c).be.eql(7) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][2].e.c).be.eql(11) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].s.c).be.eql(12) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][3].e.c).be.eql(16) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].s.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].e.r).be.eql(3) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][4].e.c).be.eql(0) | ||
}) | ||
it('should work when using special rowspan layout #9 (using rowspan in different rows)', async () => { | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td rowspan='3'>Row 1 Col 1</td> | ||
<td>Row 1 Col 2</td> | ||
<td>Row 1 Col 3</td> | ||
<td>Row 1 Col 4</td> | ||
</tr> | ||
<tr> | ||
<td rowspan='2'>Row 2 Col 1</td> | ||
<td rowspan='2'>Row 2 Col 2</td> | ||
<td>Row 2 Col 3</td> | ||
</tr> | ||
<tr> | ||
<td>Row 3 Col 3</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('Row 1 Col 1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A2']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A3']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql('Row 1 Col 2') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B2'].v).be.eql('Row 2 Col 1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B3']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C1'].v).be.eql('Row 1 Col 3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C2'].v).be.eql('Row 2 Col 2') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C3']).be.undefined() | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D1'].v).be.eql('Row 1 Col 4') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D2'].v).be.eql('Row 2 Col 3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D3'].v).be.eql('Row 3 Col 3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.r).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].s.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][0].e.c).be.eql(0) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.r).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].s.c).be.eql(1) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.r).be.eql(2) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['!merges'][1].e.c).be.eql(1) | ||
}) | ||
it('should be able to set fontFamily', async () => { | ||
const stream = await conversion(` | ||
<style> | ||
* { | ||
font-family: Verdana | ||
} | ||
</style> | ||
<table> | ||
<tr> | ||
<td style="font-size: 34px">Hello</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Styles.Fonts).matchAny((font) => should(font.name).be.eql('Verdana')) | ||
}) | ||
it('should wait for JS trigger', async () => { | ||
const stream = await conversion(` | ||
<table id="main"> | ||
</table> | ||
<script> | ||
setTimeout(function () { | ||
var table = document.getElementById('main') | ||
var row = document.createElement('tr') | ||
var cell = document.createElement('td') | ||
cell.innerHTML = 'Hello' | ||
row.appendChild(cell) | ||
table.appendChild(row) | ||
window.CHROME_PAGE_EVAL_READY = true | ||
window.PHANTOM_PAGE_EVAL_READY = true | ||
}, 500) | ||
</script> | ||
`, { | ||
waitForJS: true | ||
}) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('Hello') | ||
}) | ||
it('should wait for JS trigger (custom var name)', async () => { | ||
const stream = await conversion(` | ||
<table id="main"> | ||
</table> | ||
<script> | ||
setTimeout(function () { | ||
var table = document.getElementById('main') | ||
var row = document.createElement('tr') | ||
var cell = document.createElement('td') | ||
cell.innerHTML = 'Hello' | ||
row.appendChild(cell) | ||
table.appendChild(row) | ||
window.READY_TO_START = true | ||
}, 500) | ||
</script> | ||
`, { | ||
waitForJS: true, | ||
waitForJSVarName: 'READY_TO_START' | ||
}) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('Hello') | ||
}) | ||
it('should generate multiple sheets when there are multiple tables in html', async () => { | ||
const stream = await conversion(` | ||
<table name="Data1"> | ||
<tr> | ||
<td>1</td> | ||
<td>2</td> | ||
<td>3</td> | ||
<td>4</td> | ||
</tr> | ||
</table> | ||
<table name="Data2"> | ||
<tr> | ||
<td>1</td> | ||
<td>2</td> | ||
<td>3</td> | ||
<td>4</td> | ||
</tr> | ||
</table> | ||
`) | ||
const parsedXlsx = await new Promise((resolve, reject) => { | ||
const bufs = [] | ||
stream.on('error', reject) | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
resolve(xlsx.read(buf)) | ||
}) | ||
}) | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['A1'].v).be.eql('1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['B1'].v).be.eql('2') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['C1'].v).be.eql('3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[0]]['D1'].v).be.eql('4') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[1]]['A1'].v).be.eql('1') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[1]]['B1'].v).be.eql('2') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[1]]['C1'].v).be.eql('3') | ||
should(parsedXlsx.Sheets[parsedXlsx.SheetNames[1]]['D1'].v).be.eql('4') | ||
}) | ||
it('should callback error when input contains invalid characters', async () => { | ||
return ( | ||
conversion('<table><tr><td></td></tr></table>') | ||
conversion(` | ||
<table> | ||
<tr> | ||
<td></td> | ||
</tr> | ||
</table> | ||
`) | ||
).should.be.rejected() | ||
@@ -705,10 +1365,24 @@ }) | ||
it('should translate ampersands', async () => { | ||
const stream = await conversion('<table><tr><td>& &</td></tr>') | ||
const stream = await conversion(` | ||
<table> | ||
<tr> | ||
<td>& &</td> | ||
</tr> | ||
</table> | ||
`) | ||
const bufs = [] | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('end', () => { | ||
const buf = Buffer.concat(bufs) | ||
xlsx.read(buf).Strings[0].t.should.be.eql('& &') | ||
await new Promise((resolve, reject) => { | ||
stream.on('data', (d) => { bufs.push(d) }) | ||
stream.on('error', reject) | ||
stream.on('end', () => { | ||
try { | ||
const buf = Buffer.concat(bufs) | ||
xlsx.read(buf).Strings[0].t.should.be.eql('& &') | ||
resolve() | ||
} catch (e) { | ||
reject(e) | ||
} | ||
}) | ||
}) | ||
@@ -724,1 +1398,35 @@ }) | ||
}) | ||
async function createHtmlFile (html) { | ||
const outputPath = path.join(tmpDir, `${uuid()}.html`) | ||
await writeFileAsync(outputPath, html) | ||
return outputPath | ||
} | ||
function rmDir (dirPath) { | ||
if (!fs.existsSync(dirPath)) { | ||
fs.mkdirSync(dirPath) | ||
} | ||
let files | ||
try { | ||
files = fs.readdirSync(dirPath) | ||
} catch (e) { | ||
return | ||
} | ||
if (files.length > 0) { | ||
for (let i = 0; i < files.length; i++) { | ||
let filePath = `${dirPath}/${files[i]}` | ||
try { | ||
if (fs.statSync(filePath).isFile()) { | ||
fs.unlinkSync(filePath) | ||
} | ||
} catch (e) { } | ||
} | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
76041
71.38%14
27.27%1721
81.35%4
100%3
50%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed