Skip to content

Commit 29e3bb4

Browse files
committed
Lotus WK1 worksheet writer [ci skip]
- unpack errors from WK3 long double - explicitly ignore ss:Null in XLML (fixes #2447)
1 parent c1f5f04 commit 29e3bb4

File tree

13 files changed

+235
-44
lines changed

13 files changed

+235
-44
lines changed

bin/xlsx.njs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ program
5656
.option('-E, --eth', 'emit ETH to <sheetname> or <file>.eth (Ethercalc)')
5757
.option('-t, --txt', 'emit TXT to <sheetname> or <file>.txt (UTF-8 TSV)')
5858
.option('-r, --rtf', 'emit RTF to <sheetname> or <file>.txt (Table RTF)')
59+
.option('--wk1', 'emit WK1 to <sheetname> or <file>.txt (Lotus WK1)')
5960
.option('-z, --dump', 'dump internal representation as JSON')
6061
.option('--props', 'dump workbook properties as CSV')
6162

@@ -229,6 +230,7 @@ if(!program.quiet && !program.book) console.error(target_sheet);
229230
['rtf', '.rtf'],
230231
['txt', '.txt'],
231232
['dbf', '.dbf'],
233+
['wk1', '.wk1'],
232234
['dif', '.dif']
233235
].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) {
234236
wopts.bookType = m[0];

bits/24_hoppers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {
1212
length = tmpbyte & 0x7F;
1313
for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);
1414
tgt = data.l + length;
15-
var d = (R.f||parsenoop)(data, length, opts);
15+
var d = R.f && R.f(data, length, opts);
1616
data.l = tgt;
1717
if(cb(d, R.n, RT)) return;
1818
}

bits/41_lotus.js

Lines changed: 218 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
var WK_ = (function() {
1+
var WK_ = /*#__PURE__*/ (function() {
22
function lotushopper(data, cb/*:RecordHopperCB*/, opts/*:any*/) {
33
if(!data) return;
44
prep_blob(data, data.l || 0);
55
var Enum = opts.Enum || WK1Enum;
66
while(data.l < data.length) {
77
var RT = data.read_shift(2);
8-
var R = Enum[RT] || Enum[0xFF];
8+
var R = Enum[RT] || Enum[0xFFFF];
99
var length = data.read_shift(2);
1010
var tgt = data.l + length;
11-
var d = (R.f||parsenoop)(data, length, opts);
11+
var d = R.f && R.f(data, length, opts);
1212
data.l = tgt;
13-
if(cb(d, R.n, RT)) return;
13+
if(cb(d, R, RT)) return;
1414
}
1515
}
1616

@@ -34,12 +34,9 @@ var WK_ = (function() {
3434
var refguess = {s: {r:0, c:0}, e: {r:0, c:0} };
3535
var sheetRows = o.sheetRows || 0;
3636

37-
if(d[2] == 0x02) o.Enum = WK1Enum;
38-
else if(d[2] == 0x1a) o.Enum = WK3Enum;
39-
else if(d[2] == 0x0e) { o.Enum = WK3Enum; o.qpro = true; d.l = 0; }
40-
else throw new Error("Unrecognized LOTUS BOF " + d[2]);
41-
lotushopper(d, function(val, Rn, RT) {
42-
if(d[2] == 0x02) switch(RT) {
37+
if(d[2] == 0x02) {
38+
o.Enum = WK1Enum;
39+
lotushopper(d, function(val, R, RT) { switch(RT) {
4340
case 0x00:
4441
o.vers = val;
4542
if(val >= 0x1000) o.qpro = true;
@@ -62,7 +59,11 @@ var WK_ = (function() {
6259
s[val[0].r][val[0].c] = val[1];
6360
} else s[encode_cell(val[0])] = val[1];
6461
break;
65-
} else switch(RT) {
62+
}}, o);
63+
} else if(d[2] == 0x1A || d[2] == 0x0E) {
64+
o.Enum = WK3Enum;
65+
if(d[2] == 0x0E) { o.qpro = true; d.l = 0; }
66+
lotushopper(d, function(val, R, RT) { switch(RT) {
6667
case 0x16: /* LABEL16 */
6768
val[1].v = val[1].v.slice(1);
6869
/* falls through */
@@ -89,14 +90,53 @@ var WK_ = (function() {
8990
if(refguess.e.r < val[0].r) refguess.e.r = val[0].r;
9091
break;
9192
default: break;
92-
}
93-
}, o);
93+
}}, o);
94+
} else throw new Error("Unrecognized LOTUS BOF " + d[2]);
9495

9596
s["!ref"] = encode_range(refguess);
9697
sheets[n] = s;
9798
return { SheetNames: snames, Sheets:sheets };
9899
}
99100

101+
function sheet_to_wk1(ws/*:Worksheet*/, opts/*:WriteOpts*/) {
102+
var o = opts || {};
103+
if(+o.codepage >= 0) set_cp(+o.codepage);
104+
if(o.type == "string") throw new Error("Cannot write DBF to JS string");
105+
var ba = buf_array();
106+
var range = safe_decode_range(ws["!ref"]);
107+
var dense = Array.isArray(ws);
108+
var cols = [];
109+
110+
write_biff_rec(ba, 0x00, write_BOF_WK1(0x0406));
111+
write_biff_rec(ba, 0x06, write_RANGE(range));
112+
for(var R = range.s.r; R <= range.e.r; ++R) {
113+
var rr = encode_row(R);
114+
for(var C = range.s.c; C <= range.e.c; ++C) {
115+
if(R === range.s.r) cols[C] = encode_col(C);
116+
var ref = cols[C] + rr;
117+
var cell = dense ? (ws[R]||[])[C] : ws[ref];
118+
if(!cell || cell.t == "z") continue;
119+
/* write cell */
120+
if(cell.t == "n") {
121+
if((cell.v|0)==cell.v && cell.v >= -32768 && cell.v <= 32767) write_biff_rec(ba, 0x0d, write_INTEGER(R, C, cell.v));
122+
else write_biff_rec(ba, 0x0e, write_NUMBER(R, C, cell.v));
123+
} else {
124+
var str = format_cell(cell);
125+
write_biff_rec(ba, 0x0F, write_LABEL(R, C, str.slice(0, 239)));
126+
}
127+
}
128+
}
129+
130+
write_biff_rec(ba, 0x01);
131+
return ba.end();
132+
}
133+
134+
function write_BOF_WK1(v/*:number*/) {
135+
var out = new_buf(2);
136+
out.write_shift(2, v);
137+
return out;
138+
}
139+
100140
function parse_RANGE(blob) {
101141
var o = {s:{c:0,r:0},e:{c:0,r:0}};
102142
o.s.c = blob.read_shift(2);
@@ -106,6 +146,14 @@ var WK_ = (function() {
106146
if(o.s.c == 0xFFFF) o.s.c = o.e.c = o.s.r = o.e.r = 0;
107147
return o;
108148
}
149+
function write_RANGE(range) {
150+
var out = new_buf(8);
151+
out.write_shift(2, range.s.c);
152+
out.write_shift(2, range.s.r);
153+
out.write_shift(2, range.e.c);
154+
out.write_shift(2, range.e.r);
155+
return out;
156+
}
109157

110158
function parse_cell(blob, length, opts) {
111159
var o = [{c:0,r:0}, {t:'n',v:0}, 0];
@@ -135,18 +183,48 @@ var WK_ = (function() {
135183
o[1].v = blob.read_shift(tgt - blob.l, 'cstr');
136184
return o;
137185
}
186+
function write_LABEL(R, C, s) {
187+
/* TODO: encoding */
188+
var o = new_buf(7 + s.length);
189+
o.write_shift(1, 0xFF);
190+
o.write_shift(2, C);
191+
o.write_shift(2, R);
192+
o.write_shift(1, 0x27); // ??
193+
for(var i = 0; i < o.length; ++i) {
194+
var cc = s.charCodeAt(i);
195+
o.write_shift(1, cc >= 0x80 ? 0x5F : cc);
196+
}
197+
o.write_shift(1, 0);
198+
return o;
199+
}
138200

139201
function parse_INTEGER(blob, length, opts) {
140202
var o = parse_cell(blob, length, opts);
141203
o[1].v = blob.read_shift(2, 'i');
142204
return o;
143205
}
206+
function write_INTEGER(R, C, v) {
207+
var o = new_buf(7);
208+
o.write_shift(1, 0xFF);
209+
o.write_shift(2, C);
210+
o.write_shift(2, R);
211+
o.write_shift(2, v, 'i');
212+
return o;
213+
}
144214

145215
function parse_NUMBER(blob, length, opts) {
146216
var o = parse_cell(blob, length, opts);
147217
o[1].v = blob.read_shift(8, 'f');
148218
return o;
149219
}
220+
function write_NUMBER(R, C, v) {
221+
var o = new_buf(13);
222+
o.write_shift(1, 0xFF);
223+
o.write_shift(2, C);
224+
o.write_shift(2, R);
225+
o.write_shift(8, v, 'f');
226+
return o;
227+
}
150228

151229
function parse_FORMULA(blob, length, opts) {
152230
var tgt = blob.l + length;
@@ -178,15 +256,16 @@ var WK_ = (function() {
178256
var o = parse_cell_3(blob, length);
179257
o[1].v = blob.read_shift(2);
180258
var v = o[1].v >> 1;
181-
/* TODO: figure out all of the corner cases */
182259
if(o[1].v & 0x1) {
183260
switch(v & 0x07) {
261+
case 0: v = (v >> 3) * 5000; break;
184262
case 1: v = (v >> 3) * 500; break;
185263
case 2: v = (v >> 3) / 20; break;
264+
case 3: v = (v >> 3) / 200; break;
186265
case 4: v = (v >> 3) / 2000; break;
266+
case 5: v = (v >> 3) / 20000; break;
187267
case 6: v = (v >> 3) / 16; break;
188268
case 7: v = (v >> 3) / 64; break;
189-
default: throw "unknown NUMBER_18 encoding " + (v & 0x07);
190269
}
191270
}
192271
o[1].v = v;
@@ -198,9 +277,14 @@ var WK_ = (function() {
198277
var v1 = blob.read_shift(4);
199278
var v2 = blob.read_shift(4);
200279
var e = blob.read_shift(2);
201-
if(e == 0xFFFF) { o[1].v = 0; return o; }
280+
if(e == 0xFFFF) {
281+
if(v1 === 0 && v2 === 0xC0000000) { o[1].t = "e"; o[1].v = 0x0F; } // ERR -> #VALUE!
282+
else if(v1 === 0 && v2 === 0xD0000000) { o[1].t = "e"; o[1].v = 0x2A; } // NA -> #N/A
283+
else o[1].v = 0;
284+
return o;
285+
}
202286
var s = e & 0x8000; e = (e&0x7FFF) - 16446;
203-
o[1].v = (s*2 - 1) * ((e > 0 ? (v2 << e) : (v2 >>> -e)) + (e > -32 ? (v1 << (e + 32)) : (v1 >>> -(e + 32))));
287+
o[1].v = (1 - s*2) * ( /*(e > 0 ? (v2 << e) : (v2 >>> -e))*/ v2 * Math.pow(2, e+32) + v1 * Math.pow(2, e));
204288
return o;
205289
}
206290

@@ -288,45 +372,138 @@ var WK_ = (function() {
288372
/*::[*/0x0048/*::]*/: { n:"ACOMM" },
289373
/*::[*/0x0049/*::]*/: { n:"AMACRO" },
290374
/*::[*/0x004A/*::]*/: { n:"PARSE" },
291-
/*::[*/0x00FF/*::]*/: { n:"", f:parsenoop }
375+
/*::[*/0x0066/*::]*/: { n:"PRANGES??" },
376+
/*::[*/0x0067/*::]*/: { n:"RRANGES??" },
377+
/*::[*/0x0068/*::]*/: { n:"FNAME??" },
378+
/*::[*/0x0069/*::]*/: { n:"MRANGES??" },
379+
/*::[*/0xFFFF/*::]*/: { n:"" }
292380
};
293381

294382
var WK3Enum = {
295383
/*::[*/0x0000/*::]*/: { n:"BOF" },
296384
/*::[*/0x0001/*::]*/: { n:"EOF" },
297-
/*::[*/0x0003/*::]*/: { n:"??" },
298-
/*::[*/0x0004/*::]*/: { n:"??" },
299-
/*::[*/0x0005/*::]*/: { n:"??" },
300-
/*::[*/0x0006/*::]*/: { n:"??" },
301-
/*::[*/0x0007/*::]*/: { n:"??" },
302-
/*::[*/0x0009/*::]*/: { n:"??" },
303-
/*::[*/0x000a/*::]*/: { n:"??" },
304-
/*::[*/0x000b/*::]*/: { n:"??" },
305-
/*::[*/0x000c/*::]*/: { n:"??" },
306-
/*::[*/0x000e/*::]*/: { n:"??" },
307-
/*::[*/0x000f/*::]*/: { n:"??" },
308-
/*::[*/0x0010/*::]*/: { n:"??" },
309-
/*::[*/0x0011/*::]*/: { n:"??" },
310-
/*::[*/0x0012/*::]*/: { n:"??" },
385+
/*::[*/0x0002/*::]*/: { n:"PASSWORD" },
386+
/*::[*/0x0003/*::]*/: { n:"CALCSET" },
387+
/*::[*/0x0004/*::]*/: { n:"WINDOWSET" },
388+
/*::[*/0x0005/*::]*/: { n:"SHEETCELLPTR" },
389+
/*::[*/0x0006/*::]*/: { n:"SHEETLAYOUT" },
390+
/*::[*/0x0007/*::]*/: { n:"COLUMNWIDTH" },
391+
/*::[*/0x0008/*::]*/: { n:"HIDDENCOLUMN" },
392+
/*::[*/0x0009/*::]*/: { n:"USERRANGE" },
393+
/*::[*/0x000A/*::]*/: { n:"SYSTEMRANGE" },
394+
/*::[*/0x000B/*::]*/: { n:"ZEROFORCE" },
395+
/*::[*/0x000C/*::]*/: { n:"SORTKEYDIR" },
396+
/*::[*/0x000D/*::]*/: { n:"FILESEAL" },
397+
/*::[*/0x000E/*::]*/: { n:"DATAFILLNUMS" },
398+
/*::[*/0x000F/*::]*/: { n:"PRINTMAIN" },
399+
/*::[*/0x0010/*::]*/: { n:"PRINTSTRING" },
400+
/*::[*/0x0011/*::]*/: { n:"GRAPHMAIN" },
401+
/*::[*/0x0012/*::]*/: { n:"GRAPHSTRING" },
311402
/*::[*/0x0013/*::]*/: { n:"??" },
312-
/*::[*/0x0015/*::]*/: { n:"??" },
403+
/*::[*/0x0014/*::]*/: { n:"ERRCELL" },
404+
/*::[*/0x0015/*::]*/: { n:"NACELL" },
313405
/*::[*/0x0016/*::]*/: { n:"LABEL16", f:parse_LABEL_16},
314406
/*::[*/0x0017/*::]*/: { n:"NUMBER17", f:parse_NUMBER_17 },
315407
/*::[*/0x0018/*::]*/: { n:"NUMBER18", f:parse_NUMBER_18 },
316408
/*::[*/0x0019/*::]*/: { n:"FORMULA19", f:parse_FORMULA_19},
317-
/*::[*/0x001a/*::]*/: { n:"??" },
318-
/*::[*/0x001b/*::]*/: { n:"??" },
319-
/*::[*/0x001c/*::]*/: { n:"??" },
320-
/*::[*/0x001d/*::]*/: { n:"??" },
321-
/*::[*/0x001e/*::]*/: { n:"??" },
322-
/*::[*/0x001f/*::]*/: { n:"??" },
323-
/*::[*/0x0021/*::]*/: { n:"??" },
409+
/*::[*/0x001A/*::]*/: { n:"FORMULA1A" },
410+
/*::[*/0x001B/*::]*/: { n:"XFORMAT" },
411+
/*::[*/0x001C/*::]*/: { n:"DTLABELMISC" },
412+
/*::[*/0x001D/*::]*/: { n:"DTLABELCELL" },
413+
/*::[*/0x001E/*::]*/: { n:"GRAPHWINDOW" },
414+
/*::[*/0x001F/*::]*/: { n:"CPA" },
415+
/*::[*/0x0020/*::]*/: { n:"LPLAUTO" },
416+
/*::[*/0x0021/*::]*/: { n:"QUERY" },
417+
/*::[*/0x0022/*::]*/: { n:"HIDDENSHEET" },
418+
/*::[*/0x0023/*::]*/: { n:"??" },
324419
/*::[*/0x0025/*::]*/: { n:"NUMBER25", f:parse_NUMBER_25 },
420+
/*::[*/0x0026/*::]*/: { n:"??" },
325421
/*::[*/0x0027/*::]*/: { n:"NUMBER27", f:parse_NUMBER_27 },
326422
/*::[*/0x0028/*::]*/: { n:"FORMULA28", f:parse_FORMULA_28 },
327-
/*::[*/0x00FF/*::]*/: { n:"", f:parsenoop }
423+
/*::[*/0x008E/*::]*/: { n:"??" },
424+
/*::[*/0x0093/*::]*/: { n:"??" },
425+
/*::[*/0x0096/*::]*/: { n:"??" },
426+
/*::[*/0x0097/*::]*/: { n:"??" },
427+
/*::[*/0x0098/*::]*/: { n:"??" },
428+
/*::[*/0x0099/*::]*/: { n:"??" },
429+
/*::[*/0x009A/*::]*/: { n:"??" },
430+
/*::[*/0x009B/*::]*/: { n:"??" },
431+
/*::[*/0x009C/*::]*/: { n:"??" },
432+
/*::[*/0x00A3/*::]*/: { n:"??" },
433+
/*::[*/0x00AE/*::]*/: { n:"??" },
434+
/*::[*/0x00AF/*::]*/: { n:"??" },
435+
/*::[*/0x00B0/*::]*/: { n:"??" },
436+
/*::[*/0x00B1/*::]*/: { n:"??" },
437+
/*::[*/0x00B8/*::]*/: { n:"??" },
438+
/*::[*/0x00B9/*::]*/: { n:"??" },
439+
/*::[*/0x00BA/*::]*/: { n:"??" },
440+
/*::[*/0x00BB/*::]*/: { n:"??" },
441+
/*::[*/0x00BC/*::]*/: { n:"??" },
442+
/*::[*/0x00C3/*::]*/: { n:"??" },
443+
/*::[*/0x00C9/*::]*/: { n:"??" },
444+
/*::[*/0x00CD/*::]*/: { n:"??" },
445+
/*::[*/0x00CE/*::]*/: { n:"??" },
446+
/*::[*/0x00CF/*::]*/: { n:"??" },
447+
/*::[*/0x00D0/*::]*/: { n:"??" },
448+
/*::[*/0x0100/*::]*/: { n:"??" },
449+
/*::[*/0x0103/*::]*/: { n:"??" },
450+
/*::[*/0x0104/*::]*/: { n:"??" },
451+
/*::[*/0x0105/*::]*/: { n:"??" },
452+
/*::[*/0x0106/*::]*/: { n:"??" },
453+
/*::[*/0x0107/*::]*/: { n:"??" },
454+
/*::[*/0x0109/*::]*/: { n:"??" },
455+
/*::[*/0x010A/*::]*/: { n:"??" },
456+
/*::[*/0x010B/*::]*/: { n:"??" },
457+
/*::[*/0x010C/*::]*/: { n:"??" },
458+
/*::[*/0x010E/*::]*/: { n:"??" },
459+
/*::[*/0x010F/*::]*/: { n:"??" },
460+
/*::[*/0x0180/*::]*/: { n:"??" },
461+
/*::[*/0x0185/*::]*/: { n:"??" },
462+
/*::[*/0x0186/*::]*/: { n:"??" },
463+
/*::[*/0x0189/*::]*/: { n:"??" },
464+
/*::[*/0x018C/*::]*/: { n:"??" },
465+
/*::[*/0x0200/*::]*/: { n:"??" },
466+
/*::[*/0x0202/*::]*/: { n:"??" },
467+
/*::[*/0x0201/*::]*/: { n:"??" },
468+
/*::[*/0x0204/*::]*/: { n:"??" },
469+
/*::[*/0x0205/*::]*/: { n:"??" },
470+
/*::[*/0x0280/*::]*/: { n:"??" },
471+
/*::[*/0x0281/*::]*/: { n:"??" },
472+
/*::[*/0x0282/*::]*/: { n:"??" },
473+
/*::[*/0x0283/*::]*/: { n:"??" },
474+
/*::[*/0x0284/*::]*/: { n:"??" },
475+
/*::[*/0x0285/*::]*/: { n:"??" },
476+
/*::[*/0x0286/*::]*/: { n:"??" },
477+
/*::[*/0x0287/*::]*/: { n:"??" },
478+
/*::[*/0x0288/*::]*/: { n:"??" },
479+
/*::[*/0x0292/*::]*/: { n:"??" },
480+
/*::[*/0x0293/*::]*/: { n:"??" },
481+
/*::[*/0x0294/*::]*/: { n:"??" },
482+
/*::[*/0x0295/*::]*/: { n:"??" },
483+
/*::[*/0x0296/*::]*/: { n:"??" },
484+
/*::[*/0x0299/*::]*/: { n:"??" },
485+
/*::[*/0x029A/*::]*/: { n:"??" },
486+
/*::[*/0x0300/*::]*/: { n:"??" },
487+
/*::[*/0x0304/*::]*/: { n:"??" },
488+
/*::[*/0x0640/*::]*/: { n:"??" },
489+
/*::[*/0x0642/*::]*/: { n:"??" },
490+
/*::[*/0x0701/*::]*/: { n:"??" },
491+
/*::[*/0x0702/*::]*/: { n:"??" },
492+
/*::[*/0x0703/*::]*/: { n:"??" },
493+
/*::[*/0x0704/*::]*/: { n:"??" },
494+
/*::[*/0x0780/*::]*/: { n:"??" },
495+
/*::[*/0x0800/*::]*/: { n:"??" },
496+
/*::[*/0x0801/*::]*/: { n:"??" },
497+
/*::[*/0x0804/*::]*/: { n:"??" },
498+
/*::[*/0x0A80/*::]*/: { n:"??" },
499+
/*::[*/0x2AF6/*::]*/: { n:"??" },
500+
/*::[*/0x3231/*::]*/: { n:"??" },
501+
/*::[*/0x6E49/*::]*/: { n:"??" },
502+
/*::[*/0x6F44/*::]*/: { n:"??" },
503+
/*::[*/0xFFFF/*::]*/: { n:"" }
328504
};
329505
return {
506+
sheet_to_wk1: sheet_to_wk1,
330507
to_workbook: lotus_to_workbook
331508
};
332509
})();

0 commit comments

Comments
 (0)