diff --git a/.gitignore b/.gitignore index 785798e..9a15266 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ tmp *.[pP][dD][fF] *.[sS][lL][kK] *.socialcalc -*.[xX][lL][sSwWcCaAtTmM] +*.[xX][lL][sSwWcCaAtTmMrR] *.[xX][lL][sSaAtT][xXmMbB] *.[oO][dD][sS] *.[fF][oO][dD][sS] @@ -23,6 +23,7 @@ tmp *.[uU][oO][sS] *.[wW][kKqQbB][S1234567890] *.[qQ][pP][wW] +*.[fF][mM][3tT] *.[bB][iI][fF][fF][23458] *.[rR][tT][fF] *.[eE][tT][hH] diff --git a/.spelling b/.spelling index 896b659..8e99309 100644 --- a/.spelling +++ b/.spelling @@ -59,6 +59,15 @@ webpack weex # Other terms +1.x +2.x +3.x +4.x +5.x +6.x +7.x +8.x +9.x ActiveX APIs ArrayBuffer diff --git a/Makefile b/Makefile index 628726a..639e3ad 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ FLOWAUX=$(patsubst %.js,%.flow.js,$(AUXTARGETS)) AUXSCPTS=xlsxworker.js FLOWTGTS=$(TARGET) $(AUXTARGETS) $(AUXSCPTS) $(MINITGT) UGLIFYOPTS=--support-ie8 -m -# CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar +CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar ## Main Targets @@ -73,7 +73,7 @@ DISTHDR=misc/suppress_export.js .PHONY: dist dist: dist-deps $(TARGET) bower.json ## Prepare JS files for distribution mkdir -p dist - <$(TARGET) sed "s/require('stream')/{}/g;s/require('....*')/undefined/g" > dist/$(TARGET) + <$(TARGET) sed "s/require('....*')/undefined/g" > dist/$(TARGET) cp LICENSE dist/ uglifyjs shim.js $(UGLIFYOPTS) -o dist/shim.min.js --preamble "$$(head -n 1 bits/00_header.js)" uglifyjs $(DISTHDR) dist/$(TARGET) $(UGLIFYOPTS) -o dist/$(LIB).min.js --source-map dist/$(LIB).min.map --preamble "$$(head -n 1 bits/00_header.js)" @@ -148,47 +148,6 @@ ctest: ## Build browser test fixtures ctestserv: ## Start a test server on port 8000 @cd tests && python -mSimpleHTTPServer -## Demos - -DEMOS=angular angular-new browserify requirejs rollup systemjs webpack -DEMOTGTS=$(patsubst %,demo-%,$(DEMOS)) -.PHONY: demos -demos: $(DEMOTGTS) - -.PHONY: demo-angular -demo-angular: ## Run angular demo build - #make -C demos/angular - @echo "start a local server and go to demos/angular/angular.html" - -.PHONY: demo-angular-new -demo-angular-new: ## Run angular 2 demo build - make -C demos/angular2 - @echo "go to demos/angular/angular.html and run 'ng serve'" - -.PHONY: demo-browserify -demo-browserify: ## Run browserify demo build - make -C demos/browserify - @echo "start a local server and go to demos/browserify/browserify.html" - -.PHONY: demo-webpack -demo-webpack: ## Run webpack demo build - make -C demos/webpack - @echo "start a local server and go to demos/webpack/webpack.html" - -.PHONY: demo-requirejs -demo-requirejs: ## Run requirejs demo build - make -C demos/requirejs - @echo "start a local server and go to demos/requirejs/requirejs.html" - -.PHONY: demo-rollup -demo-rollup: ## Run rollup demo build - make -C demos/rollup - @echo "start a local server and go to demos/rollup/rollup.html" - -.PHONY: demo-systemjs -demo-systemjs: ## Run systemjs demo build - make -C demos/systemjs - ## Code Checking .PHONY: fullint @@ -197,7 +156,7 @@ fullint: lint mdlint ## Run all checks (removed: old-lint, tslint, flow) .PHONY: lint lint: $(TARGET) $(AUXTARGETS) ## Run eslint checks @./node_modules/.bin/eslint --ext .js,.njs,.json,.html,.htm $(TARGET) $(AUXTARGETS) $(CMDS) $(HTMLLINT) package.json bower.json - if [ -n "$(CLOSURE-)" ] && [ -e "${CLOSURE}" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi + @if [ -x "$(CLOSURE)" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi .PHONY: old-lint old-lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks @@ -206,7 +165,7 @@ old-lint: $(TARGET) $(AUXTARGETS) ## Run jshint and jscs checks @./node_modules/.bin/jshint --show-non-errors package.json bower.json test.js @./node_modules/.bin/jshint --show-non-errors --extract=always $(HTMLLINT) @./node_modules/.bin/jscs $(TARGET) $(AUXTARGETS) test.js - if [ -e $(CLOSURE) ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi + @if [ -x "$(CLOSURE)" ]; then java -jar $(CLOSURE) $(REQS) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi .PHONY: tslint tslint: $(TARGET) ## Run typescript checks diff --git a/bin/xlsx.njs b/bin/xlsx.njs index 1231aa5..2f5c18a 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -5,7 +5,7 @@ var n = "xlsx"; var X = require('../'); try { X = require('../xlsx.flow'); } catch(e) {} -require('exit-on-epipe'); +try { require('exit-on-epipe'); } catch(e) {} var fs = require('fs'), program; try { program = require('commander'); } catch(e) { [ diff --git a/bits/31_rels.js b/bits/31_rels.js index 7d731a6..faf3e89 100644 --- a/bits/31_rels.js +++ b/bits/31_rels.js @@ -30,7 +30,7 @@ function parse_rels(data/*:?string*/, currentFilePath/*:string*/) { var y = parsexmltag(x); /* 9.3.2.2 OPC_Relationships */ if (y[0] === ' 0)) ? [] : parse_NameParsedFormula(blob, npflen, opts, cce); + /*jshint +W018 */ return { chKey: chKey, Name: name, diff --git a/bits/41_lotus.js b/bits/41_lotus.js index 54f3d2b..cede0fa 100644 --- a/bits/41_lotus.js +++ b/bits/41_lotus.js @@ -28,12 +28,18 @@ var WK_ = /*#__PURE__*/ (function() { if(!d) return d; var o = opts || {}; if(DENSE != null && o.dense == null) o.dense = DENSE; - var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/), n = "Sheet1", sidx = 0; - var sheets = {}, snames = [n], realnames = []; + var s/*:Worksheet*/ = ((o.dense ? [] : {})/*:any*/), n = "Sheet1", next_n = "", sidx = 0; + var sheets = {}, snames = [], realnames = []; var refguess = {s: {r:0, c:0}, e: {r:0, c:0} }; var sheetRows = o.sheetRows || 0; + if(d[2] == 0x00) { + if(d[3] == 0x08 || d[3] == 0x09) { + if(d.length >= 16 && d[14] == 0x05 && d[15] === 0x6c) throw new Error("Unsupported Works 3 for Mac file"); + } + } + if(d[2] == 0x02) { o.Enum = WK1Enum; lotushopper(d, function(val, R, RT) { switch(RT) { @@ -42,6 +48,8 @@ var WK_ = /*#__PURE__*/ (function() { if(val >= 0x1000) o.qpro = true; break; case 0x06: refguess = val; break; /* RANGE */ + case 0xCC: if(val) next_n = val; break; /* SHEETNAMECS */ + case 0xDE: next_n = val; break; /* SHEETNAMELP */ case 0x0F: /* LABEL */ case 0x33: /* STRING */ if(!o.qpro) val[1].v = val[1].v.slice(1); @@ -54,6 +62,18 @@ var WK_ = /*#__PURE__*/ (function() { val[1].z = o.dateNF || SSF._table[14]; if(o.cellDates) { val[1].t = 'd'; val[1].v = numdate(val[1].v); } } + + if(o.qpro) { + if(val[3] > sidx) { + s["!ref"] = encode_range(refguess); + sheets[n] = s; + snames.push(n); + s = (o.dense ? [] : {}); + refguess = {s: {r:0, c:0}, e: {r:0, c:0} }; + sidx = val[3]; n = next_n || "Sheet" + (sidx + 1); next_n = ""; + } + } + var tmpcell = o.dense ? (s[val[0].r]||[])[val[0].c] : s[encode_cell(val[0])]; if(tmpcell) { tmpcell.t = val[1].t; tmpcell.v = val[1].v; @@ -72,6 +92,7 @@ var WK_ = /*#__PURE__*/ (function() { o.Enum = WK3Enum; if(d[2] == 0x0E) { o.qpro = true; d.l = 0; } lotushopper(d, function(val, R, RT) { switch(RT) { + case 0xCC: n = val; break; /* SHEETNAMECS */ case 0x16: /* LABEL16 */ val[1].v = val[1].v.slice(1); /* falls through */ @@ -84,10 +105,10 @@ var WK_ = /*#__PURE__*/ (function() { if(val[3] > sidx) { s["!ref"] = encode_range(refguess); sheets[n] = s; + snames.push(n); s = (o.dense ? [] : {}); refguess = {s: {r:0, c:0}, e: {r:0, c:0} }; sidx = val[3]; n = "Sheet" + (sidx + 1); - snames.push(n); } if(sheetRows > 0 && val[0].r >= sheetRows) break; if(o.dense) { @@ -100,17 +121,23 @@ var WK_ = /*#__PURE__*/ (function() { case 0x1B: /* XFORMAT */ if(val[0x36b0]) realnames[val[0x36b0][0]] = val[0x36b0][1]; break; + case 0x0601: /* SHEETINFOQP */ + realnames[val[0]] = val[1]; if(val[0] == sidx) n = val[1]; break; default: break; }}, o); } else throw new Error("Unrecognized LOTUS BOF " + d[2]); - s["!ref"] = encode_range(refguess); - sheets[n] = s; + sheets[next_n || n] = s; + snames.push(next_n || n); if(!realnames.length) return { SheetNames: snames, Sheets: sheets }; var osheets = {}, rnames = []; + /* TODO: verify no collisions */ for(var i = 0; i < realnames.length; ++i) if(sheets[snames[i]]) { + rnames.push(realnames[i] || snames[i]); + osheets[realnames[i]] = sheets[realnames[i]] || sheets[snames[i]]; + } else { rnames.push(realnames[i]); - osheets[realnames[i]] = sheets[snames[i]]; + osheets[realnames[i]] = ({ "!ref": "A1" }); } return { SheetNames: rnames, Sheets: osheets }; } @@ -126,7 +153,8 @@ var WK_ = /*#__PURE__*/ (function() { write_biff_rec(ba, 0x00, write_BOF_WK1(0x0406)); write_biff_rec(ba, 0x06, write_RANGE(range)); - for(var R = range.s.r; R <= range.e.r; ++R) { + var max_R = Math.min(range.e.r, 8191); + for(var R = range.s.r; R <= max_R; ++R) { var rr = encode_row(R); for(var C = range.s.c; C <= range.e.c; ++C) { if(R === range.s.r) cols[C] = encode_col(C); @@ -165,7 +193,8 @@ var WK_ = /*#__PURE__*/ (function() { var range = safe_decode_range(ws["!ref"]); var dense = Array.isArray(ws); var cols = []; - for(var R = range.s.r; R <= range.e.r; ++R) { + var max_R = Math.min(range.e.r, 8191); + for(var R = range.s.r; R <= max_R; ++R) { var rr = encode_row(R); for(var C = range.s.c; C <= range.e.c; ++C) { if(R === range.s.r) cols[C] = encode_col(C); @@ -211,6 +240,7 @@ var WK_ = /*#__PURE__*/ (function() { if(rows < range.e.r) rows = range.e.r; if(cols < range.e.c) cols = range.e.c; } + if(rows > 8191) rows = 8191; out.write_shift(2, rows); out.write_shift(1, wscnt); out.write_shift(1, cols); @@ -223,12 +253,23 @@ var WK_ = /*#__PURE__*/ (function() { return out; } - function parse_RANGE(blob) { + function parse_RANGE(blob, length, opts) { var o = {s:{c:0,r:0},e:{c:0,r:0}}; + if(length == 8 && opts.qpro) { + o.s.c = blob.read_shift(1); + blob.l++; + o.s.r = blob.read_shift(2); + o.e.c = blob.read_shift(1); + blob.l++; + o.e.r = blob.read_shift(2); + return o; + } o.s.c = blob.read_shift(2); o.s.r = blob.read_shift(2); + if(length == 12 && opts.qpro) blob.l += 2; o.e.c = blob.read_shift(2); o.e.r = blob.read_shift(2); + if(length == 12 && opts.qpro) blob.l += 2; if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0; return o; } @@ -242,10 +283,10 @@ var WK_ = /*#__PURE__*/ (function() { } function parse_cell(blob, length, opts) { - var o = [{c:0,r:0}, {t:'n',v:0}, 0]; + var o = [{c:0,r:0}, {t:'n',v:0}, 0, 0]; if(opts.qpro && opts.vers != 0x5120) { o[0].c = blob.read_shift(1); - blob.l++; + o[3] = blob.read_shift(1); o[0].r = blob.read_shift(2); blob.l+=2; } else { @@ -357,7 +398,7 @@ var WK_ = /*#__PURE__*/ (function() { function wk1_fmla_to_csf(blob, o) { prep_blob(blob, 0); - var out = [], argc = 0, R = "", C = ""; + var out = [], argc = 0, R = "", C = "", argL = "", argR = ""; while(blob.l < blob.length) { var cc = blob[blob.l++]; switch(cc) { @@ -390,7 +431,7 @@ var WK_ = /*#__PURE__*/ (function() { case 0x16: out.push("NOT(" + out.pop() + ")"); break; case 0x14: case 0x15: { - var argR = out.pop(), argL = out.pop(); + argR = out.pop(); argL = out.pop(); out.push(["AND", "OR"][cc - 0x14] + "(" + argL + "," + argR + ")"); } break; @@ -536,6 +577,27 @@ var WK_ = /*#__PURE__*/ (function() { return o; } + function parse_SHEETNAMECS(blob, length) { + return blob[blob.l + length - 1] == 0 ? blob.read_shift(length, 'cstr') : ""; + } + + function parse_SHEETNAMELP(blob, length) { + var len = blob[blob.l++]; + if(len > length - 1) len = length - 1; + var o = ""; while(o.length < len) o += String.fromCharCode(blob[blob.l++]); + return o; + } + + function parse_SHEETINFOQP(blob, length, opts) { + if(!opts.qpro || length < 21) return; + var id = blob.read_shift(1); + blob.l += 17; + var len = blob.read_shift(1); + blob.l += 2; + var nm = blob.read_shift(length - 21, 'cstr'); + return [id, nm]; + } + function parse_XFORMAT(blob, length) { var o = {}, tgt = blob.l + length; while(blob.l < tgt) { @@ -623,6 +685,8 @@ var WK_ = /*#__PURE__*/ (function() { /*::[*/0x0067/*::]*/: { n:"RRANGES??" }, /*::[*/0x0068/*::]*/: { n:"FNAME??" }, /*::[*/0x0069/*::]*/: { n:"MRANGES??" }, + /*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS }, + /*::[*/0x00DE/*::]*/: { n:"SHEETNAMELP", f:parse_SHEETNAMELP }, /*::[*/0xFFFF/*::]*/: { n:"" } }; @@ -688,6 +752,7 @@ var WK_ = /*#__PURE__*/ (function() { /*::[*/0x00BC/*::]*/: { n:"??" }, /*::[*/0x00C3/*::]*/: { n:"??" }, /*::[*/0x00C9/*::]*/: { n:"??" }, + /*::[*/0x00CC/*::]*/: { n:"SHEETNAMECS", f:parse_SHEETNAMECS }, /*::[*/0x00CD/*::]*/: { n:"??" }, /*::[*/0x00CE/*::]*/: { n:"??" }, /*::[*/0x00CF/*::]*/: { n:"??" }, @@ -732,6 +797,7 @@ var WK_ = /*#__PURE__*/ (function() { /*::[*/0x029A/*::]*/: { n:"??" }, /*::[*/0x0300/*::]*/: { n:"??" }, /*::[*/0x0304/*::]*/: { n:"??" }, + /*::[*/0x0601/*::]*/: { n:"SHEETINFOQP", f:parse_SHEETINFOQP }, /*::[*/0x0640/*::]*/: { n:"??" }, /*::[*/0x0642/*::]*/: { n:"??" }, /*::[*/0x0701/*::]*/: { n:"??" }, diff --git a/bits/76_xls.js b/bits/76_xls.js index b2b5916..dc8ee12 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -855,7 +855,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(!options.bookSheets) wb.Sheets=Sheets; if(!wb.SheetNames.length && Preamble["!ref"]) { wb.SheetNames.push("Sheet1"); + /*jshint -W069 */ if(wb.Sheets) wb.Sheets["Sheet1"] = Preamble; + /*jshint +W069 */ } else wb.Preamble=Preamble; if(wb.Sheets) FilterDatabases.forEach(function(r,i) { wb.Sheets[wb.SheetNames[i]]['!autofilter'] = r; }); wb.Strings = sst; @@ -950,6 +952,8 @@ else/*:: if(cfb instanceof CFBContainer) */ { else if((_data=CFB.find(cfb, 'PerfectOffice_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options)); /* Quattro Pro 9 */ else if((_data=CFB.find(cfb, 'NativeContent_MAIN')) && _data.content) WorkbookP = WK_.to_workbook(_data.content, (options.type = T, options)); + /* Works 4 for Mac */ + else if((_data=CFB.find(cfb, 'MN0')) && _data.content) throw new Error("Unsupported Works 4 for Mac file"); else throw new Error("Cannot find Workbook stream"); if(options.bookVBA && cfb.FullPaths && CFB.find(cfb, '/_VBA_PROJECT_CUR/VBA/dir')) WorkbookP.vbaraw = make_vba_xls(cfb); } diff --git a/bits/77_parsetab.js b/bits/77_parsetab.js index f52a20e..24fd18c 100644 --- a/bits/77_parsetab.js +++ b/bits/77_parsetab.js @@ -842,7 +842,9 @@ var XLSBRecordEnum = { }; var XLSBRE = evert_key(XLSBRecordEnum, 'n'); +/*jshint -W069 */ XLSBRE["BrtFRTArchID$"] = 0x0010; +/*jshint +W069 */ /* [MS-XLS] 2.3 Record Enumeration (and other sources) */ var XLSRecordEnum = { diff --git a/bits/81_writeods.js b/bits/81_writeods.js index 4764c96..6661d39 100644 --- a/bits/81_writeods.js +++ b/bits/81_writeods.js @@ -1,13 +1,15 @@ /* OpenDocument */ var write_styles_ods/*:{(wb:any, opts:any):string}*/ = (function() { - var master_styles = '' - + '' - + '' - + '' - + '' - + '' - + '' - + ''; + var master_styles = [ + '', + '', + '', + '', + '', + '', + '', + '' + ].join(""); var payload = '= 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o); break; + case 0xFF: + if(n[1] === 0xFE) { return read_utf16(d, o); } + else if(n[1] === 0x00 && n[2] === 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o); + break; + case 0x00: + if(n[1] === 0x00) { + if(n[2] >= 0x02 && n[3] === 0x00) return WK_.to_workbook(d, o); + if(n[2] === 0x00 && (n[3] === 0x08 || n[3] === 0x09)) return WK_.to_workbook(d, o); + } + break; case 0x03: case 0x83: case 0x8B: case 0x8C: return DBF.to_workbook(d, o); case 0x7B: if(n[1] === 0x5C && n[2] === 0x72 && n[3] === 0x74) return RTF.to_workbook(d, o); break; case 0x0A: case 0x0D: case 0x20: return read_plaintext_raw(d, o); - case 0x89: if(n[1] === 0x50 && n[2] === 0x4E && n[3] === 0x47) throw new Error("PNG Image File is not a spreadsheet"); break; + case 0x89: if(n[1] === 0x50 && n[2] === 0x4E && n[3] === 0x47) throw new Error("PNG Image File is not a spreadsheet"); break; } if(DBF.versions.indexOf(n[0]) > -1 && n[2] <= 12 && n[3] <= 31) return DBF.to_workbook(d, o); return read_prn(data, d, o, str); diff --git a/bits/97_node.js b/bits/97_node.js index 95d7b05..dd641d3 100644 --- a/bits/97_node.js +++ b/bits/97_node.js @@ -1,5 +1,8 @@ if(has_buf && typeof require != 'undefined') (function() { - var Readable = require('stream').Readable; + var strmod = require('stream'); + if(!strmod) return; + var Readable = strmod.Readable; + if(!Readable) return; var write_csv_stream = function(sheet/*:Worksheet*/, opts/*:?Sheet2CSVOpts*/) { var stream = Readable(); diff --git a/bits/99_footer.js b/bits/99_footer.js index 75b3151..dc20acb 100644 --- a/bits/99_footer.js +++ b/bits/99_footer.js @@ -6,6 +6,6 @@ else if(typeof module !== 'undefined' && module.exports) make_xlsx_lib(module.ex else if(typeof define === 'function' && define.amd) define('xlsx', function() { if(!XLSX.version) make_xlsx_lib(XLSX); return XLSX; }); else make_xlsx_lib(XLSX); /* NOTE: the following extra line is needed for "Lightning Locker Service" */ -if(typeof window !== 'undefined' && !window.XLSX) window.XLSX = XLSX; +if(typeof window !== 'undefined' && !window.XLSX) try { window.XLSX = XLSX; } catch(e) {} /*exported XLS, ODS */ var XLS = XLSX, ODS = XLSX; diff --git a/docbits/80_parseopts.md b/docbits/80_parseopts.md index e9374c6..d0dc641 100644 --- a/docbits/80_parseopts.md +++ b/docbits/80_parseopts.md @@ -85,7 +85,7 @@ file but Excel will know how to handle it. This library applies similar logic: | Byte 0 | Raw File Type | Spreadsheet Types | |:-------|:--------------|:----------------------------------------------------| -| `0xD0` | CFB Container | BIFF 5/8 or password-protected XLSX/XLSB or WQ3/QPW | +| `0xD0` | CFB Container | BIFF 5/8 or protected XLSX/XLSB or WQ3/QPW or XLR | | `0x09` | BIFF Stream | BIFF 2/3/4/5 | | `0x3C` | XML/HTML | SpreadsheetML / Flat ODS / UOS1 / HTML / plain text | | `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plain text | @@ -102,6 +102,8 @@ file but Excel will know how to handle it. This library applies similar logic: DBF files are detected based on the first byte as well as the third and fourth bytes (corresponding to month and day of the file date) +Works for Windows files are detected based on the BOF record with type `0xFF` + Plain text format guessing follows the priority order: | Format | Test | diff --git a/docbits/85_filetype.md b/docbits/85_filetype.md index 9beec6d..dc27ce5 100644 --- a/docbits/85_filetype.md +++ b/docbits/85_filetype.md @@ -27,6 +27,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | Lotus 1-2-3 (WK1/WK3) | ✔ | ✔ | | Lotus 1-2-3 (WKS/WK2/WK4/123) | ✔ | | | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | | +| Works 1.x-3.x DOS / 2.x-5.x Windows Spreadsheet (WKS) | ✔ | | +| Works 6.x-9.x Spreadsheet (XLR) | ✔ | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | HTML Tables | ✔ | ✔ | | Rich Text Format tables (RTF) | | ✔ | @@ -44,6 +46,8 @@ range limits will be silently truncated: | 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 | +| Lotus 1-2-3 R2 - R5 (WK1/WK3/WK4) | IV8192 | 256 | 8192 | +| Lotus 1-2-3 R1 (WKS) | IV2048 | 256 | 2048 | Excel 2003 SpreadsheetML range limits are governed by the version of Excel and are not enforced by the writer. @@ -180,6 +184,27 @@ BIFF8 XLS. +#### Works for DOS / Windows Spreadsheet (WKS/XLR) + +
+ (click to show) + +All versions of Works were limited to a single worksheet. + +Works for DOS 1.x - 3.x and Works for Windows 2.x extends the Lotus WKS format +with additional record types. + +Works for Windows 3.x - 5.x uses the same format and WKS extension. The BOF +record has type `FF` + +Works for Windows 6.x - 9.x use the XLR format. XLR is nearly identical to +BIFF8 XLS: it uses the CFB container with a Workbook stream. Works 9 saves the +exact Workbook stream for the XLR and the 97-2003 XLS export. Works 6 XLS +includes two empty worksheets but the main worksheet has an identical encoding. +XLR also includes a `WksSSWorkBook` stream similar to Lotus FM3/FMT files. + +
+ #### OpenDocument Spreadsheet (ODS/FODS)
diff --git a/formats.png b/formats.png index 3419732..0e8c072 100644 Binary files a/formats.png and b/formats.png differ diff --git a/misc/coarsify.js b/misc/coarsify.js index 3c5de3e..f21d008 100644 --- a/misc/coarsify.js +++ b/misc/coarsify.js @@ -2,8 +2,12 @@ const fs = require('fs'); const coarse = require('coarse'); -const svg = fs.readFileSync(process.argv[2]); -const roughened = coarse(svg); +const svg = fs.readFileSync(process.argv[2], "utf8"); +let roughened = coarse(svg); +const viewbox = roughened.match(/viewBox="(.*?)"/)[1].split(/\s+/); +const v = viewbox.map(x => parseFloat(x)); +v[0] -= 40; v[1] += 40; v[2] += 80; v[3] += 80; +roughened = roughened.replace(/G<\/title>/, `$&<polygon fill="white" stroke="" points="${v[0]},${v[1]} ${v[0]},${v[1]-v[3]} ${v[0]+v[2]},${v[1]-v[3]} ${v[0]+v[2]},${v[1]} ${v[0]},${v[1]}"/>`); fs.writeFileSync(process.argv[3], roughened); diff --git a/misc/formats.dot b/misc/formats.dot index a490f32..d26cfaa 100644 --- a/misc/formats.dot +++ b/misc/formats.dot @@ -27,9 +27,15 @@ digraph G { slk [label="SYLK"]; prn [label="PRN"]; rtf [label="RTF"]; - wk1 [label="WK1/2\n123"]; - wk3 [label="WK3/4"]; - wqb [label="WQ*\nWB*"]; + wk1 [label="WK1"]; + wksl [label="WKS\nLotus"]; + wk3 [label="WK3"]; + wk4 [label="WK4"] + 123 [label="123"]; + wksm [label="WKS\nWorks"]; + xlr [label="XLR"]; + wq1 [label="WQ1"]; + wq2 [label="WQ2\nWB*"]; qpw [label="QPW"]; eth [label="ETH"]; } @@ -46,6 +52,7 @@ digraph G { xls5 -> csf csf -> xls8 xls8 -> csf + wq2 -> csf ods -> csf csf -> ods fods -> csf @@ -53,6 +60,8 @@ digraph G { uos -> csf wk3 -> csf csf -> wk3 + wk4 -> csf + 123 -> csf qpw -> csf } subgraph WORKSHEET { @@ -68,7 +77,10 @@ digraph G { csf -> dif wk1 -> csf csf -> wk1 - wqb -> csf + xlr -> csf + wq1 -> csf + wksl -> csf + wksm -> csf dif -> csf csf -> rtf prn -> csf diff --git a/test.js b/test.js index f7b84ec..25410ad 100644 --- a/test.js +++ b/test.js @@ -524,9 +524,11 @@ describe('parse options', function() { } } }; var str = X.write(wb, {bookType: "xlsx", type: "binary"}); var wb2 = X.read(str, {type: "binary"}); + /*jshint -W069 */ assert.equal(wb2.Sheets.Sheet1["A1"].f, "IFS(2>3,1,3>2,2)"); var wb3 = X.read(str, {type: "binary", xlfn: true}); assert.equal(wb3.Sheets.Sheet1["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)"); + /*jshint +W069 */ }); }); describe('sheet', function() { diff --git a/tests/core.js b/tests/core.js index f7b84ec..25410ad 100644 --- a/tests/core.js +++ b/tests/core.js @@ -524,9 +524,11 @@ describe('parse options', function() { } } }; var str = X.write(wb, {bookType: "xlsx", type: "binary"}); var wb2 = X.read(str, {type: "binary"}); + /*jshint -W069 */ assert.equal(wb2.Sheets.Sheet1["A1"].f, "IFS(2>3,1,3>2,2)"); var wb3 = X.read(str, {type: "binary", xlfn: true}); assert.equal(wb3.Sheets.Sheet1["A1"].f, "_xlfn.IFS(2>3,1,3>2,2)"); + /*jshint +W069 */ }); }); describe('sheet', function() {