New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

html-to-xlsx

Package Overview
Dependencies
Maintainers
2
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

html-to-xlsx - npm Package Compare versions

Comparing version

to
1.1.0

lib/stylesMap.js

351

lib/conversion.js

@@ -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;(?!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 &amp;
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"
}
}

@@ -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 &amp; 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) { }
}
}
}