From 6e260c91858924dc6e01f3ff0d15019df9ffe973 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 13 Jun 2023 00:49:18 -0400 Subject: [PATCH] cell comments - `sheet_to_json` handle arrays (h/t AaronWoodrow) - XLS parse comment visibility (h/t Godrules500) - chrome manifest v3 writeFile support - raw v8 (no TextEncoder/TextDecoder) optimization - handle empty shared formula body (h/t florian) --- bits/04_base64.js | 20 +++ bits/19_fsutils.js | 6 +- bits/20_jsutils.js | 14 ++- bits/39_xlsbiff.js | 19 ++- bits/40_harb.js | 27 +++- bits/55_vml.js | 41 ++++++- bits/57_cmntxml.js | 1 + bits/58_cmntbin.js | 10 +- bits/67_wsxml.js | 38 ++++-- bits/68_wsbin.js | 6 +- bits/75_xlml.js | 10 +- bits/76_xls.js | 10 +- bits/77_parsetab.js | 46 +++---- bits/78_writebiff.js | 278 +++++++++++++++++++++++++++++++++++++++++- bits/80_parseods.js | 17 ++- bits/81_writeods.js | 19 ++- bits/83_numbers.js | 57 ++++++++- bits/85_parsezip.js | 8 ++ bits/86_writezip.js | 21 ++++ bits/90_utils.js | 2 +- modules/04_base64.js | 20 +++ modules/04_base64.ts | 19 +++ modules/83_numbers.js | 57 ++++++++- modules/83_numbers.ts | 57 ++++++++- 24 files changed, 722 insertions(+), 81 deletions(-) diff --git a/bits/04_base64.js b/bits/04_base64.js index 637bee2..459523d 100644 --- a/bits/04_base64.js +++ b/bits/04_base64.js @@ -45,6 +45,26 @@ function Base64_encode_pass(input) { } return o; } +function Base64_encode_arr(input) { + var o = ""; + var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0; + for (var i = 0; i < input.length; ) { + c1 = input[i++]; + e1 = c1 >> 2; + c2 = input[i++]; + e2 = (c1 & 3) << 4 | c2 >> 4; + c3 = input[i++]; + e3 = (c2 & 15) << 2 | c3 >> 6; + e4 = c3 & 63; + if (isNaN(c2)) { + e3 = e4 = 64; + } else if (isNaN(c3)) { + e4 = 64; + } + o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4); + } + return o; +} function Base64_decode(input) { var o = ""; var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0; diff --git a/bits/19_fsutils.js b/bits/19_fsutils.js index 378f67a..1047e67 100644 --- a/bits/19_fsutils.js +++ b/bits/19_fsutils.js @@ -35,7 +35,7 @@ function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) { /*:: declare var chrome: any; */ if(typeof chrome === 'object' && typeof (chrome.downloads||{}).download == "function") { if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); - return chrome.downloads.download({ url: url, filename: fname, saveAs: true}); + return chrome.downloads.download({ url: url, filename: fname, saveAs: true }); } var a = document.createElement("a"); if(a.download != null) { @@ -45,6 +45,10 @@ function write_dl(fname/*:string*/, payload/*:any*/, enc/*:?string*/) { if(URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function() { URL.revokeObjectURL(url); }, 60000); return url; } + } else if(typeof URL !== 'undefined' && !URL.createObjectURL && typeof chrome === 'object') { + /* manifest v3 extensions -- no URL.createObjectURL */ + var b64 = "data:application/octet-stream;base64," + Base64_encode_arr(new Uint8Array(blobify(data))); + return chrome.downloads.download({ url: b64, filename: fname, saveAs: true }); } } // $FlowIgnore diff --git a/bits/20_jsutils.js b/bits/20_jsutils.js index 786e38b..1dd375a 100644 --- a/bits/20_jsutils.js +++ b/bits/20_jsutils.js @@ -129,8 +129,18 @@ function cc2str(arr/*:Array*/, debomit)/*:string*/ { return new TextDecoder("latin1").decode(arr).replace(/[€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ]/g, function(c) { return rev[c] || c; }); } catch(e) {} - var o = []; - for(var i = 0; i != arr.length; ++i) o.push(String.fromCharCode(arr[i])); + var o = [], i = 0; + // this cascade is for the browsers and runtimes of antiquity (and for modern runtimes that lack TextEncoder) + try { + for(i = 0; i < arr.length - 65536; i+=65536) o.push(String.fromCharCode.apply(0, arr.slice(i, i + 65536))); + o.push(String.fromCharCode.apply(0, arr.slice(i))); + } catch(e) { try { + for(; i < arr.length - 16384; i+=16384) o.push(String.fromCharCode.apply(0, arr.slice(i, i + 16384))); + o.push(String.fromCharCode.apply(0, arr.slice(i))); + } catch(e) { + for(; i != arr.length; ++i) o.push(String.fromCharCode(arr[i])); + } + } return o.join(""); } diff --git a/bits/39_xlsbiff.js b/bits/39_xlsbiff.js index e1a254d..8c7794a 100644 --- a/bits/39_xlsbiff.js +++ b/bits/39_xlsbiff.js @@ -752,21 +752,34 @@ function parse_MTRSettings(blob) { return [fMTREnabled, fUserSetThreadCount, cUserThreadCount]; } -/* [MS-XLS] 2.5.186 TODO: BIFF5 */ +/* [MS-XLS] 2.5.186 */ function parse_NoteSh(blob, length, opts) { - if(opts.biff < 8) return; var row = blob.read_shift(2), col = blob.read_shift(2); var flags = blob.read_shift(2), idObj = blob.read_shift(2); var stAuthor = parse_XLUnicodeString2(blob, 0, opts); - if(opts.biff < 8) blob.read_shift(1); return [{r:row,c:col}, stAuthor, idObj, flags]; } /* [MS-XLS] 2.4.179 */ function parse_Note(blob, length, opts) { + if(opts && (opts.biff < 8)) { + var row = blob.read_shift(2), col = blob.read_shift(2); + if(row == 0xFFFF || row == -1) return; // TODO: test continuation + var cch = blob.read_shift(2); + var cmnt = blob.read_shift(Math.min(cch,2048), 'cpstr'); + return [{r:row, c:col}, cmnt]; + } /* TODO: Support revisions */ return parse_NoteSh(blob, length, opts); } +function write_NOTE_BIFF2(text/*:string*/, R/*:number*/, C/*:number*/, len/*?:number*/) { + var o = new_buf(6 + (len || text.length)); + o.write_shift(2, R); + o.write_shift(2, C); + o.write_shift(2, len || text.length); + o.write_shift(text.length, text, "sbcs"); + return o; +} /* [MS-XLS] 2.4.168 */ function parse_MergeCells(blob, length)/*:Array*/ { diff --git a/bits/40_harb.js b/bits/40_harb.js index 1ceb55b..ec30fa8 100644 --- a/bits/40_harb.js +++ b/bits/40_harb.js @@ -405,6 +405,8 @@ var SYLK = /*#__PURE__*/(function() { var sylk_char_fn = function(_, $1){ var o = sylk_escapes[$1]; return typeof o == "number" ? _getansi(o) : o; }; var decode_sylk_char = function($$, $1, $2) { var newcc = (($1.charCodeAt(0) - 0x20)<<4) | ($2.charCodeAt(0) - 0x30); return newcc == 59 ? $$ : _getansi(newcc); }; sylk_escapes["|"] = 254; + /* TODO: evert the escape map */ + var encode_sylk_str = function($$) { return $$.replace(/\n/g, "\x1b :").replace(/\r/g, "\x1b ="); }; /* https://oss.sheetjs.com/notes/sylk/ for more details */ function sylk_to_aoa(d/*:RawData*/, opts)/*:[AOA, Worksheet]*/ { switch(opts.type) { @@ -457,8 +459,9 @@ var SYLK = /*#__PURE__*/(function() { // case 'NU': // ?? case 'C': /* cell */ var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z"; + var cmnt = ""; for(rj=1; rj]*)?>([\s\S]*?)<\/(?:\w+:)?shape>/g; +function parse_vml(data/*:string*/, sheet, comments) { + var cidx = 0; + (data.match(shapevmlregex)||[]).forEach(function(m) { + var type = ""; + var hidden = true; + var R = -1, C = -1; + m.replace(tagregex, function(x/*:string*/, idx/*:number*/) { + var y = parsexmltag(x); + switch(strip_ns(y[0])) { + case '': hidden = false; break; + + case '': aidx = idx + x.length; break; + case '': R = +m.slice(aidx, idx).trim(); break; + + case '': aidx = idx + x.length; break; + case '': C = +m.slice(aidx, idx).trim(); break; + } + return ""; + }); + switch(type) { + case 'Note': + var cell = ws_get_cell_stub(sheet, ((R>=0 && C>=0) ? encode_cell({r:R,c:C}) : comments[cidx].ref)); + if(cell.c) { + cell.c.hidden = hidden; + } + ++cidx; + break; + } + + }); +} + + +/* comment boxes */ +function write_vml(rId/*:number*/, comments, ws) { var csize = [21600, 21600]; /* L.5.2.1.2 Path Attribute */ var bbox = ["m0,0l0",csize[1],csize[0],csize[1],csize[0],"0xe"].join(","); @@ -21,7 +58,7 @@ function write_vml(rId/*:number*/, comments) { return o.join(""); } -function write_vml_comment(x, _shapeid)/*:string*/ { +function write_vml_comment(x, _shapeid, ws)/*:string*/ { var c = decode_cell(x[0]); var fillopts = /*::(*/{'color2':"#BEFF82", 'type':"gradient"}/*:: :any)*/; if(fillopts.type == "gradient") fillopts.angle = "-180"; diff --git a/bits/57_cmntxml.js b/bits/57_cmntxml.js index 45c3bcf..132a827 100644 --- a/bits/57_cmntxml.js +++ b/bits/57_cmntxml.js @@ -64,6 +64,7 @@ function write_comments_xml(data/*::, opts*/) { o.push(''); }); } else { + if(d[1][0] && d[1][0].T && d[1][0].ID) lastauthor = iauthor.indexOf("tc=" + d[1][0].ID); /* based on Threaded Comments -> Comments projection */ o.push(''); var t = "Comment:\n " + (ts[0]) + "\n"; diff --git a/bits/58_cmntbin.js b/bits/58_cmntbin.js index 9b28af2..1c60d4a 100644 --- a/bits/58_cmntbin.js +++ b/bits/58_cmntbin.js @@ -74,6 +74,10 @@ function write_comments_bin(data/*::, opts*/) { if(iauthor.indexOf(c.a) > -1) return; iauthor.push(c.a.slice(0,54)); write_record(ba, 0x0278 /* BrtCommentAuthor */, write_BrtCommentAuthor(c.a)); + if(c.T && c.ID && iauthor.indexOf("tc=" + c.ID) == -1) { + iauthor.push("tc=" + c.ID); + write_record(ba, 0x0278 /* BrtCommentAuthor */, write_BrtCommentAuthor("tc=" + c.ID)); + } }); }); write_record(ba, 0x0277 /* BrtEndCommentAuthors */); @@ -81,7 +85,11 @@ function write_comments_bin(data/*::, opts*/) { write_record(ba, 0x0279 /* BrtBeginCommentList */); data.forEach(function(comment) { comment[1].forEach(function(c) { - c.iauthor = iauthor.indexOf(c.a); + var _ia = -1; + if(c.ID) _ia = iauthor.indexOf("tc=" + c.ID); + if(_ia == -1 && comment[1][0].T && comment[1][0].ID) _ia = iauthor.indexOf("tc=" + comment[1][0].ID); + if(_ia == -1) _ia = iauthor.indexOf(c.a); + c.iauthor = _ia; var range = {s:decode_cell(comment[0]),e:decode_cell(comment[0])}; write_record(ba, 0x027B /* BrtBeginComment */, write_BrtBeginComment([range, c])); if(c.t && c.t.length > 0) write_record(ba, 0x027D /* BrtCommentText */, write_BrtCommentText(c)); diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index b5385fa..956d9e2 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -75,6 +75,9 @@ function parse_ws_xml(data/*:?string*/, opts, idx/*:number*/, rels, wb/*:WBWBPro var margins = data2.match(marginregex); if(margins) s['!margins'] = parse_ws_xml_margins(parsexmltag(margins[0])); + /* legacyDrawing */ + if((m = data2.match(/legacyDrawing r:id="(.*?)"/))) s['!legrel'] = m[1]; + if(opts && opts.nodim) refguess.s.c = refguess.s.r = 0; if(!s["!ref"] && refguess.e.c >= refguess.s.c && refguess.e.r >= refguess.s.r) s["!ref"] = encode_range(refguess); if(opts.sheetRows > 0 && s["!ref"]) { @@ -91,6 +94,7 @@ function parse_ws_xml(data/*:?string*/, opts, idx/*:number*/, rels, wb/*:WBWBPro } if(columns.length > 0) s["!cols"] = columns; if(merges.length > 0) s["!merges"] = merges; + if(rels['!id'][s['!legrel']]) s['!legdrawel'] = rels['!id'][s['!legrel']]; return s; } @@ -395,19 +399,27 @@ return function parse_ws_xml_data(sdata/*:string*/, s, opts, guess/*:Range*/, th if((cref=d.match(match_v))!= null && /*::cref != null && */cref[1] !== '') p.v=unescapexml(cref[1]); if(opts.cellFormula) { - if((cref=d.match(match_f))!= null && /*::cref != null && */cref[1] !== '') { - /* TODO: match against XLSXFutureFunctions */ - p.f=unescapexml(utf8read(cref[1]), true); - if(!opts.xlfn) p.f = _xlfn(p.f); - if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="array"') > -1) { - p.F = (d.match(refregex)||[])[1]; - if(p.F.indexOf(":") > -1) arrayf.push([safe_decode_range(p.F), p.F]); - } else if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="shared"') > -1) { - // TODO: parse formula - ftag = parsexmltag(cref[0]); - var ___f = unescapexml(utf8read(cref[1])); - if(!opts.xlfn) ___f = _xlfn(___f); - sharedf[parseInt(ftag.si, 10)] = [ftag, ___f, tag.r]; + if((cref=d.match(match_f))!= null /*:: && cref != null*/) { + if(cref[1] == "") { + if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="shared"') > -1) { + // TODO: parse formula + ftag = parsexmltag(cref[0]); + if(sharedf[ftag.si]) p.f = shift_formula_xlsx(sharedf[ftag.si][1], sharedf[ftag.si][2]/*[0].ref*/, tag.r); + } + } else { + /* TODO: match against XLSXFutureFunctions */ + p.f=unescapexml(utf8read(cref[1]), true); + if(!opts.xlfn) p.f = _xlfn(p.f); + if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="array"') > -1) { + p.F = (d.match(refregex)||[])[1]; + if(p.F.indexOf(":") > -1) arrayf.push([safe_decode_range(p.F), p.F]); + } else if(/*::cref != null && cref[0] != null && */cref[0].indexOf('t="shared"') > -1) { + // TODO: parse formula + ftag = parsexmltag(cref[0]); + var ___f = unescapexml(utf8read(cref[1])); + if(!opts.xlfn) ___f = _xlfn(___f); + sharedf[parseInt(ftag.si, 10)] = [ftag, ___f, tag.r]; + } } } else if((cref=d.match(/]*\/>/))) { ftag = parsexmltag(cref[0]); diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index 5dae457..9bafc81 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -686,6 +686,10 @@ function parse_ws_bin(data, _opts, idx, rels, wb/*:WBWBProps*/, themes, styles)/ } break; + case 0x0227: /* 'BrtLegacyDrawing' */ + if(val) s["!legrel"] = val; + break; + case 0x00A1: /* 'BrtBeginAFilter' */ s['!autofilter'] = { ref:encode_range(val) }; break; @@ -742,7 +746,6 @@ function parse_ws_bin(data, _opts, idx, rels, wb/*:WBWBProps*/, themes, styles)/ case 0x0499: /* 'BrtFilter14' */ case 0x00A9: /* 'BrtIconFilter' */ case 0x049D: /* 'BrtIconFilter14' */ - case 0x0227: /* 'BrtLegacyDrawing' */ case 0x0228: /* 'BrtLegacyDrawingHF' */ case 0x0295: /* 'BrtListPart' */ case 0x027F: /* 'BrtOleObject' */ @@ -801,6 +804,7 @@ function parse_ws_bin(data, _opts, idx, rels, wb/*:WBWBProps*/, themes, styles)/ if(merges.length > 0) s["!merges"] = merges; if(colinfo.length > 0) s["!cols"] = colinfo; if(rowinfo.length > 0) s["!rows"] = rowinfo; + if(rels['!id'][s['!legrel']]) s['!legdrawel'] = rels['!id'][s['!legrel']]; return s; } diff --git a/bits/75_xlml.js b/bits/75_xlml.js index eff56cc..b353c52 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -443,6 +443,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { } else { state.push([Rn[3], false]); tmp = xlml_parsexmltag(Rn[0]); + if(!parsexmlbool(tmp["ShowAlways"]||"0")) comments.hidden = true; comment = ({a:tmp.Author}/*:any*/); } break; @@ -534,6 +535,9 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { /*:: if(!Workbook.WBProps) Workbook.WBProps = {}; */ Workbook.WBProps.date1904 = true; break; + case 'hidehorizontalscrollbar' /*case 'HideHorizontalScrollBar'*/: break; + case 'hideverticalscrollbar' /*case 'HideVerticalScrollBar'*/: break; + case 'hideworkbooktabs' /*case 'HideWorkbookTabs'*/: break; case 'windowheight' /*case 'WindowHeight'*/: break; case 'windowwidth' /*case 'WindowWidth'*/: break; case 'windowtopx' /*case 'WindowTopX'*/: break; @@ -1102,11 +1106,15 @@ function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workb return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x}); } function write_ws_xlml_comment(comments/*:Array*/)/*:string*/ { + /* TODO: test multiple comments */ return comments.map(function(c) { // TODO: formatted text var t = xlml_unfixstr(c.t||""); var d =writextag("ss:Data", t, {"xmlns":"http://www.w3.org/TR/REC-html40"}); - return writextag("Comment", d, {"ss:Author":c.a}); + var p = {}; + if(c.a) p["ss:Author"] = c.a; + if(!comments.hidden) p["ss:ShowAlways"] = "1"; + return writextag("Comment", d, p); }).join(""); } function write_ws_xlml_cell(cell, ref/*:string*/, ws, opts, idx/*:number*/, wb, addr)/*:string*/{ diff --git a/bits/76_xls.js b/bits/76_xls.js index 488653f..adf01b6 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -520,9 +520,8 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { } } break; case 0x001c /* Note */: { - if(opts.biff <= 5 && opts.biff >= 2) break; /* TODO: BIFF5 */ + /* TODO: comment continuation (row == -1 / 0xFFFF) */ cc = options.dense ? (out["!data"][val[0].r]||[])[val[0].c] : out[encode_cell(val[0])]; - var noteobj = objects[val[2]]; if(!cc) { if(options.dense) { if(!out["!data"][val[0].r]) out["!data"][val[0].r] = []; @@ -536,7 +535,12 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { range.s.c = Math.min(range.s.c, val[0].c); } if(!cc.c) cc.c = []; - cmnt = {a:val[1],t:noteobj.TxO.t}; + if(opts.biff <= 5 && opts.biff >= 2) cmnt = {a:"SheetJ5", t:val[1]}; + else { + var noteobj = objects[val[2]]; + cmnt = {a:val[1],t:noteobj.TxO.t}; + if(val[3] != null && !(val[3] & 0x02)) cc.c.hidden = true; + } cc.c.push(cmnt); } break; case 0x087d /* XFExt */: update_xfext(XFs[val.ixfe], val.ext); break; diff --git a/bits/77_parsetab.js b/bits/77_parsetab.js index 38232ce..0f9a577 100644 --- a/bits/77_parsetab.js +++ b/bits/77_parsetab.js @@ -482,7 +482,7 @@ var XLSBRecordEnum = { /*::[*/0x0224/*::]*/: { /* n:"BrtFileSharing" */ }, /*::[*/0x0225/*::]*/: { /* n:"BrtOleSize" */ }, /*::[*/0x0226/*::]*/: { /* n:"BrtDrawing", */ f:parse_RelID }, - /*::[*/0x0227/*::]*/: { /* n:"BrtLegacyDrawing" */ }, + /*::[*/0x0227/*::]*/: { /* n:"BrtLegacyDrawing", */ f:parse_XLNullableWideString }, /*::[*/0x0228/*::]*/: { /* n:"BrtLegacyDrawingHF" */ }, /*::[*/0x0229/*::]*/: { /* n:"BrtWebOpt" */ }, /*::[*/0x022A/*::]*/: { /* n:"BrtBeginWebPubItems", */ T:1 }, @@ -1233,30 +1233,30 @@ var XLSRecordEnum = { /*::[*/0x0021/*::]*/: { /* n:"Array", */ f:parse_Array }, /*::[*/0x0024/*::]*/: { /* n:"COLWIDTH", */ }, /*::[*/0x0025/*::]*/: { /* n:"DefaultRowHeight", */ f:parse_DefaultRowHeight }, - // 0x2c ?? - // 0x2d ?? - // 0x2e ?? - // 0x30 FONTCOUNT: number of fonts + // 0x002c ?? + // 0x002d ?? + // 0x002e ?? + // 0x0030 FONTCOUNT: number of fonts /*::[*/0x0032/*::]*/: { /* n:"BIFF2FONTXTRA", */ f:parse_BIFF2FONTXTRA }, - // 0x35: INFOOPTS - // 0x36: TABLE (BIFF2 only) - // 0x37: TABLE2 (BIFF2 only) - // 0x38: WNDESK - // 0x39 ?? - // 0x3a: BEGINPREF - // 0x3b: ENDPREF + // 0x0035: INFOOPTS + // 0x0036: TABLE (BIFF2 only) + // 0x0037: TABLE2 (BIFF2 only) + // 0x0038: WNDESK + // 0x0039 ?? + // 0x003a: BEGINPREF + // 0x003b: ENDPREF /*::[*/0x003e/*::]*/: { /* n:"BIFF2WINDOW2", */ }, - // 0x3f ?? - // 0x46: SHOWSCROLL - // 0x47: SHOWFORMULA - // 0x48: STATUSBAR - // 0x49: SHORTMENUS - // 0x4A: - // 0x4B: - // 0x4C: - // 0x4E: - // 0x4F: - // 0x58: TOOLBAR (BIFF3) + // 0x003f ?? + // 0x0046: SHOWSCROLL + // 0x0047: SHOWFORMULA + // 0x0048: STATUSBAR + // 0x0049: SHORTMENUS + // 0x004A: + // 0x004B: + // 0x004C: + // 0x004E: + // 0x004F: + // 0x0058: TOOLBAR (BIFF3) /* - - - */ /*::[*/0x0034/*::]*/: { /* n:"DDEObjName", */ }, diff --git a/bits/78_writebiff.js b/bits/78_writebiff.js index 0743f39..5ec9418 100644 --- a/bits/78_writebiff.js +++ b/bits/78_writebiff.js @@ -56,6 +56,17 @@ function write_BIFF2LABEL(r/*:number*/, c/*:number*/, val) { return out.l < out.length ? out.slice(0, out.l) : out; } +function write_comments_biff2(ba/*:BufArray*/, comments/*:Array<[Comment[], number, number]>*/) { + comments.forEach(function(data) { + var text = data[0].map(function(cc) { return cc.t; }).join(""); + // TODO: should '\n' be translated to '\r' to correct for Excel 5.0 bug when exporting to BIFF2/3 ? + if(text.length <= 2048) return write_biff_rec(ba, 0x001C, write_NOTE_BIFF2(text, data[1], data[2])); + write_biff_rec(ba, 0x001C, write_NOTE_BIFF2(text.slice(0, 2048), data[1], data[2], text.length)); + for(var i = 2048; i < text.length; i += 2048) + write_biff_rec(ba, 0x001C, write_NOTE_BIFF2(text.slice(i, Math.min(i+2048, text.length)), -1, -1, Math.min(2048, text.length - i))); + }); +} + function write_ws_biff2_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*//*::, opts*/) { if(cell.v != null) switch(cell.t) { case 'd': case 'n': @@ -86,7 +97,7 @@ function write_ws_biff2(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts/ range.e.c = Math.min(range.e.c, 0xFF); range.e.r = Math.min(range.e.c, 0x3FFF); } - var row = []; + var row = [], comments = []; for(var C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C); for(var R = range.s.r; R <= range.e.r; ++R) { if(dense) row = ws["!data"][R] || []; @@ -96,8 +107,13 @@ function write_ws_biff2(ba/*:BufArray*/, ws/*:Worksheet*/, idx/*:number*/, opts/ if(!cell) continue; /* write cell */ write_ws_biff2_cell(ba, cell, R, C, opts); + if(cell.c) comments.push([cell.c, R, C]); } } + + /* ... 0x12 0x19 0x13 (Password) */ + write_comments_biff2(ba, comments); + /* 0x3d (Window1) ... */ } /* Based on test files */ @@ -115,6 +131,257 @@ function write_biff2_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) { return ba.end(); } +var b8oid = 1, b8ocnts/*:Array<[number, number, number]>*/ = []; +function write_MsoDrawingGroup() { + var buf = new_buf(82 + 8 * b8ocnts.length); + /* [MS-ODRAW] 2.2.12 OfficeArtDggContainer */ + buf.write_shift(2, 0x0F); + buf.write_shift(2, 0xF000); + buf.write_shift(4, 74 + 8 * b8ocnts.length); + /* 2.2.48 OfficeArtFDGGBlock */ + { + buf.write_shift(2, 0); + buf.write_shift(2, 0xF006); + buf.write_shift(4, 16 + 8 * b8ocnts.length); + /* 2.2.47 OfficeArtFDGG */ + { + buf.write_shift(4, b8oid); + buf.write_shift(4, b8ocnts.length+1); + buf.write_shift(4, b8ocnts.reduce(function(acc,x) { return acc+x[1]; }, 0)); + buf.write_shift(4, b8ocnts.length); + } + /* 2.2.46 OfficeArtIDCL + */ + b8ocnts.forEach(function(b8) { + buf.write_shift(4, b8[0]); + buf.write_shift(4, b8[2]); + }); + } + /* 2.2.9 OfficeArtFOPT */ + { + buf.write_shift(2, 0x33); // 0x03 | (3 << 4) + buf.write_shift(2, 0xF00B); + buf.write_shift(4, 0x12); // 3 * 6 + /* 2.3.21.15 Text Boolean Properties */ + buf.write_shift(2, 0xBF); buf.write_shift(4, 0x00080008); + /* 2.3.7.2 fillColor */ + buf.write_shift(2, 0x0181); buf.write_shift(4, 0x08000041); + /* 2.3.8.1 lineColor */ + buf.write_shift(2, 0x01C0); buf.write_shift(4, 0x08000040); + } + /* 2.2.45 OfficeArtSplitMenuColorContainer */ + { + buf.write_shift(2, 0x40); + buf.write_shift(2, 0xF11E); + buf.write_shift(4, 16); + buf.write_shift(4, 0x0800000D); + buf.write_shift(4, 0x0800000C); + buf.write_shift(4, 0x08000017); + buf.write_shift(4, 0x100000F7); + } + return buf; +} +function write_comments_biff8(ba/*:BufArray*/, comments/*:Array<[Comment[], number, number]>*/) { + var notes/*:Array*/ = [], sz = 0, pl = buf_array(), baseid = b8oid; + var _oasc; + comments.forEach(function(c, ci) { + var author = ""; + var text = c[0].map(function(t) { if(t.a && !author) author = t.a; return t.t }).join(""); + ++b8oid; + + /* 2.2.14 OfficeArtSpContainer */ + { + var oasc = new_buf(0x96); + oasc.write_shift(2, 0x0F); + oasc.write_shift(2, 0xF004); + oasc.write_shift(4, 0x96); + /* 2.2.40 OfficeArtFSP */ + { + oasc.write_shift(2, 0xca2); // 0x02 | (0xca << 4) + oasc.write_shift(2, 0xF00A); + oasc.write_shift(4, 8); + oasc.write_shift(4, b8oid); + oasc.write_shift(4, 0xA00); + } + /* 2.2.9 OfficeArtFOPT */ + { + oasc.write_shift(2, 0xE3); // 0x03 | (14 << 4) + oasc.write_shift(2, 0xF00B); + oasc.write_shift(4, 0x54); // 14 * 6 + /* 2.3.21.1 ITxid */ + oasc.write_shift(2, 0x80); oasc.write_shift(4, 0); + /* 2.3.21.12 txdir */ + oasc.write_shift(2, 0x8B); oasc.write_shift(4, 0x02); + /* 2.3.21.15 Text Boolean Properties */ + oasc.write_shift(2, 0xBF); oasc.write_shift(4, 0x00080008); + /* 2.3.6.30 cxk */ + oasc.write_shift(2, 0x0158); oasc.l += 4; + /* 2.3.7.2 fillColor */ + oasc.write_shift(2, 0x0181); oasc.write_shift(4, 0x08000050); + /* 2.3.7.4 fillBackColor */ + oasc.write_shift(2, 0x0183); oasc.write_shift(4, 0x08000050); + /* 2.3.7.6 fillCrMod */ + oasc.write_shift(2, 0x0185); oasc.write_shift(4, 0x100000F4); + /* 2.3.7.43 Fill Style Boolean Properties */ + oasc.write_shift(2, 0x01BF); oasc.write_shift(4, 0x00100010); + /* 2.3.8.1 lineColor */ + oasc.write_shift(2, 0x01C0); oasc.write_shift(4, 0x08000051); + /* 2.3.8.4 lineCrMod */ + oasc.write_shift(2, 0x01C3); oasc.write_shift(4, 0x100000F4); + /* 2.3.13.2 shadowColor */ + oasc.write_shift(2, 0x0201); oasc.write_shift(4, 0x08000051); + /* 2.3.13.4 shadowCrMod */ + oasc.write_shift(2, 0x0203); oasc.write_shift(4, 0x100000F4); + /* 2.3.13.23 Shadow Style Boolean Properties */ + oasc.write_shift(2, 0x023F); oasc.write_shift(4, 0x00030001); + /* 2.3.4.44 Group Shape Boolean Properties */ + oasc.write_shift(2, 0x03BF); oasc.write_shift(4, 0x00020000 | (c[0].hidden ? 2 : 0)); + } + /* [MS-XLS] 2.5.193 OfficeArtClientAnchorSheet */ + { + oasc.l += 2; + oasc.write_shift(2, 0xF010); + oasc.write_shift(4, 0x12); + oasc.write_shift(2, 0x3); // do not move or size with cells + oasc.write_shift(2, c[2] + 2); oasc.l += 2; + oasc.write_shift(2, c[1] + 1); oasc.l += 2; + oasc.write_shift(2, c[2] + 4); oasc.l += 2; + oasc.write_shift(2, c[1] + 5); oasc.l += 2; + } + /* [MS-XLS] 2.5.194 OfficeArtClientData */ + { + oasc.l += 2; + oasc.write_shift(2, 0xF011); + oasc.l += 4; + } + oasc.l = 0x96; + if(ci == 0) /* write_biff_rec(pl, 0x003C, oasc); */ _oasc = oasc; + else write_biff_rec(pl, 0x00EC, oasc); + } + sz += 0x96; + + /* [MS-XLS] 2.4.181 Obj */ + { + var obj = new_buf(52); // 22 + 26 + 4 + /* [MS-XLS] 2.5.143 FtCmo */ + obj.write_shift(2, 0x15); + obj.write_shift(2, 0x12); + obj.write_shift(2, 0x19); + obj.write_shift(2, b8oid); + obj.write_shift(2, 0); + obj.l = 22; + /* [MS-XLS] 2.5.149 FtNts */ + obj.write_shift(2, 0x0D); + obj.write_shift(2, 0x16); + obj.write_shift(4, 0x62726272); + obj.write_shift(4, 0x95374305); + obj.write_shift(4, 0x80301328); + obj.write_shift(4, 0x69696904 + b8oid*256); + obj.write_shift(2,0); + obj.write_shift(4,0); + // reserved + obj.l += 4; + write_biff_rec(pl, 0x005D, obj); + } + + /* [MS-XLS] 2.5.195 OfficeArtClientTextbox */ + { + var oact = new_buf(8); + oact.l += 2; + oact.write_shift(2, 0xF00D); + oact.l += 4; + write_biff_rec(pl, 0x00EC, oact); + } + sz += 8; + + /* [MS-XLS] 2.4.329 TxO */ + { + var txo = new_buf(18); + txo.write_shift(2, 0x12); + txo.l += 8; + txo.write_shift(2, text.length); + txo.write_shift(2, 0x10); + txo.l += 4; + write_biff_rec(pl, 0x01b6, txo); + /* text continue record TODO: switch to wide strings */ + { + var cont = new_buf(1 + text.length); + cont.write_shift(1, 0); + cont.write_shift(text.length, text, "sbcs"); + write_biff_rec(pl, 0x003C, cont); + } + /* formatting continue records */ + { + var conf = new_buf(0x10); + conf.l += 8; + conf.write_shift(2, text.length); + conf.l += 6; + write_biff_rec(pl, 0x003C, conf); + } + } + + /* 2.4.179 Note */ + { + var notesh = new_buf(12 + author.length); + notesh.write_shift(2, c[1]); + notesh.write_shift(2, c[2]); + notesh.write_shift(2, 0 | (c[0].hidden ? 0 : 2)); + notesh.write_shift(2, b8oid); + notesh.write_shift(2, author.length); + notesh.write_shift(1, 0); + notesh.write_shift(author.length, author, "sbcs"); + notesh.l ++; + notes.push(notesh); + } + }); + /* [MS-ODRAW] 2.2.13 OfficeArtDgContainer */ + { + var hdr = new_buf(80); + hdr.write_shift(2, 0x0F) + hdr.write_shift(2, 0xF002); + hdr.write_shift(4, sz + hdr.length - 8); + /* [MS-ODRAW] 2.2.49 OfficeArtFDG */ + { + hdr.write_shift(2, 0x10); + hdr.write_shift(2, 0xF008); + hdr.write_shift(4, 0x08); + hdr.write_shift(4, comments.length + 1); + hdr.write_shift(4, b8oid); + } + /* [MS-ODRAW] 2.2.16 OfficeArtSpgrContainer */ + { + hdr.write_shift(2, 0x0f); + hdr.write_shift(2, 0xF003); + hdr.write_shift(4, sz + 0x30); + /* [MS-ODRAW] 2.2.14 OfficeArtSpContainer */ + { + hdr.write_shift(2, 0x0f); + hdr.write_shift(2, 0xF004); + hdr.write_shift(4, 0x28); + /* [MS-ODRAW] 2.2.38 OfficeArtFSPGR */ + { + hdr.write_shift(2, 0x01); + hdr.write_shift(2, 0xF009); + hdr.write_shift(4, 0x10); + hdr.l += 16; + } + /* [MS-ODRAW] 2.2.40 OfficeArtFSP */ + { + hdr.write_shift(2, 0x02); + hdr.write_shift(2, 0xF00A); + hdr.write_shift(4, 0x08); + hdr.write_shift(4, baseid); + hdr.write_shift(4, 0x05); + } + } + } + write_biff_rec(ba, 0x00EC, /* hdr */ bconcat([hdr, _oasc])); + } + ba.push(pl.end()); + notes.forEach(function(n) { write_biff_rec(ba, 0x001C, n); }); + b8ocnts.push([baseid, comments.length + 1, b8oid]); + ++b8oid; +} + function write_FONTS_biff8(ba, data, opts) { write_biff_rec(ba, 0x0031 /* Font */, write_Font({ sz:12, @@ -260,12 +527,12 @@ function write_ws_biff8(idx/*:number*/, opts, wb/*:Workbook*/) { /* write cell */ write_ws_biff8_cell(ba, cell, R, C, opts); if(b8 && cell.l) ws['!links'].push([ref, cell.l]); - if(b8 && cell.c) comments.push([ref, cell.c]); + if(cell.c) comments.push([cell.c, R, C]); } } var cname/*:string*/ = _sheet.CodeName || _sheet.name || s; /* ... */ - // if(b8) comments.forEach(function(comment) { write_biff_rec(ba, 0x001c /* Note */, write_NoteSh(comment)); }); + if(b8) write_comments_biff8(ba, comments); else write_comments_biff2(ba, comments); /* ... */ if(b8) write_biff_rec(ba, 0x023e /* Window2 */, write_Window2((_WB.Views||[])[0])); /* ... */ @@ -335,8 +602,10 @@ function write_biff8_global(wb/*:Workbook*/, bufs, opts/*:WriteOpts*/) { var C = buf_array(); /* METADATA [MTRSettings] [ForceFullCalculation] */ if(b8) write_biff_rec(C, 0x008C /* Country */, write_Country()); - /* *SUPBOOK *LBL *RTD [RecalcId] *HFPicture *MSODRAWINGGROUP */ + /* *SUPBOOK *LBL *RTD [RecalcId] *HFPicture */ + /* BIFF8: MsoDrawingGroup [*Continue] */ + if(b8 && b8ocnts.length) write_biff_rec(C, 0x00EB /* MsoDrawingGroup */, write_MsoDrawingGroup()); /* BIFF8: [SST *Continue] ExtSST */ if(b8 && opts.Strings) write_biff_continue(C, 0x00FC /* SST */, write_SST(opts.Strings, opts)); @@ -379,6 +648,7 @@ function write_biff8_buf(wb/*:Workbook*/, opts/*:WriteOpts*/) { o.ssf = wb.SSF; } + b8oid = 1; b8ocnts = []; o.Strings = /*::((*/[]/*:: :any):SST)*/; o.Strings.Count = 0; o.Strings.Unique = 0; fix_write_opts(o); diff --git a/bits/80_parseods.js b/bits/80_parseods.js index 8b44a69..622006c 100644 --- a/bits/80_parseods.js +++ b/bits/80_parseods.js @@ -248,8 +248,8 @@ function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ { var ws = ({}/*:any*/); if(opts.dense) ws["!data"] = []; var Rn, q/*:: :any = ({t:"", v:null, z:null, w:"",c:[],}:any)*/; var ctag = ({value:""}/*:any*/); - var textp = "", textpidx = 0, textptag/*:: = {}*/; - var textR = []; + var textp = "", textpidx = 0, textptag/*:: = {}*/, oldtextp = "", oldtextpidx = 0; + var textR = [], oldtextR = []; var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}}; var row_ol = 0; var number_format_map = _nfm || {}, styles = {}; @@ -334,7 +334,7 @@ function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ { C+= colpeat-1; } else if(Rn[1]!=='/') { ++C; - textp = ""; textpidx = 0; textR = []; + textp = oldtextp = ""; textpidx = oldtextpidx = 0; textR = []; oldtextR = []; colpeat = 1; var rptR = rowpeat ? R + rowpeat - 1 : R; if(C > range.e.c) range.e.c = C; @@ -443,10 +443,17 @@ function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ { if(textR.length) /*::(*/comment/*:: :any)*/.R = textR; comment.a = creator; comments.push(comment); + textp = oldtextp; textpidx = oldtextpidx; textR = oldtextR; + } + else if(Rn[0].charAt(Rn[0].length-2) !== '/') { + state.push([Rn[3], false]); + var annotag = parsexmltag(Rn[0], true); + /* office:display TODO: check if there is a global override */ + if(!(annotag["display"] && parsexmlbool(annotag["display"]))) comments.hidden = true; + oldtextp = textp; oldtextpidx = textpidx; oldtextR = textR; + textp = ""; textpidx = 0; textR = []; } - else if(Rn[0].charAt(Rn[0].length-2) !== '/') {state.push([Rn[3], false]);} creator = ""; creatoridx = 0; - textp = ""; textpidx = 0; textR = []; break; case 'creator': // 4.3.2.7 diff --git a/bits/81_writeods.js b/bits/81_writeods.js index 3c612a0..67f5ced 100644 --- a/bits/81_writeods.js +++ b/bits/81_writeods.js @@ -204,7 +204,6 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function }; var null_cell_xml = ' \n'; - var covered_cell_xml = ' \n'; var write_ws = function(ws, wb/*:Workbook*/, i/*:number*/, opts, nfs)/*:string*/ { /* Section 9 Tables */ var o/*:Array*/ = []; @@ -236,7 +235,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function ct['table:number-rows-spanned'] = (marr[mi].e.r - marr[mi].s.r + 1); break; } - if(skip) { o.push(covered_cell_xml); continue; } + if(skip) { o.push(' \n'); continue; } var ref = encode_cell({r:R, c:C}), cell = dense ? (ws["!data"][R]||[])[C]: ws[ref]; if(cell && cell.f) { ct['table:formula'] = escapexml(csf_to_ods_formula(cell.f)); @@ -270,8 +269,8 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function ct['office:date-value'] = (parseDate(cell.v).toISOString()); ct['table:style-name'] = "ce1"; break; - //case 'e': - default: o.push(null_cell_xml); continue; + //case 'e': // TODO: translate to ODS errors + default: o.push(null_cell_xml); continue; // TODO: empty cell with comments } var text_p = write_text_p(textp); if(cell.l && cell.l.Target) { @@ -282,7 +281,17 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")}); } if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1); - o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n'); + var payload = writextag('text:p', text_p, {}); + if(cell.c) { + var acreator = "", apayload = "", aprops = {}; + for(var ci = 0; ci < cell.c.length; ++ci) { + if(!acreator && cell.c[ci].a) acreator = cell.c[ci].a; + apayload += "" + write_text_p(cell.c[ci].t) + ""; + } + if(!cell.c.hidden) aprops["office:display"] = true; + payload = writextag('office:annotation', apayload, aprops) + payload; + } + o.push(' ' + writextag('table:table-cell', payload, ct) + '\n'); } o.push(' \n'); } diff --git a/bits/83_numbers.js b/bits/83_numbers.js index 731d47a..07b6834 100644 --- a/bits/83_numbers.js +++ b/bits/83_numbers.js @@ -450,7 +450,7 @@ function compress_iwa_file(buf) { return u8concat(out); } var numbers_lut_new = function() { - return { sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [] }; + return { sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [], cmnt: [] }; }; function numbers_format_cell(cell, t, flags, ofmt, nfmt) { var _a, _b, _c, _d; @@ -684,7 +684,8 @@ function parse_new_storage(buf, lut) { var t = buf[1]; switch (t) { case 0: - return void 0; + ret = { t: "z" }; + break; case 2: ret = { t: "n", v: d128 }; break; @@ -726,6 +727,12 @@ function parse_new_storage(buf, lut) { zidx = dv.getUint32(doff, true); doff += 4; } + if (fields & 524288) { + var cmntidx = dv.getUint32(doff, true); + doff += 4; + if (lut.cmnt[cmntidx]) + ret.c = iwa_to_s5s_comment(lut.cmnt[cmntidx]); + } if (zidx > -1) numbers_format_cell(ret, t | 5 << 8, fields >> 13, lut.ofmt[zidx], lut.nfmt[zidx]); if (t == 7) @@ -927,6 +934,12 @@ function parse_TST_TableDataList(M, root) { case 3: data[key] = parse_shallow(le[5][0].data); break; + case 10: + { + var cs = M[parse_TSP_Reference(le[10][0].data)][0]; + data[key] = parse_TSD_CommentStorageArchive(M, cs.data); + } + break; default: throw type; } @@ -992,8 +1005,38 @@ function parse_TST_Tile(M, root) { }, []) }; } +function parse_TSD_CommentStorageArchive(M, data) { + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; + var out = { t: "", a: "" }; + var csp = parse_shallow(data); + if ((_b = (_a = csp == null ? void 0 : csp[1]) == null ? void 0 : _a[0]) == null ? void 0 : _b.data) + out.t = u8str((_d = (_c = csp == null ? void 0 : csp[1]) == null ? void 0 : _c[0]) == null ? void 0 : _d.data) || ""; + if ((_f = (_e = csp == null ? void 0 : csp[3]) == null ? void 0 : _e[0]) == null ? void 0 : _f.data) { + var as = M[parse_TSP_Reference((_h = (_g = csp == null ? void 0 : csp[3]) == null ? void 0 : _g[0]) == null ? void 0 : _h.data)][0]; + var asp = parse_shallow(as.data); + if ((_j = (_i = asp[1]) == null ? void 0 : _i[0]) == null ? void 0 : _j.data) + out.a = u8str(asp[1][0].data); + } + if (csp == null ? void 0 : csp[4]) { + out.replies = []; + csp[4].forEach(function(pi) { + var cs = M[parse_TSP_Reference(pi.data)][0]; + out.replies.push(parse_TSD_CommentStorageArchive(M, cs.data)); + }); + } + return out; +} +function iwa_to_s5s_comment(iwa) { + var out = []; + out.push({ t: iwa.t || "", a: iwa.a, T: iwa.replies && iwa.replies.length > 0 }); + if (iwa.replies) + iwa.replies.forEach(function(reply) { + out.push({ t: reply.t || "", a: reply.a, T: true }); + }); + return out; +} function parse_TST_TableModelArchive(M, root, ws) { - var _a, _b, _c, _d, _e, _f, _g, _h; + var _a, _b, _c, _d, _e, _f, _g, _h, _i; var pb = parse_shallow(root.data); var range = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } }; range.e.r = (varint_to_i32(pb[6][0].data) >>> 0) - 1; @@ -1016,7 +1059,9 @@ function parse_TST_TableModelArchive(M, root, ws) { lut.ferr = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[12][0].data)][0]); if ((_e = store[17]) == null ? void 0 : _e[0]) lut.rsst = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[17][0].data)][0]); - if ((_f = store[22]) == null ? void 0 : _f[0]) + if ((_f = store[19]) == null ? void 0 : _f[0]) + lut.cmnt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[19][0].data)][0]); + if ((_g = store[22]) == null ? void 0 : _g[0]) lut.nfmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[22][0].data)][0]); var tile = parse_shallow(store[3][0].data); var _R = 0; @@ -1043,12 +1088,12 @@ function parse_TST_TableModelArchive(M, root, ws) { }); _R += _tile.nrows; }); - if ((_g = store[13]) == null ? void 0 : _g[0]) { + if ((_h = store[13]) == null ? void 0 : _h[0]) { var ref = M[parse_TSP_Reference(store[13][0].data)][0]; var mtype = varint_to_i32(ref.meta[1][0].data); if (mtype != 6144) throw new Error("Expected merge type 6144, found ".concat(mtype)); - ws["!merges"] = (_h = parse_shallow(ref.data)) == null ? void 0 : _h[1].map(function(pi) { + ws["!merges"] = (_i = parse_shallow(ref.data)) == null ? void 0 : _i[1].map(function(pi) { var merge = parse_shallow(pi.data); var origin = u8_to_dataview(parse_shallow(merge[1][0].data)[1][0].data), size = u8_to_dataview(parse_shallow(merge[2][0].data)[1][0].data); return { diff --git a/bits/85_parsezip.js b/bits/85_parsezip.js index b5262dc..41f6a28 100644 --- a/bits/85_parsezip.js +++ b/bits/85_parsezip.js @@ -13,6 +13,13 @@ function safe_parse_wbrels(wbrels, sheets) { return !wbrels || wbrels.length === 0 ? null : wbrels; } +function parse_sheet_legacy_drawing(sheet, type, zip, path, idx, opts, wb, comments) { + if(!sheet || !sheet['!legdrawel']) return; + var dfile = resolve_path(sheet['!legdrawel'].Target, path); + var draw = getzipstr(zip, dfile, true); + if(draw) parse_vml(utf8read(draw), sheet, comments||[]); +} + function safe_parse_sheet(zip, path/*:string*/, relsPath/*:string*/, sheet, idx/*:number*/, sheetRels, sheets, stype/*:string*/, opts, wb, themes, styles) { try { sheetRels[sheet]=parse_rels(getzipstr(zip, relsPath, true), path); @@ -51,6 +58,7 @@ function safe_parse_sheet(zip, path/*:string*/, relsPath/*:string*/, sheet, idx/ } }); if(tcomments && tcomments.length) sheet_insert_comments(_ws, tcomments, true, opts.people || []); + parse_sheet_legacy_drawing(_ws, stype, zip, path, idx, opts, wb, comments); } catch(e) { if(opts.WTF) throw e; } } diff --git a/bits/86_writezip.js b/bits/86_writezip.js index 4c85250..5e819f0 100644 --- a/bits/86_writezip.js +++ b/bits/86_writezip.js @@ -51,6 +51,9 @@ function write_zip_xlsb(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { add_rels(opts.rels, 4, f, RELS.CUST_PROPS); } + var people = ["SheetJ5"]; + opts.tcid = 0; + for(rId=1;rId <= wb.SheetNames.length; ++rId) { var wsrels = {'!id':{}}; var ws = wb.Sheets[wb.SheetNames[rId-1]]; @@ -70,6 +73,17 @@ function write_zip_xlsb(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { var need_vml = false; var cf = ""; if(comments && comments.length > 0) { + var needtc = false; + comments.forEach(function(carr) { + carr[1].forEach(function(c) { if(c.T == true) needtc = true; }); + }); + if(needtc) { + cf = "xl/threadedComments/threadedComment" + rId + ".xml"; + zip_add_file(zip, cf, write_tcmnt_xml(comments, people, opts)); + ct.threadedcomments.push(cf); + add_rels(wsrels, -1, "../threadedComments/threadedComment" + rId + ".xml", RELS.TCMNT); + } + cf = "xl/comments" + rId + "." + wbext; zip_add_file(zip, cf, write_comments_bin(comments, opts)); ct.comments.push(cf); @@ -125,6 +139,13 @@ function write_zip_xlsb(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ { ct.metadata.push(f); add_rels(opts.wbrels, -1, "metadata." + wbext, RELS.XLMETA); + if(people.length > 1) { + f = "xl/persons/person.xml"; + zip_add_file(zip, f, write_people_xml(people, opts)); + ct.people.push(f); + add_rels(opts.wbrels, -1, "persons/person.xml", RELS.PEOPLE); + } + zip_add_file(zip, "[Content_Types].xml", write_ct(ct, opts)); zip_add_file(zip, '_rels/.rels', write_rels(opts.rels)); zip_add_file(zip, 'xl/_rels/workbook.' + wbext + '.rels', write_rels(opts.wbrels)); diff --git a/bits/90_utils.js b/bits/90_utils.js index 14e1353..35b09c4 100644 --- a/bits/90_utils.js +++ b/bits/90_utils.js @@ -15,7 +15,7 @@ function make_json_row(sheet/*:Worksheet*/, r/*:Range*/, R/*:number*/, cols/*:Ar } if(!dense || sheet["!data"][R]) for (var C = r.s.c; C <= r.e.c; ++C) { var val = dense ? (sheet["!data"][R]||[])[C] : sheet[cols[C] + rr]; - if(val === undefined || val.t === undefined) { + if(val == null || val.t === undefined) { if(defval === undefined) continue; if(hdr[C] != null) { row[hdr[C]] = defval; } continue; diff --git a/modules/04_base64.js b/modules/04_base64.js index 637bee2..459523d 100644 --- a/modules/04_base64.js +++ b/modules/04_base64.js @@ -45,6 +45,26 @@ function Base64_encode_pass(input) { } return o; } +function Base64_encode_arr(input) { + var o = ""; + var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0; + for (var i = 0; i < input.length; ) { + c1 = input[i++]; + e1 = c1 >> 2; + c2 = input[i++]; + e2 = (c1 & 3) << 4 | c2 >> 4; + c3 = input[i++]; + e3 = (c2 & 15) << 2 | c3 >> 6; + e4 = c3 & 63; + if (isNaN(c2)) { + e3 = e4 = 64; + } else if (isNaN(c3)) { + e4 = 64; + } + o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4); + } + return o; +} function Base64_decode(input) { var o = ""; var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0; diff --git a/modules/04_base64.ts b/modules/04_base64.ts index 10bfc37..1943bcc 100644 --- a/modules/04_base64.ts +++ b/modules/04_base64.ts @@ -37,6 +37,25 @@ function Base64_encode_pass(input: string): string { } return o; } +function Base64_encode_arr(input: Uint8Array|number[]): string { + var o = ""; + var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0; + for(var i = 0; i < input.length; ) { + c1 = input[i++]; + e1 = (c1 >> 2); + + c2 = input[i++]; + e2 = ((c1 & 3) << 4) | (c2 >> 4); + + c3 = input[i++]; + e3 = ((c2 & 15) << 2) | (c3 >> 6); + e4 = (c3 & 63); + if (isNaN(c2)) { e3 = e4 = 64; } + else if (isNaN(c3)) { e4 = 64; } + o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4); + } + return o; +} function Base64_decode(input: string): string { var o = ""; var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0; diff --git a/modules/83_numbers.js b/modules/83_numbers.js index 731d47a..07b6834 100644 --- a/modules/83_numbers.js +++ b/modules/83_numbers.js @@ -450,7 +450,7 @@ function compress_iwa_file(buf) { return u8concat(out); } var numbers_lut_new = function() { - return { sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [] }; + return { sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [], cmnt: [] }; }; function numbers_format_cell(cell, t, flags, ofmt, nfmt) { var _a, _b, _c, _d; @@ -684,7 +684,8 @@ function parse_new_storage(buf, lut) { var t = buf[1]; switch (t) { case 0: - return void 0; + ret = { t: "z" }; + break; case 2: ret = { t: "n", v: d128 }; break; @@ -726,6 +727,12 @@ function parse_new_storage(buf, lut) { zidx = dv.getUint32(doff, true); doff += 4; } + if (fields & 524288) { + var cmntidx = dv.getUint32(doff, true); + doff += 4; + if (lut.cmnt[cmntidx]) + ret.c = iwa_to_s5s_comment(lut.cmnt[cmntidx]); + } if (zidx > -1) numbers_format_cell(ret, t | 5 << 8, fields >> 13, lut.ofmt[zidx], lut.nfmt[zidx]); if (t == 7) @@ -927,6 +934,12 @@ function parse_TST_TableDataList(M, root) { case 3: data[key] = parse_shallow(le[5][0].data); break; + case 10: + { + var cs = M[parse_TSP_Reference(le[10][0].data)][0]; + data[key] = parse_TSD_CommentStorageArchive(M, cs.data); + } + break; default: throw type; } @@ -992,8 +1005,38 @@ function parse_TST_Tile(M, root) { }, []) }; } +function parse_TSD_CommentStorageArchive(M, data) { + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; + var out = { t: "", a: "" }; + var csp = parse_shallow(data); + if ((_b = (_a = csp == null ? void 0 : csp[1]) == null ? void 0 : _a[0]) == null ? void 0 : _b.data) + out.t = u8str((_d = (_c = csp == null ? void 0 : csp[1]) == null ? void 0 : _c[0]) == null ? void 0 : _d.data) || ""; + if ((_f = (_e = csp == null ? void 0 : csp[3]) == null ? void 0 : _e[0]) == null ? void 0 : _f.data) { + var as = M[parse_TSP_Reference((_h = (_g = csp == null ? void 0 : csp[3]) == null ? void 0 : _g[0]) == null ? void 0 : _h.data)][0]; + var asp = parse_shallow(as.data); + if ((_j = (_i = asp[1]) == null ? void 0 : _i[0]) == null ? void 0 : _j.data) + out.a = u8str(asp[1][0].data); + } + if (csp == null ? void 0 : csp[4]) { + out.replies = []; + csp[4].forEach(function(pi) { + var cs = M[parse_TSP_Reference(pi.data)][0]; + out.replies.push(parse_TSD_CommentStorageArchive(M, cs.data)); + }); + } + return out; +} +function iwa_to_s5s_comment(iwa) { + var out = []; + out.push({ t: iwa.t || "", a: iwa.a, T: iwa.replies && iwa.replies.length > 0 }); + if (iwa.replies) + iwa.replies.forEach(function(reply) { + out.push({ t: reply.t || "", a: reply.a, T: true }); + }); + return out; +} function parse_TST_TableModelArchive(M, root, ws) { - var _a, _b, _c, _d, _e, _f, _g, _h; + var _a, _b, _c, _d, _e, _f, _g, _h, _i; var pb = parse_shallow(root.data); var range = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } }; range.e.r = (varint_to_i32(pb[6][0].data) >>> 0) - 1; @@ -1016,7 +1059,9 @@ function parse_TST_TableModelArchive(M, root, ws) { lut.ferr = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[12][0].data)][0]); if ((_e = store[17]) == null ? void 0 : _e[0]) lut.rsst = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[17][0].data)][0]); - if ((_f = store[22]) == null ? void 0 : _f[0]) + if ((_f = store[19]) == null ? void 0 : _f[0]) + lut.cmnt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[19][0].data)][0]); + if ((_g = store[22]) == null ? void 0 : _g[0]) lut.nfmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[22][0].data)][0]); var tile = parse_shallow(store[3][0].data); var _R = 0; @@ -1043,12 +1088,12 @@ function parse_TST_TableModelArchive(M, root, ws) { }); _R += _tile.nrows; }); - if ((_g = store[13]) == null ? void 0 : _g[0]) { + if ((_h = store[13]) == null ? void 0 : _h[0]) { var ref = M[parse_TSP_Reference(store[13][0].data)][0]; var mtype = varint_to_i32(ref.meta[1][0].data); if (mtype != 6144) throw new Error("Expected merge type 6144, found ".concat(mtype)); - ws["!merges"] = (_h = parse_shallow(ref.data)) == null ? void 0 : _h[1].map(function(pi) { + ws["!merges"] = (_i = parse_shallow(ref.data)) == null ? void 0 : _i[1].map(function(pi) { var merge = parse_shallow(pi.data); var origin = u8_to_dataview(parse_shallow(merge[1][0].data)[1][0].data), size = u8_to_dataview(parse_shallow(merge[2][0].data)[1][0].data); return { diff --git a/modules/83_numbers.ts b/modules/83_numbers.ts index 43c8bca..c412627 100644 --- a/modules/83_numbers.ts +++ b/modules/83_numbers.ts @@ -3,7 +3,7 @@ /* these are type imports and do not show up in the generated JS */ import { CFB$Container, CFB$Entry } from 'cfb'; -import { WorkBook, WorkSheet, Range, CellObject, ParsingOptions, WritingOptions, DenseWorkSheet } from '../'; +import { WorkBook, WorkSheet, Range, CellObject, ParsingOptions, WritingOptions, DenseWorkSheet, Comments } from '../'; import type { utils } from "../"; declare var encode_col: typeof utils.encode_col; @@ -389,6 +389,14 @@ interface RichText { l?: string; } +/** IWA tree-style comment */ +interface IWAComment { + /** text */ + t?: string; + /** author */ + a?: string; + replies?: IWAComment[]; +} /** .TST.DataStore */ interface DataLUT { /** shared string table */ @@ -403,8 +411,10 @@ interface DataLUT { fmla: ProtoMessage[]; /** formula errors */ ferr: ProtoMessage[]; + /** comment table */ + cmnt: IWAComment[]; } -var numbers_lut_new = (): DataLUT => ({ sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [] }); +var numbers_lut_new = (): DataLUT => ({ sst: [], rsst: [], ofmt: [], nfmt: [], fmla: [], ferr: [], cmnt: [] }); function numbers_format_cell(cell: CellObject, t: number, flags: number, ofmt: ProtoMessage, nfmt: ProtoMessage): void { var ctype = t & 0xFF, ver = t >> 8; @@ -552,7 +562,7 @@ function parse_new_storage(buf: Uint8Array, lut: DataLUT): CellObject | void { var ret: CellObject; var t = buf[1]; switch(t) { - case 0: return void 0; // return { t: "z" }; // blank? + case 0: ret = { t: "z" }; break; case 2: ret = { t: "n", v: d128 }; break; // number case 3: ret = { t: "s", v: lut.sst[sidx] }; break; // string case 5: ret = { t: "d", v: dt }; break; // date-time @@ -578,6 +588,10 @@ function parse_new_storage(buf: Uint8Array, lut: DataLUT): CellObject | void { if(fields & 0x07E000) { if(zidx == -1) zidx = dv.getUint32(doff, true); doff += 4; } // 0x080000 comment + if(fields & 0x080000) { + var cmntidx = dv.getUint32(doff, true); doff += 4; + if(lut.cmnt[cmntidx]) ret.c = iwa_to_s5s_comment(lut.cmnt[cmntidx]); + } // 0x100000 warning if(zidx > -1) numbers_format_cell(ret, t | (5<<8), fields >> 13, lut.ofmt[zidx], lut.nfmt[zidx] ); @@ -736,6 +750,11 @@ function parse_TST_TableDataList(M: MessageSpace, root: IWAMessage): any[] { } break; case 2: data[key] = parse_shallow(le[6][0].data); break; case 3: data[key] = parse_shallow(le[5][0].data); break; + case 10: { + // .TSD.CommentStorageArchive + var cs = M[parse_TSP_Reference(le[10][0].data)][0]; + data[key] = parse_TSD_CommentStorageArchive(M, cs.data); + } break; default: throw type; } }); @@ -804,6 +823,37 @@ function parse_TST_Tile(M: MessageSpace, root: IWAMessage): TileInfo { }; } +/** Parse .TSD.CommentStorageArchive (3056) */ +function parse_TSD_CommentStorageArchive(M: MessageSpace, data: Uint8Array): IWAComment { + var out: IWAComment = { t: "", a: ""}; + var csp = parse_shallow(data); + if(csp?.[1]?.[0]?.data) out.t = u8str(csp?.[1]?.[0]?.data) || ""; + if(csp?.[3]?.[0]?.data) { + /* .TSK.AnnotationAuthorArchive (212) */ + var as = M[parse_TSP_Reference(csp?.[3]?.[0]?.data)][0]; + var asp = parse_shallow(as.data); + if(asp[1]?.[0]?.data) out.a = u8str(asp[1][0].data) + } + if(csp?.[4]) { + out.replies = []; + csp[4].forEach(pi => { + var cs = M[parse_TSP_Reference(pi.data)][0]; + out.replies!.push(parse_TSD_CommentStorageArchive(M, cs.data)); + }); + } + return out; +} +/** Create SheetJS threaded comment structure from IWA comment */ +function iwa_to_s5s_comment(iwa: IWAComment): Comments { + var out: Comments = []; + out.push({t: iwa.t||"", a: iwa.a, T: iwa.replies && (iwa.replies.length > 0) }); + /* TODO: do apps support a tree of replies? */ + if(iwa.replies) iwa.replies.forEach(reply => { + out.push({t: reply.t ||"", a: reply.a, T:true }); + }); + return out; +} + /** Parse .TST.TableModelArchive (6001) */ function parse_TST_TableModelArchive(M: MessageSpace, root: IWAMessage, ws: WorkSheet) { var pb = parse_shallow(root.data); @@ -822,6 +872,7 @@ function parse_TST_TableModelArchive(M: MessageSpace, root: IWAMessage, ws: Work if(store[11]?.[0]) lut.ofmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[11][0].data)][0]); if(store[12]?.[0]) lut.ferr = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[12][0].data)][0]); if(store[17]?.[0]) lut.rsst = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[17][0].data)][0]); + if(store[19]?.[0]) lut.cmnt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[19][0].data)][0]); if(store[22]?.[0]) lut.nfmt = parse_TST_TableDataList(M, M[parse_TSP_Reference(store[22][0].data)][0]); // .TST.TileStorage