diff --git a/bin/xlsx.njs b/bin/xlsx.njs index 8896705..fac6518 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -56,6 +56,7 @@ program .option('-E, --eth', 'emit ETH to or .eth (Ethercalc)') .option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)') .option('-r, --rtf', 'emit RTF to or .txt (Table RTF)') + .option('--wk1', 'emit WK1 to or .txt (Lotus WK1)') .option('-z, --dump', 'dump internal representation as JSON') .option('--props', 'dump workbook properties as CSV') @@ -229,6 +230,7 @@ if(!program.quiet && !program.book) console.error(target_sheet); ['rtf', '.rtf'], ['txt', '.txt'], ['dbf', '.dbf'], + ['wk1', '.wk1'], ['dif', '.dif'] ].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) { wopts.bookType = m[0]; diff --git a/bits/24_hoppers.js b/bits/24_hoppers.js index b306146..1ad60f1 100644 --- a/bits/24_hoppers.js +++ b/bits/24_hoppers.js @@ -12,7 +12,7 @@ function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) { length = tmpbyte & 0x7F; for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte); tgt = data.l + length; - var d = (R.f||parsenoop)(data, length, opts); + var d = R.f && R.f(data, length, opts); data.l = tgt; if(cb(d, R.n, RT)) return; } diff --git a/bits/41_lotus.js b/bits/41_lotus.js index 7842146..06373a5 100644 --- a/bits/41_lotus.js +++ b/bits/41_lotus.js @@ -1,16 +1,16 @@ -var WK_ = (function() { +var WK_ = /*#__PURE__*/ (function() { function lotushopper(data, cb/*:RecordHopperCB*/, opts/*:any*/) { if(!data) return; prep_blob(data, data.l || 0); var Enum = opts.Enum || WK1Enum; while(data.l < data.length) { var RT = data.read_shift(2); - var R = Enum[RT] || Enum[0xFF]; + var R = Enum[RT] || Enum[0xFFFF]; var length = data.read_shift(2); var tgt = data.l + length; - var d = (R.f||parsenoop)(data, length, opts); + var d = R.f && R.f(data, length, opts); data.l = tgt; - if(cb(d, R.n, RT)) return; + if(cb(d, R, RT)) return; } } @@ -34,12 +34,9 @@ var WK_ = (function() { var refguess = {s: {r:0, c:0}, e: {r:0, c:0} }; var sheetRows = o.sheetRows || 0; - if(d[2] == 0x02) o.Enum = WK1Enum; - else if(d[2] == 0x1a) o.Enum = WK3Enum; - else if(d[2] == 0x0e) { o.Enum = WK3Enum; o.qpro = true; d.l = 0; } - else throw new Error("Unrecognized LOTUS BOF " + d[2]); - lotushopper(d, function(val, Rn, RT) { - if(d[2] == 0x02) switch(RT) { + if(d[2] == 0x02) { + o.Enum = WK1Enum; + lotushopper(d, function(val, R, RT) { switch(RT) { case 0x00: o.vers = val; if(val >= 0x1000) o.qpro = true; @@ -62,7 +59,11 @@ var WK_ = (function() { s[val[0].r][val[0].c] = val[1]; } else s[encode_cell(val[0])] = val[1]; break; - } else switch(RT) { + }}, o); + } else if(d[2] == 0x1A || d[2] == 0x0E) { + o.Enum = WK3Enum; + if(d[2] == 0x0E) { o.qpro = true; d.l = 0; } + lotushopper(d, function(val, R, RT) { switch(RT) { case 0x16: /* LABEL16 */ val[1].v = val[1].v.slice(1); /* falls through */ @@ -89,14 +90,53 @@ var WK_ = (function() { if(refguess.e.r < val[0].r) refguess.e.r = val[0].r; break; default: break; - } - }, o); + }}, o); + } else throw new Error("Unrecognized LOTUS BOF " + d[2]); s["!ref"] = encode_range(refguess); sheets[n] = s; return { SheetNames: snames, Sheets:sheets }; } + function sheet_to_wk1(ws/*:Worksheet*/, opts/*:WriteOpts*/) { + var o = opts || {}; + if(+o.codepage >= 0) set_cp(+o.codepage); + if(o.type == "string") throw new Error("Cannot write DBF to JS string"); + var ba = buf_array(); + var range = safe_decode_range(ws["!ref"]); + var dense = Array.isArray(ws); + var cols = []; + + 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 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); + var ref = cols[C] + rr; + var cell = dense ? (ws[R]||[])[C] : ws[ref]; + if(!cell || cell.t == "z") continue; + /* write cell */ + if(cell.t == "n") { + if((cell.v|0)==cell.v && cell.v >= -32768 && cell.v <= 32767) write_biff_rec(ba, 0x0d, write_INTEGER(R, C, cell.v)); + else write_biff_rec(ba, 0x0e, write_NUMBER(R, C, cell.v)); + } else { + var str = format_cell(cell); + write_biff_rec(ba, 0x0F, write_LABEL(R, C, str.slice(0, 239))); + } + } + } + + write_biff_rec(ba, 0x01); + return ba.end(); + } + + function write_BOF_WK1(v/*:number*/) { + var out = new_buf(2); + out.write_shift(2, v); + return out; + } + function parse_RANGE(blob) { var o = {s:{c:0,r:0},e:{c:0,r:0}}; o.s.c = blob.read_shift(2); @@ -106,6 +146,14 @@ var WK_ = (function() { if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0; return o; } + function write_RANGE(range) { + var out = new_buf(8); + out.write_shift(2, range.s.c); + out.write_shift(2, range.s.r); + out.write_shift(2, range.e.c); + out.write_shift(2, range.e.r); + return out; + } function parse_cell(blob, length, opts) { var o = [{c:0,r:0}, {t:'n',v:0}, 0]; @@ -135,18 +183,48 @@ var WK_ = (function() { o[1].v = blob.read_shift(tgt - blob.l, 'cstr'); return o; } + function write_LABEL(R, C, s) { + /* TODO: encoding */ + var o = new_buf(7 + s.length); + o.write_shift(1, 0xFF); + o.write_shift(2, C); + o.write_shift(2, R); + o.write_shift(1, 0x27); // ?? + for(var i = 0; i < o.length; ++i) { + var cc = s.charCodeAt(i); + o.write_shift(1, cc >= 0x80 ? 0x5F : cc); + } + o.write_shift(1, 0); + return o; + } function parse_INTEGER(blob, length, opts) { var o = parse_cell(blob, length, opts); o[1].v = blob.read_shift(2, 'i'); return o; } + function write_INTEGER(R, C, v) { + var o = new_buf(7); + o.write_shift(1, 0xFF); + o.write_shift(2, C); + o.write_shift(2, R); + o.write_shift(2, v, 'i'); + return o; + } function parse_NUMBER(blob, length, opts) { var o = parse_cell(blob, length, opts); o[1].v = blob.read_shift(8, 'f'); return o; } + function write_NUMBER(R, C, v) { + var o = new_buf(13); + o.write_shift(1, 0xFF); + o.write_shift(2, C); + o.write_shift(2, R); + o.write_shift(8, v, 'f'); + return o; + } function parse_FORMULA(blob, length, opts) { var tgt = blob.l + length; @@ -178,15 +256,16 @@ var WK_ = (function() { var o = parse_cell_3(blob, length); o[1].v = blob.read_shift(2); var v = o[1].v >> 1; - /* TODO: figure out all of the corner cases */ if(o[1].v & 0x1) { switch(v & 0x07) { + case 0: v = (v >> 3) * 5000; break; case 1: v = (v >> 3) * 500; break; case 2: v = (v >> 3) / 20; break; + case 3: v = (v >> 3) / 200; break; case 4: v = (v >> 3) / 2000; break; + case 5: v = (v >> 3) / 20000; break; case 6: v = (v >> 3) / 16; break; case 7: v = (v >> 3) / 64; break; - default: throw "unknown NUMBER_18 encoding " + (v & 0x07); } } o[1].v = v; @@ -198,9 +277,14 @@ var WK_ = (function() { var v1 = blob.read_shift(4); var v2 = blob.read_shift(4); var e = blob.read_shift(2); - if(e == 0xFFFF) { o[1].v = 0; return o; } + if(e == 0xFFFF) { + if(v1 === 0 && v2 === 0xC0000000) { o[1].t = "e"; o[1].v = 0x0F; } // ERR -> #VALUE! + else if(v1 === 0 && v2 === 0xD0000000) { o[1].t = "e"; o[1].v = 0x2A; } // NA -> #N/A + else o[1].v = 0; + return o; + } var s = e & 0x8000; e = (e&0x7FFF) - 16446; - o[1].v = (s*2 - 1) * ((e > 0 ? (v2 << e) : (v2 >>> -e)) + (e > -32 ? (v1 << (e + 32)) : (v1 >>> -(e + 32)))); + o[1].v = (1 - s*2) * ( /*(e > 0 ? (v2 << e) : (v2 >>> -e))*/ v2 * Math.pow(2, e+32) + v1 * Math.pow(2, e)); return o; } @@ -288,45 +372,138 @@ var WK_ = (function() { /*::[*/0x0048/*::]*/: { n:"ACOMM" }, /*::[*/0x0049/*::]*/: { n:"AMACRO" }, /*::[*/0x004A/*::]*/: { n:"PARSE" }, - /*::[*/0x00FF/*::]*/: { n:"", f:parsenoop } + /*::[*/0x0066/*::]*/: { n:"PRANGES??" }, + /*::[*/0x0067/*::]*/: { n:"RRANGES??" }, + /*::[*/0x0068/*::]*/: { n:"FNAME??" }, + /*::[*/0x0069/*::]*/: { n:"MRANGES??" }, + /*::[*/0xFFFF/*::]*/: { n:"" } }; var WK3Enum = { /*::[*/0x0000/*::]*/: { n:"BOF" }, /*::[*/0x0001/*::]*/: { n:"EOF" }, - /*::[*/0x0003/*::]*/: { n:"??" }, - /*::[*/0x0004/*::]*/: { n:"??" }, - /*::[*/0x0005/*::]*/: { n:"??" }, - /*::[*/0x0006/*::]*/: { n:"??" }, - /*::[*/0x0007/*::]*/: { n:"??" }, - /*::[*/0x0009/*::]*/: { n:"??" }, - /*::[*/0x000a/*::]*/: { n:"??" }, - /*::[*/0x000b/*::]*/: { n:"??" }, - /*::[*/0x000c/*::]*/: { n:"??" }, - /*::[*/0x000e/*::]*/: { n:"??" }, - /*::[*/0x000f/*::]*/: { n:"??" }, - /*::[*/0x0010/*::]*/: { n:"??" }, - /*::[*/0x0011/*::]*/: { n:"??" }, - /*::[*/0x0012/*::]*/: { n:"??" }, + /*::[*/0x0002/*::]*/: { n:"PASSWORD" }, + /*::[*/0x0003/*::]*/: { n:"CALCSET" }, + /*::[*/0x0004/*::]*/: { n:"WINDOWSET" }, + /*::[*/0x0005/*::]*/: { n:"SHEETCELLPTR" }, + /*::[*/0x0006/*::]*/: { n:"SHEETLAYOUT" }, + /*::[*/0x0007/*::]*/: { n:"COLUMNWIDTH" }, + /*::[*/0x0008/*::]*/: { n:"HIDDENCOLUMN" }, + /*::[*/0x0009/*::]*/: { n:"USERRANGE" }, + /*::[*/0x000A/*::]*/: { n:"SYSTEMRANGE" }, + /*::[*/0x000B/*::]*/: { n:"ZEROFORCE" }, + /*::[*/0x000C/*::]*/: { n:"SORTKEYDIR" }, + /*::[*/0x000D/*::]*/: { n:"FILESEAL" }, + /*::[*/0x000E/*::]*/: { n:"DATAFILLNUMS" }, + /*::[*/0x000F/*::]*/: { n:"PRINTMAIN" }, + /*::[*/0x0010/*::]*/: { n:"PRINTSTRING" }, + /*::[*/0x0011/*::]*/: { n:"GRAPHMAIN" }, + /*::[*/0x0012/*::]*/: { n:"GRAPHSTRING" }, /*::[*/0x0013/*::]*/: { n:"??" }, - /*::[*/0x0015/*::]*/: { n:"??" }, + /*::[*/0x0014/*::]*/: { n:"ERRCELL" }, + /*::[*/0x0015/*::]*/: { n:"NACELL" }, /*::[*/0x0016/*::]*/: { n:"LABEL16", f:parse_LABEL_16}, /*::[*/0x0017/*::]*/: { n:"NUMBER17", f:parse_NUMBER_17 }, /*::[*/0x0018/*::]*/: { n:"NUMBER18", f:parse_NUMBER_18 }, /*::[*/0x0019/*::]*/: { n:"FORMULA19", f:parse_FORMULA_19}, - /*::[*/0x001a/*::]*/: { n:"??" }, - /*::[*/0x001b/*::]*/: { n:"??" }, - /*::[*/0x001c/*::]*/: { n:"??" }, - /*::[*/0x001d/*::]*/: { n:"??" }, - /*::[*/0x001e/*::]*/: { n:"??" }, - /*::[*/0x001f/*::]*/: { n:"??" }, - /*::[*/0x0021/*::]*/: { n:"??" }, + /*::[*/0x001A/*::]*/: { n:"FORMULA1A" }, + /*::[*/0x001B/*::]*/: { n:"XFORMAT" }, + /*::[*/0x001C/*::]*/: { n:"DTLABELMISC" }, + /*::[*/0x001D/*::]*/: { n:"DTLABELCELL" }, + /*::[*/0x001E/*::]*/: { n:"GRAPHWINDOW" }, + /*::[*/0x001F/*::]*/: { n:"CPA" }, + /*::[*/0x0020/*::]*/: { n:"LPLAUTO" }, + /*::[*/0x0021/*::]*/: { n:"QUERY" }, + /*::[*/0x0022/*::]*/: { n:"HIDDENSHEET" }, + /*::[*/0x0023/*::]*/: { n:"??" }, /*::[*/0x0025/*::]*/: { n:"NUMBER25", f:parse_NUMBER_25 }, + /*::[*/0x0026/*::]*/: { n:"??" }, /*::[*/0x0027/*::]*/: { n:"NUMBER27", f:parse_NUMBER_27 }, /*::[*/0x0028/*::]*/: { n:"FORMULA28", f:parse_FORMULA_28 }, - /*::[*/0x00FF/*::]*/: { n:"", f:parsenoop } + /*::[*/0x008E/*::]*/: { n:"??" }, + /*::[*/0x0093/*::]*/: { n:"??" }, + /*::[*/0x0096/*::]*/: { n:"??" }, + /*::[*/0x0097/*::]*/: { n:"??" }, + /*::[*/0x0098/*::]*/: { n:"??" }, + /*::[*/0x0099/*::]*/: { n:"??" }, + /*::[*/0x009A/*::]*/: { n:"??" }, + /*::[*/0x009B/*::]*/: { n:"??" }, + /*::[*/0x009C/*::]*/: { n:"??" }, + /*::[*/0x00A3/*::]*/: { n:"??" }, + /*::[*/0x00AE/*::]*/: { n:"??" }, + /*::[*/0x00AF/*::]*/: { n:"??" }, + /*::[*/0x00B0/*::]*/: { n:"??" }, + /*::[*/0x00B1/*::]*/: { n:"??" }, + /*::[*/0x00B8/*::]*/: { n:"??" }, + /*::[*/0x00B9/*::]*/: { n:"??" }, + /*::[*/0x00BA/*::]*/: { n:"??" }, + /*::[*/0x00BB/*::]*/: { n:"??" }, + /*::[*/0x00BC/*::]*/: { n:"??" }, + /*::[*/0x00C3/*::]*/: { n:"??" }, + /*::[*/0x00C9/*::]*/: { n:"??" }, + /*::[*/0x00CD/*::]*/: { n:"??" }, + /*::[*/0x00CE/*::]*/: { n:"??" }, + /*::[*/0x00CF/*::]*/: { n:"??" }, + /*::[*/0x00D0/*::]*/: { n:"??" }, + /*::[*/0x0100/*::]*/: { n:"??" }, + /*::[*/0x0103/*::]*/: { n:"??" }, + /*::[*/0x0104/*::]*/: { n:"??" }, + /*::[*/0x0105/*::]*/: { n:"??" }, + /*::[*/0x0106/*::]*/: { n:"??" }, + /*::[*/0x0107/*::]*/: { n:"??" }, + /*::[*/0x0109/*::]*/: { n:"??" }, + /*::[*/0x010A/*::]*/: { n:"??" }, + /*::[*/0x010B/*::]*/: { n:"??" }, + /*::[*/0x010C/*::]*/: { n:"??" }, + /*::[*/0x010E/*::]*/: { n:"??" }, + /*::[*/0x010F/*::]*/: { n:"??" }, + /*::[*/0x0180/*::]*/: { n:"??" }, + /*::[*/0x0185/*::]*/: { n:"??" }, + /*::[*/0x0186/*::]*/: { n:"??" }, + /*::[*/0x0189/*::]*/: { n:"??" }, + /*::[*/0x018C/*::]*/: { n:"??" }, + /*::[*/0x0200/*::]*/: { n:"??" }, + /*::[*/0x0202/*::]*/: { n:"??" }, + /*::[*/0x0201/*::]*/: { n:"??" }, + /*::[*/0x0204/*::]*/: { n:"??" }, + /*::[*/0x0205/*::]*/: { n:"??" }, + /*::[*/0x0280/*::]*/: { n:"??" }, + /*::[*/0x0281/*::]*/: { n:"??" }, + /*::[*/0x0282/*::]*/: { n:"??" }, + /*::[*/0x0283/*::]*/: { n:"??" }, + /*::[*/0x0284/*::]*/: { n:"??" }, + /*::[*/0x0285/*::]*/: { n:"??" }, + /*::[*/0x0286/*::]*/: { n:"??" }, + /*::[*/0x0287/*::]*/: { n:"??" }, + /*::[*/0x0288/*::]*/: { n:"??" }, + /*::[*/0x0292/*::]*/: { n:"??" }, + /*::[*/0x0293/*::]*/: { n:"??" }, + /*::[*/0x0294/*::]*/: { n:"??" }, + /*::[*/0x0295/*::]*/: { n:"??" }, + /*::[*/0x0296/*::]*/: { n:"??" }, + /*::[*/0x0299/*::]*/: { n:"??" }, + /*::[*/0x029A/*::]*/: { n:"??" }, + /*::[*/0x0300/*::]*/: { n:"??" }, + /*::[*/0x0304/*::]*/: { n:"??" }, + /*::[*/0x0640/*::]*/: { n:"??" }, + /*::[*/0x0642/*::]*/: { n:"??" }, + /*::[*/0x0701/*::]*/: { n:"??" }, + /*::[*/0x0702/*::]*/: { n:"??" }, + /*::[*/0x0703/*::]*/: { n:"??" }, + /*::[*/0x0704/*::]*/: { n:"??" }, + /*::[*/0x0780/*::]*/: { n:"??" }, + /*::[*/0x0800/*::]*/: { n:"??" }, + /*::[*/0x0801/*::]*/: { n:"??" }, + /*::[*/0x0804/*::]*/: { n:"??" }, + /*::[*/0x0A80/*::]*/: { n:"??" }, + /*::[*/0x2AF6/*::]*/: { n:"??" }, + /*::[*/0x3231/*::]*/: { n:"??" }, + /*::[*/0x6E49/*::]*/: { n:"??" }, + /*::[*/0x6F44/*::]*/: { n:"??" }, + /*::[*/0xFFFF/*::]*/: { n:"" } }; return { + sheet_to_wk1: sheet_to_wk1, to_workbook: lotus_to_workbook }; })(); diff --git a/bits/75_xlml.js b/bits/75_xlml.js index 9fec98b..2487ca7 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -472,6 +472,8 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]); break; + case 'null' /*case 'Null'*/: break; + default: /* FODS file root is */ if(state.length == 0 && Rn[3] == "document") return parse_fods(str, opts); diff --git a/bits/78_writebiff.js b/bits/78_writebiff.js index fda4a84..ab6c0fc 100644 --- a/bits/78_writebiff.js +++ b/bits/78_writebiff.js @@ -1,5 +1,5 @@ function write_biff_rec(ba/*:BufArray*/, type/*:number|string*/, payload, length/*:?number*/)/*:void*/ { - var t/*:number*/ = +type || +XLSRE[/*::String(*/type/*::)*/]; + var t/*:number*/ = typeof type == "number" ? type : (+type || +XLSRE[/*::String(*/type/*::)*/]); if(isNaN(t)) return; var len = length || (payload||[]).length || 0; var o = ba.next(4); diff --git a/bits/82_sheeter.js b/bits/82_sheeter.js index e9f2ae1..86e0ca2 100644 --- a/bits/82_sheeter.js +++ b/bits/82_sheeter.js @@ -21,4 +21,5 @@ var write_rtf_str = write_obj_str(typeof RTF !== "undefined" ? RTF : {}); var write_txt_str = write_obj_str({from_sheet:sheet_to_txt}); var write_dbf_buf = write_obj_str(typeof DBF !== "undefined" ? DBF : {}); var write_eth_str = write_obj_str(typeof ETH !== "undefined" ? ETH : {}); +var write_wk1_buf = write_obj_str(typeof WK_ !== "undefined" ? {from_sheet:WK_.sheet_to_wk1} : {}); diff --git a/bits/87_read.js b/bits/87_read.js index c5af312..bb680a2 100644 --- a/bits/87_read.js +++ b/bits/87_read.js @@ -96,6 +96,7 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ { 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; } 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/88_write.js b/bits/88_write.js index 5d54dca..240f48d 100644 --- a/bits/88_write.js +++ b/bits/88_write.js @@ -105,6 +105,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { case 'rtf': return write_string_type(write_rtf_str(wb, o), o); case 'eth': return write_string_type(write_eth_str(wb, o), o); case 'fods': return write_string_type(write_ods(wb, o), o); + case 'wk1': return write_binary_type(write_wk1_buf(wb, o), o); case 'biff2': if(!o.biff) o.biff = 2; /* falls through */ case 'biff3': if(!o.biff) o.biff = 3; /* falls through */ case 'biff4': if(!o.biff) o.biff = 4; return write_binary_type(write_biff_buf(wb, o), o); diff --git a/docbits/81_writeopts.md b/docbits/81_writeopts.md index a9d5662..98e0985 100644 --- a/docbits/81_writeopts.md +++ b/docbits/81_writeopts.md @@ -53,6 +53,7 @@ output formats. The specific file type is controlled with `bookType` option: | `html` | `.html` | none | single | HTML Document | | `dif` | `.dif` | none | single | Data Interchange Format (DIF) | | `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) | +| `wk1` | `.wk1` | none | single | Lotus Worksheet (WK1) | | `rtf` | `.rtf` | none | single | Rich Text Format (RTF) | | `prn` | `.prn` | none | single | Lotus Formatted Text | | `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) | diff --git a/docbits/85_filetype.md b/docbits/85_filetype.md index fdde3d7..3d1d0a4 100644 --- a/docbits/85_filetype.md +++ b/docbits/85_filetype.md @@ -24,7 +24,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | 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) | ✔ | | +| Lotus 1-2-3 (WK1) | ✔ | ✔ | +| Lotus 1-2-3 (WKS/WK2/WK3/WK4/123) | ✔ | | | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | | | **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | HTML Tables | ✔ | ✔ | @@ -162,6 +163,8 @@ The Lotus formats consist of binary records similar to the BIFF structure. Lotus did release a specification decades ago covering the original WK1 format. Other features were deduced by producing files and comparing to Excel support. +Generated WK1 files are compatible with Lotus 1-2-3 v2 and Excel 5.0. + #### Quattro Pro (WQ1/WQ2/WB1/WB2/WB3/QPW) diff --git a/formats.png b/formats.png index fae2a8e..a28ab0d 100644 Binary files a/formats.png and b/formats.png differ diff --git a/misc/formats.dot b/misc/formats.dot index 36cb62f..8bbc2d9 100644 --- a/misc/formats.dot +++ b/misc/formats.dot @@ -66,6 +66,7 @@ digraph G { slk -> csf csf -> dif wk1 -> csf + csf -> wk1 wqb -> csf dif -> csf csf -> rtf diff --git a/packages/xlsx-cli/index.js b/packages/xlsx-cli/index.js index fe8f058..7c233c4 100644 --- a/packages/xlsx-cli/index.js +++ b/packages/xlsx-cli/index.js @@ -44,6 +44,7 @@ function run() { .option('-E, --eth', 'emit ETH to or .eth (Ethercalc)') .option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)') .option('-r, --rtf', 'emit RTF to or .txt (Table RTF)') + .option('--wk1', 'emit WK1 to or .txt (Lotus WK1)') .option('-z, --dump', 'dump internal representation as JSON') .option('--props', 'dump workbook properties as CSV') @@ -221,6 +222,7 @@ function run() { ['rtf', '.rtf'], ['txt', '.txt'], ['dbf', '.dbf'], + ['wk1', '.wk1'], ['dif', '.dif'] ].forEach(function (m) { if (program[m[0]] || isfmt(m[1])) {