XLS to JSONExcel 97-2003 (XLS)
Legacy binary Excel format.
Binary BIFF-based format used by Excel 97 through 2003. TabularJS decodes BIFF records and PTG tokens to reconstruct formulas and data, including references to other sheets and workbooks, named ranges and array formulas. Write-protected (read-only recommended) workbooks open normally.
An .xls file is a binary OLE2 compound document that contains a BIFF8 Workbook stream: a long list of records for strings, fonts, formats, sheets and cells. Formulas are not stored as text but as compiled tokens (PTG), so a reader has to rebuild =SUM(A1:A3) from bytes.
TabularJS does this in plain JavaScript, with no native modules and no Excel or LibreOffice installation, so the same code reads .xls files in Node.js and in the browser. Typical uses are imports of old ERP and accounting exports, archives that were never converted to .xlsx, and files people still save as "Excel 97-2003 Workbook".
Feature support
What TabularJS extracts from XLS files
- Every sheet in workbook order with its name in
worksheetName. Hidden sheets are included and marked withworksheetState('hidden'or'veryHidden'). Chart sheets and charts embedded in a sheet do not shift or cut the cell data. - Cell values in
data[row][col]: strings (including rich-text and phonetic strings from the shared string table), numbers, booleans and error values such as#DIV/0!or#N/A. - Formulas as text.
dataholds the formula with=, andcells['E6'].formulaholds the same formula without=. - Formula coverage: references to other sheets (
Sheet2!A1,'My Sheet'!A1:B2,Sheet1:Sheet3!A1) and to other workbooks ('[Book2.xls]Prices'!A1), defined names, add-in functions, array formulas, array constants and shared formulas. - Styles converted to CSS (fonts, colors, fills, borders, alignment).
worksheet.style['A1']is an index intoresult.style. - Number formats in
cells[...].format, alignment and wrapping incells[...].alignandwrap, merged ranges inmergeCells, column widths and row heights.
Convert XLS to JSON in Node.js
import tabularjs from 'tabularjs';
const result = await tabularjs('legacy-report.xls');
console.log(result.worksheets.map((w) => w.worksheetName));
// [ 'Flow', 'Temp &Injector', 'Detector', 'Gradient', 'RID' ]
const sheet = result.worksheets[1];
console.log(sheet.data[5][4]); // '=IF((COUNTIF(B6,">"&D6)+COUNTIF(B6,"<"&C6))>0,$K$2,$K$3)'
console.log(sheet.cells.E6.formula); // same formula, without the leading '='Read XLS files in the browser
import tabularjs from 'tabularjs';
document.querySelector('input[type=file]').addEventListener('change', async (event) => {
try {
const result = await tabularjs(event.target.files[0]);
console.log(result.worksheets[0].data);
} catch (error) {
// e.g. 'Workbook is encrypted with a password'
console.error(error.message);
}
});JSON output
{
"worksheets": [
{
"worksheetName": "Temp &Injector",
"data": [
["Set Temperature", "Observed Value (°C)", "Acceptance criteria", "", "Results"],
["10°C", 10.21, 8, 12, "=IF((COUNTIF(B6,\">\"&D6)+COUNTIF(B6,\"<\"&C6))>0,$K$2,$K$3)"]
],
"columns": [{ "width": 146, "title": "A", "type": "text" }],
"rows": { "0": { "height": 25.15 } },
"cells": {
"E4": { "format": "0.0", "align": "right", "wrap": true },
"E6": { "formula": "IF((COUNTIF(B6,\">\"&D6)+COUNTIF(B6,\"<\"&C6))>0,$K$2,$K$3)", "align": "right" }
},
"style": { "A1": 0, "B1": 1 },
"mergeCells": { "A1": [10, 1] }
}
],
"style": ["font-family: Times New Roman; font-size: 11pt; text-align: right; ..."]
}Trimmed output for a five-sheet laboratory workbook: one sheet, two of its rows (rows 4 and 6 of the sheet) and shortened CSS. The property names are the real ones.
Limitations
- Formulas are never calculated:
dataholds the formula text, not the result Excel saved. When a formula uses a token TabularJS cannot decode faithfully, the cell gets the saved value instead of a wrong formula. - Password-encrypted workbooks are rejected with the error
Workbook is encrypted with a password(orWorkbook is encrypted (...); decryption is not supportedfor other encryption schemes). Workbooks that are only write-protected or "read-only recommended" open normally. - Cell comments (notes), images and charts are not extracted from .xls files.
- Defined names are used to decode formulas, but they are not returned in
result.definedNamesas they are for XLSX. - Dates are serial numbers, with the number format in
cells. - The parser is written for BIFF8, the format of Excel 97 to 2003. Files from Excel 5.0/95 and earlier use older BIFF versions and are not a supported target.
Frequently asked questions
How do I read an .xls file in JavaScript?
Install the package with npm install tabularjs, then call await tabularjs('report.xls') in Node.js, or pass the File from a file input in the browser. The result has one entry per sheet in result.worksheets, with the cell values in data.
Are .xls formulas returned as text or as values?
As text. TabularJS rebuilds the formula from its binary tokens and returns it as =SUM(A1:A3) in data and as SUM(A1:A3) in cells['A4'].formula. It does not calculate formulas.
Can TabularJS open password-protected .xls files?
Only write-protected ones. Excel encrypts "read-only recommended" and write-protected workbooks with a default password, which TabularJS decrypts. A workbook that needs a password to open is rejected with an error.
Do I need Excel, LibreOffice or a native module?
No. The CFB container, the BIFF records and the formula tokens are all decoded in JavaScript, so it runs anywhere Node.js or a modern browser runs.
Why TabularJS for Excel 97-2003 (XLS)?
- No external spreadsheet parser dependencies: faster installs, smaller bundles.
- One API in Node.js and modern browsers.
- Output is shape-compatible withJspreadsheet: render parsed data in a live workbook with one call.