Lotus WK1 worksheet writer [ci skip]

- unpack errors from WK3 long double
- explicitly ignore ss:Null in XLML (fixes )
This commit is contained in:
SheetJS 2021-11-07 23:52:18 -05:00
parent c1f5f041a4
commit 29e3bb4c1f
13 changed files with 235 additions and 44 deletions

@ -56,6 +56,7 @@ program
.option('-E, --eth', 'emit ETH to <sheetname> or <file>.eth (Ethercalc)') .option('-E, --eth', 'emit ETH to <sheetname> or <file>.eth (Ethercalc)')
.option('-t, --txt', 'emit TXT to <sheetname> or <file>.txt (UTF-8 TSV)') .option('-t, --txt', 'emit TXT to <sheetname> or <file>.txt (UTF-8 TSV)')
.option('-r, --rtf', 'emit RTF to <sheetname> or <file>.txt (Table RTF)') .option('-r, --rtf', 'emit RTF to <sheetname> or <file>.txt (Table RTF)')
.option('--wk1', 'emit WK1 to <sheetname> or <file>.txt (Lotus WK1)')
.option('-z, --dump', 'dump internal representation as JSON') .option('-z, --dump', 'dump internal representation as JSON')
.option('--props', 'dump workbook properties as CSV') .option('--props', 'dump workbook properties as CSV')
@ -229,6 +230,7 @@ if(!program.quiet && !program.book) console.error(target_sheet);
['rtf', '.rtf'], ['rtf', '.rtf'],
['txt', '.txt'], ['txt', '.txt'],
['dbf', '.dbf'], ['dbf', '.dbf'],
['wk1', '.wk1'],
['dif', '.dif'] ['dif', '.dif']
].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) { ].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) {
wopts.bookType = m[0]; wopts.bookType = m[0];

@ -12,7 +12,7 @@ function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {
length = tmpbyte & 0x7F; length = tmpbyte & 0x7F;
for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte); for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);
tgt = data.l + length; tgt = data.l + length;
var d = (R.f||parsenoop)(data, length, opts); var d = R.f && R.f(data, length, opts);
data.l = tgt; data.l = tgt;
if(cb(d, R.n, RT)) return; if(cb(d, R.n, RT)) return;
} }

@ -1,16 +1,16 @@
var WK_ = (function() { var WK_ = /*#__PURE__*/ (function() {
function lotushopper(data, cb/*:RecordHopperCB*/, opts/*:any*/) { function lotushopper(data, cb/*:RecordHopperCB*/, opts/*:any*/) {
if(!data) return; if(!data) return;
prep_blob(data, data.l || 0); prep_blob(data, data.l || 0);
var Enum = opts.Enum || WK1Enum; var Enum = opts.Enum || WK1Enum;
while(data.l < data.length) { while(data.l < data.length) {
var RT = data.read_shift(2); 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 length = data.read_shift(2);
var tgt = data.l + length; 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; 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 refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
var sheetRows = o.sheetRows || 0; var sheetRows = o.sheetRows || 0;
if(d[2] == 0x02) o.Enum = WK1Enum; if(d[2] == 0x02) {
else if(d[2] == 0x1a) o.Enum = WK3Enum; o.Enum = WK1Enum;
else if(d[2] == 0x0e) { o.Enum = WK3Enum; o.qpro = true; d.l = 0; } lotushopper(d, function(val, R, RT) { switch(RT) {
else throw new Error("Unrecognized LOTUS BOF " + d[2]);
lotushopper(d, function(val, Rn, RT) {
if(d[2] == 0x02) switch(RT) {
case 0x00: case 0x00:
o.vers = val; o.vers = val;
if(val >= 0x1000) o.qpro = true; if(val >= 0x1000) o.qpro = true;
@ -62,7 +59,11 @@ var WK_ = (function() {
s[val[0].r][val[0].c] = val[1]; s[val[0].r][val[0].c] = val[1];
} else s[encode_cell(val[0])] = val[1]; } else s[encode_cell(val[0])] = val[1];
break; 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 */ case 0x16: /* LABEL16 */
val[1].v = val[1].v.slice(1); val[1].v = val[1].v.slice(1);
/* falls through */ /* falls through */
@ -89,14 +90,53 @@ var WK_ = (function() {
if(refguess.e.r < val[0].r) refguess.e.r = val[0].r; if(refguess.e.r < val[0].r) refguess.e.r = val[0].r;
break; break;
default: break; default: break;
} }}, o);
}, o); } else throw new Error("Unrecognized LOTUS BOF " + d[2]);
s["!ref"] = encode_range(refguess); s["!ref"] = encode_range(refguess);
sheets[n] = s; sheets[n] = s;
return { SheetNames: snames, Sheets:sheets }; 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) { function parse_RANGE(blob) {
var o = {s:{c:0,r:0},e:{c:0,r:0}}; var o = {s:{c:0,r:0},e:{c:0,r:0}};
o.s.c = blob.read_shift(2); 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; if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0;
return o; 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) { 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];
@ -135,18 +183,48 @@ var WK_ = (function() {
o[1].v = blob.read_shift(tgt - blob.l, 'cstr'); o[1].v = blob.read_shift(tgt - blob.l, 'cstr');
return o; 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) { function parse_INTEGER(blob, length, opts) {
var o = parse_cell(blob, length, opts); var o = parse_cell(blob, length, opts);
o[1].v = blob.read_shift(2, 'i'); o[1].v = blob.read_shift(2, 'i');
return o; 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) { function parse_NUMBER(blob, length, opts) {
var o = parse_cell(blob, length, opts); var o = parse_cell(blob, length, opts);
o[1].v = blob.read_shift(8, 'f'); o[1].v = blob.read_shift(8, 'f');
return o; 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) { function parse_FORMULA(blob, length, opts) {
var tgt = blob.l + length; var tgt = blob.l + length;
@ -178,15 +256,16 @@ var WK_ = (function() {
var o = parse_cell_3(blob, length); var o = parse_cell_3(blob, length);
o[1].v = blob.read_shift(2); o[1].v = blob.read_shift(2);
var v = o[1].v >> 1; var v = o[1].v >> 1;
/* TODO: figure out all of the corner cases */
if(o[1].v & 0x1) { if(o[1].v & 0x1) {
switch(v & 0x07) { switch(v & 0x07) {
case 0: v = (v >> 3) * 5000; break;
case 1: v = (v >> 3) * 500; break; case 1: v = (v >> 3) * 500; break;
case 2: v = (v >> 3) / 20; break; case 2: v = (v >> 3) / 20; break;
case 3: v = (v >> 3) / 200; break;
case 4: v = (v >> 3) / 2000; break; case 4: v = (v >> 3) / 2000; break;
case 5: v = (v >> 3) / 20000; break;
case 6: v = (v >> 3) / 16; break; case 6: v = (v >> 3) / 16; break;
case 7: v = (v >> 3) / 64; break; case 7: v = (v >> 3) / 64; break;
default: throw "unknown NUMBER_18 encoding " + (v & 0x07);
} }
} }
o[1].v = v; o[1].v = v;
@ -198,9 +277,14 @@ var WK_ = (function() {
var v1 = blob.read_shift(4); var v1 = blob.read_shift(4);
var v2 = blob.read_shift(4); var v2 = blob.read_shift(4);
var e = blob.read_shift(2); 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; 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; return o;
} }
@ -288,45 +372,138 @@ var WK_ = (function() {
/*::[*/0x0048/*::]*/: { n:"ACOMM" }, /*::[*/0x0048/*::]*/: { n:"ACOMM" },
/*::[*/0x0049/*::]*/: { n:"AMACRO" }, /*::[*/0x0049/*::]*/: { n:"AMACRO" },
/*::[*/0x004A/*::]*/: { n:"PARSE" }, /*::[*/0x004A/*::]*/: { n:"PARSE" },
/*::[*/0x00FF/*::]*/: { n:"", f:parsenoop } /*::[*/0x0066/*::]*/: { n:"PRANGES??" },
/*::[*/0x0067/*::]*/: { n:"RRANGES??" },
/*::[*/0x0068/*::]*/: { n:"FNAME??" },
/*::[*/0x0069/*::]*/: { n:"MRANGES??" },
/*::[*/0xFFFF/*::]*/: { n:"" }
}; };
var WK3Enum = { var WK3Enum = {
/*::[*/0x0000/*::]*/: { n:"BOF" }, /*::[*/0x0000/*::]*/: { n:"BOF" },
/*::[*/0x0001/*::]*/: { n:"EOF" }, /*::[*/0x0001/*::]*/: { n:"EOF" },
/*::[*/0x0003/*::]*/: { n:"??" }, /*::[*/0x0002/*::]*/: { n:"PASSWORD" },
/*::[*/0x0004/*::]*/: { n:"??" }, /*::[*/0x0003/*::]*/: { n:"CALCSET" },
/*::[*/0x0005/*::]*/: { n:"??" }, /*::[*/0x0004/*::]*/: { n:"WINDOWSET" },
/*::[*/0x0006/*::]*/: { n:"??" }, /*::[*/0x0005/*::]*/: { n:"SHEETCELLPTR" },
/*::[*/0x0007/*::]*/: { n:"??" }, /*::[*/0x0006/*::]*/: { n:"SHEETLAYOUT" },
/*::[*/0x0009/*::]*/: { n:"??" }, /*::[*/0x0007/*::]*/: { n:"COLUMNWIDTH" },
/*::[*/0x000a/*::]*/: { n:"??" }, /*::[*/0x0008/*::]*/: { n:"HIDDENCOLUMN" },
/*::[*/0x000b/*::]*/: { n:"??" }, /*::[*/0x0009/*::]*/: { n:"USERRANGE" },
/*::[*/0x000c/*::]*/: { n:"??" }, /*::[*/0x000A/*::]*/: { n:"SYSTEMRANGE" },
/*::[*/0x000e/*::]*/: { n:"??" }, /*::[*/0x000B/*::]*/: { n:"ZEROFORCE" },
/*::[*/0x000f/*::]*/: { n:"??" }, /*::[*/0x000C/*::]*/: { n:"SORTKEYDIR" },
/*::[*/0x0010/*::]*/: { n:"??" }, /*::[*/0x000D/*::]*/: { n:"FILESEAL" },
/*::[*/0x0011/*::]*/: { n:"??" }, /*::[*/0x000E/*::]*/: { n:"DATAFILLNUMS" },
/*::[*/0x0012/*::]*/: { n:"??" }, /*::[*/0x000F/*::]*/: { n:"PRINTMAIN" },
/*::[*/0x0010/*::]*/: { n:"PRINTSTRING" },
/*::[*/0x0011/*::]*/: { n:"GRAPHMAIN" },
/*::[*/0x0012/*::]*/: { n:"GRAPHSTRING" },
/*::[*/0x0013/*::]*/: { n:"??" }, /*::[*/0x0013/*::]*/: { n:"??" },
/*::[*/0x0015/*::]*/: { n:"??" }, /*::[*/0x0014/*::]*/: { n:"ERRCELL" },
/*::[*/0x0015/*::]*/: { n:"NACELL" },
/*::[*/0x0016/*::]*/: { n:"LABEL16", f:parse_LABEL_16}, /*::[*/0x0016/*::]*/: { n:"LABEL16", f:parse_LABEL_16},
/*::[*/0x0017/*::]*/: { n:"NUMBER17", f:parse_NUMBER_17 }, /*::[*/0x0017/*::]*/: { n:"NUMBER17", f:parse_NUMBER_17 },
/*::[*/0x0018/*::]*/: { n:"NUMBER18", f:parse_NUMBER_18 }, /*::[*/0x0018/*::]*/: { n:"NUMBER18", f:parse_NUMBER_18 },
/*::[*/0x0019/*::]*/: { n:"FORMULA19", f:parse_FORMULA_19}, /*::[*/0x0019/*::]*/: { n:"FORMULA19", f:parse_FORMULA_19},
/*::[*/0x001a/*::]*/: { n:"??" }, /*::[*/0x001A/*::]*/: { n:"FORMULA1A" },
/*::[*/0x001b/*::]*/: { n:"??" }, /*::[*/0x001B/*::]*/: { n:"XFORMAT" },
/*::[*/0x001c/*::]*/: { n:"??" }, /*::[*/0x001C/*::]*/: { n:"DTLABELMISC" },
/*::[*/0x001d/*::]*/: { n:"??" }, /*::[*/0x001D/*::]*/: { n:"DTLABELCELL" },
/*::[*/0x001e/*::]*/: { n:"??" }, /*::[*/0x001E/*::]*/: { n:"GRAPHWINDOW" },
/*::[*/0x001f/*::]*/: { n:"??" }, /*::[*/0x001F/*::]*/: { n:"CPA" },
/*::[*/0x0021/*::]*/: { n:"??" }, /*::[*/0x0020/*::]*/: { n:"LPLAUTO" },
/*::[*/0x0021/*::]*/: { n:"QUERY" },
/*::[*/0x0022/*::]*/: { n:"HIDDENSHEET" },
/*::[*/0x0023/*::]*/: { n:"??" },
/*::[*/0x0025/*::]*/: { n:"NUMBER25", f:parse_NUMBER_25 }, /*::[*/0x0025/*::]*/: { n:"NUMBER25", f:parse_NUMBER_25 },
/*::[*/0x0026/*::]*/: { n:"??" },
/*::[*/0x0027/*::]*/: { n:"NUMBER27", f:parse_NUMBER_27 }, /*::[*/0x0027/*::]*/: { n:"NUMBER27", f:parse_NUMBER_27 },
/*::[*/0x0028/*::]*/: { n:"FORMULA28", f:parse_FORMULA_28 }, /*::[*/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 { return {
sheet_to_wk1: sheet_to_wk1,
to_workbook: lotus_to_workbook to_workbook: lotus_to_workbook
}; };
})(); })();

@ -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]); else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
break; break;
case 'null' /*case 'Null'*/: break;
default: default:
/* FODS file root is <office:document> */ /* FODS file root is <office:document> */
if(state.length == 0 && Rn[3] == "document") return parse_fods(str, opts); if(state.length == 0 && Rn[3] == "document") return parse_fods(str, opts);

@ -1,5 +1,5 @@
function write_biff_rec(ba/*:BufArray*/, type/*:number|string*/, payload, length/*:?number*/)/*:void*/ { 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; if(isNaN(t)) return;
var len = length || (payload||[]).length || 0; var len = length || (payload||[]).length || 0;
var o = ba.next(4); var o = ba.next(4);

@ -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_txt_str = write_obj_str({from_sheet:sheet_to_txt});
var write_dbf_buf = write_obj_str(typeof DBF !== "undefined" ? DBF : {}); var write_dbf_buf = write_obj_str(typeof DBF !== "undefined" ? DBF : {});
var write_eth_str = write_obj_str(typeof ETH !== "undefined" ? ETH : {}); 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} : {});

@ -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 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 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 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); 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); return read_prn(data, d, o, str);

@ -105,6 +105,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'rtf': return write_string_type(write_rtf_str(wb, o), o); 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 'eth': return write_string_type(write_eth_str(wb, o), o);
case 'fods': return write_string_type(write_ods(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 'biff2': if(!o.biff) o.biff = 2; /* falls through */
case 'biff3': if(!o.biff) o.biff = 3; /* 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); case 'biff4': if(!o.biff) o.biff = 4; return write_binary_type(write_biff_buf(wb, o), o);

@ -53,6 +53,7 @@ output formats. The specific file type is controlled with `bookType` option:
| `html` | `.html` | none | single | HTML Document | | `html` | `.html` | none | single | HTML Document |
| `dif` | `.dif` | none | single | Data Interchange Format (DIF) | | `dif` | `.dif` | none | single | Data Interchange Format (DIF) |
| `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) | | `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) |
| `wk1` | `.wk1` | none | single | Lotus Worksheet (WK1) |
| `rtf` | `.rtf` | none | single | Rich Text Format (RTF) | | `rtf` | `.rtf` | none | single | Rich Text Format (RTF) |
| `prn` | `.prn` | none | single | Lotus Formatted Text | | `prn` | `.prn` | none | single | Lotus Formatted Text |
| `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) | | `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) |

@ -24,7 +24,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ | | Flat XML ODF Spreadsheet (FODS) | ✔ | ✔ |
| Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | | | Uniform Office Format Spreadsheet (标文通 UOS1/UOS2) | ✔ | |
| dBASE II/III/IV / Visual FoxPro (DBF) | ✔ | ✔ | | 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) | ✔ | | | Quattro Pro Spreadsheet (WQ1/WQ2/WB1/WB2/WB3/QPW) | ✔ | |
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:| | **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | ✔ | ✔ | | 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 did release a specification decades ago covering the original WK1 format. Other
features were deduced by producing files and comparing to Excel support. 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.
</details> </details>
#### Quattro Pro (WQ1/WQ2/WB1/WB2/WB3/QPW) #### Quattro Pro (WQ1/WQ2/WB1/WB2/WB3/QPW)

Binary file not shown.

Before

(image error) Size: 439 KiB

After

(image error) Size: 444 KiB

@ -66,6 +66,7 @@ digraph G {
slk -> csf slk -> csf
csf -> dif csf -> dif
wk1 -> csf wk1 -> csf
csf -> wk1
wqb -> csf wqb -> csf
dif -> csf dif -> csf
csf -> rtf csf -> rtf

@ -44,6 +44,7 @@ function run() {
.option('-E, --eth', 'emit ETH to <sheetname> or <file>.eth (Ethercalc)') .option('-E, --eth', 'emit ETH to <sheetname> or <file>.eth (Ethercalc)')
.option('-t, --txt', 'emit TXT to <sheetname> or <file>.txt (UTF-8 TSV)') .option('-t, --txt', 'emit TXT to <sheetname> or <file>.txt (UTF-8 TSV)')
.option('-r, --rtf', 'emit RTF to <sheetname> or <file>.txt (Table RTF)') .option('-r, --rtf', 'emit RTF to <sheetname> or <file>.txt (Table RTF)')
.option('--wk1', 'emit WK1 to <sheetname> or <file>.txt (Lotus WK1)')
.option('-z, --dump', 'dump internal representation as JSON') .option('-z, --dump', 'dump internal representation as JSON')
.option('--props', 'dump workbook properties as CSV') .option('--props', 'dump workbook properties as CSV')
@ -221,6 +222,7 @@ function run() {
['rtf', '.rtf'], ['rtf', '.rtf'],
['txt', '.txt'], ['txt', '.txt'],
['dbf', '.dbf'], ['dbf', '.dbf'],
['wk1', '.wk1'],
['dif', '.dif'] ['dif', '.dif']
].forEach(function (m) { ].forEach(function (m) {
if (program[m[0]] || isfmt(m[1])) { if (program[m[0]] || isfmt(m[1])) {