diff --git a/.gitignore b/.gitignore index 172339d..785798e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ package-lock.json _book/ misc/coverage.html misc/prof.js +misc/*.[sS][vV][gG] v8.log tmp *.[tT][xX][tT] diff --git a/Makefile b/Makefile index 62c5df5..628726a 100644 --- a/Makefile +++ b/Makefile @@ -102,10 +102,13 @@ bytes: ## Display minified and gzipped file sizes .PHONY: graph graph: formats.png legend.png ## Rebuild format conversion graph -formats.png: formats.dot - circo -Tpng -o$@ $< -legend.png: misc/legend.dot - dot -Tpng -o$@ $< +misc/formats.svg: misc/formats.dot + circo -Tsvg -o$@ $< +misc/legend.svg: misc/legend.dot + dot -Tsvg -o$@ $< +formats.png legend.png: %.png: misc/%.svg + node misc/coarsify.js misc/$*.svg misc/$*.svg.svg + npx svgexport misc/$*.svg.svg $@ 0.5x .PHONY: nexe diff --git a/README.md b/README.md index 139deb8..42ffc7a 100644 --- a/README.md +++ b/README.md @@ -26,27 +26,26 @@ Community Translations of this README: [**Issues and Bug Reports**](https://github.com/sheetjs/sheetjs/issues) -[**File format support for known spreadsheet data formats:**](#file-formats) +![License](https://img.shields.io/github/license/SheetJS/sheetjs) +[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs) +[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx) +[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/xlsx/badge)](https://www.jsdelivr.com/package/npm/xlsx) +[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs) -
- Graph of supported formats (click to show) +[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/) + +[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs) + +**Supported File Formats** ![circo graph of format support](formats.png) +
Diagram Legend (click to show) + ![graph legend](legend.png)
-[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/) - -[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs) - -[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs) -[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master) -[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs) -[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx) -[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs) - ## Table of Contents
@@ -162,7 +161,7 @@ In the browser, just add a script tag: |-----------:|:-------------------------------------------| | `unpkg` | | | `jsDelivr` | | -| `CDNjs` | | +| `CDNjs` | | | `packd` | | `unpkg` makes the latest version available at: @@ -245,7 +244,11 @@ An appropriate version for each dependency is included in the dist/ directory. The complete single-file version is generated at `dist/xlsx.full.min.js` -A slimmer build with XLSX / HTML support is generated at `dist/xlsx.mini.min.js` +A slimmer build is generated at `dist/xlsx.mini.min.js`. Compared to full build: +- codepage library skipped (no support for XLS encodings) +- XLSX compression option not currently available +- no support for XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 +- node stream utils removed Webpack and Browserify builds include optional modules by default. Webpack can be configured to remove support with `resolve.alias`: @@ -846,7 +849,7 @@ Parse options are described in the [Parsing Options](#parsing-options) section. `XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`. In browser-based environments, it will attempt to force a client-side download. -`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`. +`XLSX.writeFileAsync(wb, filename, o, cb)` attempts to write `wb` to `filename`. If `o` is omitted, the writer will use the third argument as the callback. `XLSX.stream` contains a set of streaming write functions. @@ -1347,10 +1350,10 @@ prefixed with an apostrophe `'`, consistent with Excel's formula bar display. | Storage Representation | Formats | Read | Write | |:-----------------------|:-------------------------|:-----:|:-----:| -| A1-style strings | XLSX | :o: | :o: | -| RC-style strings | XLML and plain text | :o: | :o: | -| BIFF Parsed formulae | XLSB and all XLS formats | :o: | | -| OpenFormula formulae | ODS/FODS/UOS | :o: | :o: | +| A1-style strings | XLSX | ✔ | ✔ | +| RC-style strings | XLML and plain text | ✔ | ✔ | +| BIFF Parsed formulae | XLSB and all XLS formats | ✔ | | +| OpenFormula formulae | ODS/FODS/UOS | ✔ | ✔ | Since Excel prohibits named cells from colliding with names of A1 or RC style cell references, a (not-so-simple) regex conversion is possible. BIFF Parsed @@ -1374,6 +1377,7 @@ type ColInfo = { wch?: number; // width in characters /* other fields for preserving features from files */ + level?: number; // 0-indexed outline / group level MDW?: number; // Excel's "Max Digit Width" unit, always integral }; ``` @@ -1852,6 +1856,8 @@ output formats. The specific file type is controlled with `bookType` option: | `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format | | `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format | | `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format | +| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format | +| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format | | `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format | | `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) | | `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet | @@ -1990,14 +1996,19 @@ XLSX.utils.sheet_add_aoa(ws, [[4,5,6,7,8,9,0]], {origin: -1}); `XLSX.utils.json_to_sheet` takes an array of objects and returns a worksheet with automatically-generated "headers" based on the keys of the objects. The default column order is determined by the first appearance of the field using -`Object.keys`, but can be overridden using the options argument: +`Object.keys`. The function accepts an options argument: -| Option Name | Default | Description | -| :---------- | :------: | :-------------------------------------------------- | -|`header` | | Use specified column order (default `Object.keys`) | -|`dateNF` | FMT 14 | Use specified date format in string output | -|`cellDates` | false | Store dates as type `d` (default is `n`) | -|`skipHeader` | false | If true, do not include header row in output | +| Option Name | Default | Description | +| :---------- | :-----: | :--------------------------------------------------- | +|`header` | | Use specified field order (default `Object.keys`) ** | +|`dateNF` | FMT 14 | Use specified date format in string output | +|`cellDates` | false | Store dates as type `d` (default is `n`) | +|`skipHeader` | false | If true, do not include header row in output | + +- All fields from each row will be written. If `header` is an array and it does + not contain a particular field, the key will be appended to the array. +- Cell types are deduced from the type of each value. For example, a `Date` + object will generate a Date cell, while a string will generate a Text cell.
Examples (click to show) @@ -2368,31 +2379,31 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Format | Read | Write | |:-------------------------------------------------------------|:-----:|:-----:| | **Excel Worksheet/Workbook Formats** |:-----:|:-----:| -| Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: | -| Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: | -| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: | -| Excel 97-2004 (XLS BIFF8) | :o: | :o: | -| Excel 5.0/95 (XLS BIFF5) | :o: | :o: | -| Excel 4.0 (XLS/XLW BIFF4) | :o: | | -| Excel 3.0 (XLS BIFF3) | :o: | | -| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: | +| Excel 2007+ XML Formats (XLSX/XLSM) | ✔ | ✔ | +| Excel 2007+ Binary Format (XLSB BIFF12) | ✔ | ✔ | +| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ | +| Excel 97-2004 (XLS BIFF8) | ✔ | ✔ | +| Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ | +| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ | +| Excel 3.0 (XLS BIFF3) | ✔ | ✔ | +| Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ | | **Excel Supported Text Formats** |:-----:|:-----:| -| Delimiter-Separated Values (CSV/TXT) | :o: | :o: | -| Data Interchange Format (DIF) | :o: | :o: | -| Symbolic Link (SYLK/SLK) | :o: | :o: | -| Lotus Formatted Text (PRN) | :o: | :o: | -| UTF-16 Unicode Text (TXT) | :o: | :o: | +| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ | +| Data Interchange Format (DIF) | ✔ | ✔ | +| Symbolic Link (SYLK/SLK) | ✔ | ✔ | +| Lotus Formatted Text (PRN) | ✔ | ✔ | +| UTF-16 Unicode Text (TXT) | ✔ | ✔ | | **Other Workbook/Worksheet Formats** |:-----:|:-----:| -| OpenDocument Spreadsheet (ODS) | :o: | :o: | -| Flat XML ODF Spreadsheet (FODS) | :o: | :o: | -| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | :o: | | -| dBASE II/III/IV / Visual FoxPro (DBF) | :o: | :o: | -| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | :o: | | -| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | | +| OpenDocument Spreadsheet (ODS) | ✔ | ✔ | +| Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ | +| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | | +| dBASE II/III/IV / Visual FoxPro (DBF) | ✔ | ✔ | +| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | ✔ | | +| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| -| HTML Tables | :o: | :o: | -| Rich Text Format tables (RTF) | | :o: | -| Ethercalc Record Format (ETH) | :o: | :o: | +| HTML Tables | ✔ | ✔ | +| Rich Text Format tables (RTF) | | ✔ | +| Ethercalc Record Format (ETH) | ✔ | ✔ | Features not supported by a given file format will not be written. Formats with range limits will be silently truncated: @@ -2403,6 +2414,8 @@ range limits will be silently truncated: | Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 | | Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 | | Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 | +| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 | +| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 | | Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 | Excel 2003 SpreadsheetML range limits are governed by the version of Excel and diff --git a/bin/xlsx.njs b/bin/xlsx.njs index 6a264fa..7e4a676 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -24,8 +24,8 @@ program .option('-Y, --ods', 'emit ODS to or .ods') .option('-8, --xls', 'emit XLS to or .xls (BIFF8)') .option('-5, --biff5','emit XLS to or .xls (BIFF5)') - //.option('-4, --biff4','emit XLS to or .xls (BIFF4)') - //.option('-3, --biff3','emit XLS to or .xls (BIFF3)') + .option('-4, --biff4','emit XLS to or .xls (BIFF4)') + .option('-3, --biff3','emit XLS to or .xls (BIFF3)') .option('-2, --biff2','emit XLS to or .xls (BIFF2)') .option('-i, --xla', 'emit XLA to or .xla') .option('-6, --xlml', 'emit SSML to or .xls (2003 XML)') @@ -256,7 +256,7 @@ switch(true) { default: if(!program.book) { - var stream = X.stream.to_csv(ws, {FS:program.fieldSep, RS:program.rowSep}); + var stream = X.stream.to_csv(ws, {FS:program.fieldSep||",", RS:program.rowSep||"\n"}); if(program.output) stream.pipe(fs.createWriteStream(program.output)); else stream.pipe(process.stdout); } else doit(function(ws) { return X.utils.sheet_to_csv(ws,{FS:program.fieldSep, RS:program.rowSep}); }); diff --git a/bits/27_csfutils.js b/bits/27_csfutils.js index e9cc2c0..a31de3d 100644 --- a/bits/27_csfutils.js +++ b/bits/27_csfutils.js @@ -87,6 +87,7 @@ function format_cell(cell/*:Cell*/, v/*:any*/, o/*:any*/) { if(cell == null || cell.t == null || cell.t == 'z') return ""; if(cell.w !== undefined) return cell.w; if(cell.t == 'd' && !cell.z && o && o.dateNF) cell.z = o.dateNF; + if(cell.t == "e") return BErr[cell.v] || cell.v; if(v == undefined) return safe_format_cell(cell, cell.v); return safe_format_cell(cell, v); } diff --git a/bits/38_xlstypes.js b/bits/38_xlstypes.js index a0189c9..5008570 100644 --- a/bits/38_xlstypes.js +++ b/bits/38_xlstypes.js @@ -403,7 +403,7 @@ function parse_Bes(blob/*::, length*/) { } function write_Bes(v, t/*:string*/, o) { if(!o) o = new_buf(2); - o.write_shift(1, +v); + o.write_shift(1, ((t == 'e') ? +v : +!!v)); o.write_shift(1, ((t == 'e') ? 1 : 0)); return o; } diff --git a/bits/39_xlsbiff.js b/bits/39_xlsbiff.js index 683ae20..e5a58ec 100644 --- a/bits/39_xlsbiff.js +++ b/bits/39_xlsbiff.js @@ -422,6 +422,7 @@ function write_LabelSst(R/*:number*/, C/*:number*/, v/*:number*/, os/*:number*/ /* [MS-XLS] 2.4.148 */ function parse_Label(blob, length, opts) { + if(opts.biffguess && opts.biff == 2) opts.biff = 5; var target = blob.l + length; var cell = parse_XLSCell(blob, 6); if(opts.biff == 2) blob.l++; @@ -573,7 +574,9 @@ function write_XF(data, ixfeP, opts, o) { o.write_shift(2, (data.numFmtId||0)); o.write_shift(2, (ixfeP<<4)); } - o.write_shift(4, 0); + var f = 0; + if(data.numFmtId > 0 && b5) f |= 0x0400; + o.write_shift(4, f); o.write_shift(4, 0); if(!b5) o.write_shift(4, 0); o.write_shift(2, 0); @@ -600,7 +603,7 @@ function write_Guts(guts/*:Array*/) { /* [MS-XLS] 2.4.24 */ function parse_BoolErr(blob, length, opts) { var cell = parse_XLSCell(blob, 6); - if(opts.biff == 2) ++blob.l; + if(opts.biff == 2 || length == 9) ++blob.l; var val = parse_Bes(blob, 2); cell.val = val; cell.t = (val === true || val === false) ? 'b' : 'e'; @@ -614,7 +617,8 @@ function write_BoolErr(R/*:number*/, C/*:number*/, v, os/*:number*/, opts, t/*:s } /* [MS-XLS] 2.4.180 Number */ -function parse_Number(blob) { +function parse_Number(blob, length, opts) { + if(opts.biffguess && opts.biff == 2) opts.biff = 5; var cell = parse_XLSCell(blob, 6); var xnum = parse_Xnum(blob, 8); cell.val = xnum; @@ -1025,6 +1029,7 @@ function parse_ImData(blob) { /* BIFF2_??? where ??? is the name from [XLS] */ function parse_BIFF2STR(blob, length, opts) { + if(opts.biffguess && opts.biff == 5) opts.biff = 2; var cell = parse_XLSCell(blob, 6); ++blob.l; var str = parse_XLUnicodeString2(blob, length-7, opts); diff --git a/bits/62_fxls.js b/bits/62_fxls.js index 26520a0..a8a6b4c 100644 --- a/bits/62_fxls.js +++ b/bits/62_fxls.js @@ -935,8 +935,7 @@ function stringify_formula(formula/*Array*/, range, cell/*:any*/, supbooks, if(supbooks.sharedf[encode_cell(c)]) { var parsedf = (supbooks.sharedf[encode_cell(c)]); stack.push(stringify_formula(parsedf, _range, q, supbooks, opts)); - } - else { + } else { var fnd = false; for(e1=0;e1!=supbooks.arrayf.length; ++e1) { /* TODO: should be something like range_has */ diff --git a/bits/76_xls.js b/bits/76_xls.js index fbbb938..aa9d87c 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -217,7 +217,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(R.r === 2 || R.r == 12) { var rt = blob.read_shift(2); length -= 2; if(!opts.enc && rt !== RecordType && (((rt&0xFF)<<8)|(rt>>8)) !== RecordType) throw new Error("rt mismatch: " + rt + "!=" + RecordType); - if(R.r == 12){ blob.l += 10; length -= 10; } // skip FRT + if(R.r == 12){ + blob.l += 10; length -= 10; + } // skip FRT } //console.error(R,blob.l,length,blob.length); var val/*:any*/ = ({}/*:any*/); @@ -359,13 +361,16 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { /*::[*/0x0002/*::]*/:2, /*::[*/0x0007/*::]*/:2 }[val.BIFFVer] || 8; + opts.biffguess = val.BIFFVer == 0; + if(val.BIFFVer == 0 && val.dt == 0x1000) { opts.biff = 5; seen_codepage = true; set_cp(opts.codepage = 28591); } if(opts.biff == 8 && val.BIFFVer == 0 && val.dt == 16) opts.biff = 2; if(file_depth++) break; cell_valid = true; out = ((options.dense ? [] : {})/*:any*/); if(opts.biff < 8 && !seen_codepage) { seen_codepage = true; set_cp(opts.codepage = options.codepage || 1252); } - if(opts.biff < 5) { + + if(opts.biff < 5 || val.BIFFVer == 0 && val.dt == 0x1000) { if(cur_sheet === "") cur_sheet = "Sheet1"; range = {s:{r:0,c:0},e:{r:0,c:0}}; /* fake BoundSheet8 */ @@ -388,19 +393,19 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Number': case 'BIFF2NUM': case 'BIFF2INT': { if(out["!type"] == "chart") if(options.dense ? (out[val.r]||[])[val.c]: out[encode_cell({c:val.c, r:val.r})]) ++val.c; temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe]||{}, v:val.val, t:'n'}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); } break; case 'BoolErr': { temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.val, t:val.t}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); } break; case 'RK': { temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], v:val.rknum, t:'n'}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); } break; @@ -408,7 +413,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { for(var j = val.c; j <= val.C; ++j) { var ixfe = val.rkrec[j-val.c][0]; temp_val= ({ixfe:ixfe, XF:XFs[ixfe], v:val.rkrec[j-val.c][1], t:'n'}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:j, r:val.r}, temp_val, options); } @@ -426,7 +431,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { else temp_val.F = ((options.dense ? (out[_fr]||[])[_fc]: out[_fe]) || {}).F; } else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); } - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(val.cell, temp_val, options); last_formula = val; @@ -439,7 +444,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(options.cellFormula) { temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); } - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(last_formula.cell, temp_val, options); last_formula = null; @@ -471,13 +476,13 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { temp_val=make_cell(sst[val.isst].t, val.ixfe, 's'); if(sst[val.isst].h) temp_val.h = sst[val.isst].h; temp_val.XF = XFs[temp_val.ixfe]; - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); break; case 'Blank': if(options.sheetStubs) { temp_val = ({ixfe: val.ixfe, XF: XFs[val.ixfe], t:'z'}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); } break; @@ -485,7 +490,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { for(var _j = val.c; _j <= val.C; ++_j) { var _ixfe = val.ixfe[_j-val.c]; temp_val= ({ixfe:_ixfe, XF:XFs[_ixfe], t:'z'}/*:any*/); - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:_j, r:val.r}, temp_val, options); } @@ -494,7 +499,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'Label': case 'BIFF2STR': temp_val=make_cell(val.val, val.ixfe, 's'); temp_val.XF = XFs[temp_val.ixfe]; - if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x1F]; + if(BIFF2Fmt > 0) temp_val.z = BIFF2FmtTable[(temp_val.ixfe>>8) & 0x3F]; safe_format_xf(temp_val, options, wb.opts.Date1904); addcell({c:val.c, r:val.r}, temp_val, options); break; @@ -832,6 +837,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { /* BIFF2-4 records */ case 'BIFF2FONTCLR': case 'BIFF2FMTCNT': case 'BIFF2FONTXTRA': break; case 'BIFF2XF': case 'BIFF3XF': case 'BIFF4XF': break; + case 'BIFF2XFINDEX': break; case 'BIFF4FMTCNT': case 'BIFF2ROW': case 'BIFF2WINDOW2': break; /* Miscellaneous */ diff --git a/bits/77_parsetab.js b/bits/77_parsetab.js index a760dde..505e3b8 100644 --- a/bits/77_parsetab.js +++ b/bits/77_parsetab.js @@ -1222,6 +1222,7 @@ var XLSRecordEnum = { /*::[*/0x0034/*::]*/: { n:"DDEObjName" }, /*::[*/0x003e/*::]*/: { n:"BIFF2WINDOW2" }, /*::[*/0x0043/*::]*/: { n:"BIFF2XF" }, + /*::[*/0x0044/*::]*/: { n:"BIFF2XFINDEX", f:parseuint16 }, /*::[*/0x0045/*::]*/: { n:"BIFF2FONTCLR" }, /*::[*/0x0056/*::]*/: { n:"BIFF4FMTCNT" }, /* 16-bit cnt, similar to BIFF2 */ /*::[*/0x007e/*::]*/: { n:"RK" }, /* Not necessarily same as 0x027e */ diff --git a/bits/78_writebiff.js b/bits/78_writebiff.js index 0344c63..fda4a84 100644 --- a/bits/78_writebiff.js +++ b/bits/78_writebiff.js @@ -43,8 +43,7 @@ function write_BIFF2Cell(out, r/*:number*/, c/*:number*/) { function write_BIFF2BERR(r/*:number*/, c/*:number*/, val, t/*:?string*/) { var out = new_buf(9); write_BIFF2Cell(out, r, c); - if(t == 'e') { out.write_shift(1, val); out.write_shift(1, 1); } - else { out.write_shift(1, val?1:0); out.write_shift(1, 0); } + write_Bes(val, t || 'b', out); return out; } @@ -105,7 +104,7 @@ function write_biff2_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) { var idx = 0; for(var i=0;i - Graph of supported formats (click to show) +[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/) + +[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs) + +**Supported File Formats** ![circo graph of format support](formats.png) +
Diagram Legend (click to show) + ![graph legend](legend.png)
-[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/) - -[![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs) - -[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs) -[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master) -[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs) -[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx) -[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs) - diff --git a/docbits/81_writeopts.md b/docbits/81_writeopts.md index 026a179..a9d5662 100644 --- a/docbits/81_writeopts.md +++ b/docbits/81_writeopts.md @@ -41,6 +41,8 @@ output formats. The specific file type is controlled with `bookType` option: | `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format | | `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format | | `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format | +| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format | +| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format | | `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format | | `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) | | `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet | diff --git a/docbits/85_filetype.md b/docbits/85_filetype.md index f8f5d35..fdde3d7 100644 --- a/docbits/85_filetype.md +++ b/docbits/85_filetype.md @@ -10,8 +10,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ | | Excel 97-2004 (XLS BIFF8) | ✔ | ✔ | | Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ | -| Excel 4.0 (XLS/XLW BIFF4) | ✔ | | -| Excel 3.0 (XLS BIFF3) | ✔ | | +| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ | +| Excel 3.0 (XLS BIFF3) | ✔ | ✔ | | Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ | | **Excel Supported Text Formats** |:-----:|:-----:| | Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ | @@ -40,6 +40,8 @@ range limits will be silently truncated: | Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 | | Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 | | Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 | +| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 | +| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 | | Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 | Excel 2003 SpreadsheetML range limits are governed by the version of Excel and diff --git a/formats.png b/formats.png index 60ece9b..fae2a8e 100644 Binary files a/formats.png and b/formats.png differ diff --git a/legend.png b/legend.png index 9719f96..b531c4f 100644 Binary files a/legend.png and b/legend.png differ diff --git a/misc/coarsify.js b/misc/coarsify.js new file mode 100644 index 0000000..3c5de3e --- /dev/null +++ b/misc/coarsify.js @@ -0,0 +1,9 @@ +/* based on the `coarse` project README */ +const fs = require('fs'); +const coarse = require('coarse'); + +const svg = fs.readFileSync(process.argv[2]); +const roughened = coarse(svg); + +fs.writeFileSync(process.argv[3], roughened); + diff --git a/misc/docs/README.md b/misc/docs/README.md index 8d06bcb..b12722a 100644 --- a/misc/docs/README.md +++ b/misc/docs/README.md @@ -26,23 +26,23 @@ Community Translations of this README: [**Issues and Bug Reports**](https://github.com/sheetjs/sheetjs/issues) -[**File format support for known spreadsheet data formats:**](#file-formats) +![License](https://img.shields.io/github/license/SheetJS/sheetjs) +[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/SheetJS/sheetjs)](https://snyk.io/test/github/SheetJS/sheetjs) +[![npm Downloads](https://img.shields.io/npm/dm/xlsx.svg)](https://npmjs.org/package/xlsx) +[![jsDelivr Downloads](https://data.jsdelivr.com/v1/package/npm/xlsx/badge)](https://www.jsdelivr.com/package/npm/xlsx) +[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs) - -![circo graph of format support](formats.png) - -![graph legend](legend.png) - - -[**Browser Test**](https://oss.sheetjs.com/sheetjs/tests/) +[**Browser Test and Support Matrix**](https://oss.sheetjs.com/sheetjs/tests/) [![Build Status](https://saucelabs.com/browser-matrix/sheetjs.svg)](https://saucelabs.com/u/sheetjs) -[![Build Status](https://semaphoreci.com/api/v1/sheetjs/sheetjs/branches/master/shields_badge.svg)](https://semaphoreci.com/sheetjs/sheetjs) -[![Coverage Status](https://img.shields.io/coveralls/SheetJS/sheetjs/master.svg)](https://coveralls.io/r/SheetJS/sheetjs?branch=master) -[![Dependencies Status](https://david-dm.org/sheetjs/sheetjs/status.svg)](https://david-dm.org/sheetjs/sheetjs) -[![npm Downloads](https://img.shields.io/npm/dt/xlsx.svg)](https://npmjs.org/package/xlsx) -[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/sheetjs?pixel)](https://github.com/SheetJS/sheetjs) +**Supported File Formats** + +![circo graph of format support](formats.png) + + +![graph legend](legend.png) + ## Table of Contents @@ -154,7 +154,7 @@ In the browser, just add a script tag: |-----------:|:-------------------------------------------| | `unpkg` | | | `jsDelivr` | | -| `CDNjs` | | +| `CDNjs` | | | `packd` | | `unpkg` makes the latest version available at: @@ -234,7 +234,11 @@ An appropriate version for each dependency is included in the dist/ directory. The complete single-file version is generated at `dist/xlsx.full.min.js` -A slimmer build with XLSX / HTML support is generated at `dist/xlsx.mini.min.js` +A slimmer build is generated at `dist/xlsx.mini.min.js`. Compared to full build: +- codepage library skipped (no support for XLS encodings) +- XLSX compression option not currently available +- no support for XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 +- node stream utils removed Webpack and Browserify builds include optional modules by default. Webpack can be configured to remove support with `resolve.alias`: @@ -768,7 +772,7 @@ Parse options are described in the [Parsing Options](#parsing-options) section. `XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`. In browser-based environments, it will attempt to force a client-side download. -`XLSX.writeFileAsync(filename, wb, o, cb)` attempts to write `wb` to `filename`. +`XLSX.writeFileAsync(wb, filename, o, cb)` attempts to write `wb` to `filename`. If `o` is omitted, the writer will use the third argument as the callback. `XLSX.stream` contains a set of streaming write functions. @@ -1231,10 +1235,10 @@ prefixed with an apostrophe `'`, consistent with Excel's formula bar display. | Storage Representation | Formats | Read | Write | |:-----------------------|:-------------------------|:-----:|:-----:| -| A1-style strings | XLSX | :o: | :o: | -| RC-style strings | XLML and plain text | :o: | :o: | -| BIFF Parsed formulae | XLSB and all XLS formats | :o: | | -| OpenFormula formulae | ODS/FODS/UOS | :o: | :o: | +| A1-style strings | XLSX | ✔ | ✔ | +| RC-style strings | XLML and plain text | ✔ | ✔ | +| BIFF Parsed formulae | XLSB and all XLS formats | ✔ | | +| OpenFormula formulae | ODS/FODS/UOS | ✔ | ✔ | Since Excel prohibits named cells from colliding with names of A1 or RC style cell references, a (not-so-simple) regex conversion is possible. BIFF Parsed @@ -1257,6 +1261,7 @@ type ColInfo = { wch?: number; // width in characters /* other fields for preserving features from files */ + level?: number; // 0-indexed outline / group level MDW?: number; // Excel's "Max Digit Width" unit, always integral }; ``` @@ -1702,6 +1707,8 @@ output formats. The specific file type is controlled with `bookType` option: | `xlsb` | `.xlsb` | ZIP | multi | Excel 2007+ Binary Format | | `biff8` | `.xls` | CFB | multi | Excel 97-2004 Workbook Format | | `biff5` | `.xls` | CFB | multi | Excel 5.0/95 Workbook Format | +| `biff4` | `.xls` | none | single | Excel 4.0 Worksheet Format | +| `biff3` | `.xls` | none | single | Excel 3.0 Worksheet Format | | `biff2` | `.xls` | none | single | Excel 2.0 Worksheet Format | | `xlml` | `.xls` | none | multi | Excel 2003-2004 (SpreadsheetML) | | `ods` | `.ods` | ZIP | multi | OpenDocument Spreadsheet | @@ -1834,14 +1841,19 @@ XLSX.utils.sheet_add_aoa(ws, [[4,5,6,7,8,9,0]], {origin: -1}); `XLSX.utils.json_to_sheet` takes an array of objects and returns a worksheet with automatically-generated "headers" based on the keys of the objects. The default column order is determined by the first appearance of the field using -`Object.keys`, but can be overridden using the options argument: +`Object.keys`. The function accepts an options argument: -| Option Name | Default | Description | -| :---------- | :------: | :-------------------------------------------------- | -|`header` | | Use specified column order (default `Object.keys`) | -|`dateNF` | FMT 14 | Use specified date format in string output | -|`cellDates` | false | Store dates as type `d` (default is `n`) | -|`skipHeader` | false | If true, do not include header row in output | +| Option Name | Default | Description | +| :---------- | :-----: | :--------------------------------------------------- | +|`header` | | Use specified field order (default `Object.keys`) ** | +|`dateNF` | FMT 14 | Use specified date format in string output | +|`cellDates` | false | Store dates as type `d` (default is `n`) | +|`skipHeader` | false | If true, do not include header row in output | + +- All fields from each row will be written. If `header` is an array and it does + not contain a particular field, the key will be appended to the array. +- Cell types are deduced from the type of each value. For example, a `Date` + object will generate a Date cell, while a string will generate a Text cell. The original sheet cannot be reproduced using plain objects since JS object keys @@ -2188,31 +2200,31 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Format | Read | Write | |:-------------------------------------------------------------|:-----:|:-----:| | **Excel Worksheet/Workbook Formats** |:-----:|:-----:| -| Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: | -| Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: | -| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: | -| Excel 97-2004 (XLS BIFF8) | :o: | :o: | -| Excel 5.0/95 (XLS BIFF5) | :o: | :o: | -| Excel 4.0 (XLS/XLW BIFF4) | :o: | | -| Excel 3.0 (XLS BIFF3) | :o: | | -| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: | +| Excel 2007+ XML Formats (XLSX/XLSM) | ✔ | ✔ | +| Excel 2007+ Binary Format (XLSB BIFF12) | ✔ | ✔ | +| Excel 2003-2004 XML Format (XML "SpreadsheetML") | ✔ | ✔ | +| Excel 97-2004 (XLS BIFF8) | ✔ | ✔ | +| Excel 5.0/95 (XLS BIFF5) | ✔ | ✔ | +| Excel 4.0 (XLS/XLW BIFF4) | ✔ | ✔ | +| Excel 3.0 (XLS BIFF3) | ✔ | ✔ | +| Excel 2.0/2.1 (XLS BIFF2) | ✔ | ✔ | | **Excel Supported Text Formats** |:-----:|:-----:| -| Delimiter-Separated Values (CSV/TXT) | :o: | :o: | -| Data Interchange Format (DIF) | :o: | :o: | -| Symbolic Link (SYLK/SLK) | :o: | :o: | -| Lotus Formatted Text (PRN) | :o: | :o: | -| UTF-16 Unicode Text (TXT) | :o: | :o: | +| Delimiter-Separated Values (CSV/TXT) | ✔ | ✔ | +| Data Interchange Format (DIF) | ✔ | ✔ | +| Symbolic Link (SYLK/SLK) | ✔ | ✔ | +| Lotus Formatted Text (PRN) | ✔ | ✔ | +| UTF-16 Unicode Text (TXT) | ✔ | ✔ | | **Other Workbook/Worksheet Formats** |:-----:|:-----:| -| OpenDocument Spreadsheet (ODS) | :o: | :o: | -| Flat XML ODF Spreadsheet (FODS) | :o: | :o: | -| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | :o: | | -| dBASE II/III/IV / Visual FoxPro (DBF) | :o: | :o: | -| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | :o: | | -| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | :o: | | +| OpenDocument Spreadsheet (ODS) | ✔ | ✔ | +| Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ | +| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | | +| dBASE II/III/IV / Visual FoxPro (DBF) | ✔ | ✔ | +| Lotus 1-2-3 (WKS/WK1/WK2/WK3/WK4/123) | ✔ | | +| Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| -| HTML Tables | :o: | :o: | -| Rich Text Format tables (RTF) | | :o: | -| Ethercalc Record Format (ETH) | :o: | :o: | +| HTML Tables | ✔ | ✔ | +| Rich Text Format tables (RTF) | | ✔ | +| Ethercalc Record Format (ETH) | ✔ | ✔ | Features not supported by a given file format will not be written. Formats with range limits will be silently truncated: @@ -2223,6 +2235,8 @@ range limits will be silently truncated: | Excel 2007+ Binary Format (XLSB BIFF12) | XFD1048576 | 16384 | 1048576 | | Excel 97-2004 (XLS BIFF8) | IV65536 | 256 | 65536 | | Excel 5.0/95 (XLS BIFF5) | IV16384 | 256 | 16384 | +| Excel 4.0 (XLS BIFF4) | IV16384 | 256 | 16384 | +| Excel 3.0 (XLS BIFF3) | IV16384 | 256 | 16384 | | Excel 2.0/2.1 (XLS BIFF2) | IV16384 | 256 | 16384 | Excel 2003 SpreadsheetML range limits are governed by the version of Excel and diff --git a/formats.dot b/misc/formats.dot similarity index 93% rename from formats.dot rename to misc/formats.dot index 8d5bba1..3b20b6f 100644 --- a/formats.dot +++ b/misc/formats.dot @@ -1,8 +1,9 @@ digraph G { graph [mindist=0.1]; + node [fontname="Indie Flower"]; csf [shape=doublecircle,label="Common\nSpreadsheet\nFormat\n(JS Object)"]; subgraph XL { - node [style=filled,color=green]; + node [style=filled,color="#00FF00"]; xlsx [label="XLSX\nXLSM"]; xlsb [label="XLSB\nBIFF12"]; xlml [label="SSML\n(2003/4)"]; @@ -58,7 +59,9 @@ digraph G { xls2 -> csf csf -> xls2 xls3 -> csf + csf -> xls3 xls4 -> csf + csf -> xls4 csf -> slk slk -> csf csf -> dif diff --git a/misc/legend.dot b/misc/legend.dot index f3bac23..3a57f53 100644 --- a/misc/legend.dot +++ b/misc/legend.dot @@ -1,38 +1,45 @@ digraph G { graph [mindist=0]; + node [fontname="Indie Flower"]; labelloc=t; - label="Legend" + //label="Legend" + fontname="Indie Flower" + style=filled + fillcolor="transparent" subgraph cluster_0 { label="Supported Format Types" - color="white" - XL[label="Excel",style=filled,color=green]; + color="transparent" + fontname="Indie Flower" + XL[label="Excel",style=filled,color="#00FF00"]; CSF[label="JS",shape=doublecircle]; OLD[label="Other",style=filled,color=cyan]; { edge[style=invis] XL -> CSF -> OLD[constraint=false]} } subgraph cluster_1 { - label="Workbook Format Conversions (blue arrow)" - color="white" + label="Workbook Format Conversions\n(blue arrow)" + color="transparent" + fontname="Indie Flower" x1i[label="XLSX"] c1[shape=doublecircle,label="JS"]; x1o[label="XLSB"] { edge[color=blue] - x1i->c1[constraint=false,label="read"] - c1->x1o[constraint=false,label="write"]; + x1i->c1[constraint=false,label="read",fontname="Indie Flower"] + c1->x1o[constraint=false,label="write",fontname="Indie Flower"]; } } subgraph cluster_2 { - label="Single-Worksheet Format Conversions (green arrow)" - color="white" + label="Single-Worksheet Format Conversions\n(green arrow)" + color="transparent" + fontname="Indie Flower" x2i[label="SYLK"] c2[shape=doublecircle,label="JS"]; x2o[label="CSV"] { edge[color=aquamarine4] - x2i->c2[constraint=false,label="read"] - c2->x2o[constraint=false,label="write"]; + x2i->c2[constraint=false,label="read",fontname="Indie Flower"] + c2->x2o[constraint=false,label="write",fontname="Indie Flower"]; } }