From a4ee0ef05a65f29c2caa96c7bc1ba5c5316a3dfe Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 22 Sep 2021 02:45:08 -0400 Subject: [PATCH] outline minutiae - column levels (fixes #1655) - XLSX parse sheetPr with children (fixes #1798) - XLSX enforce 32768 string limit (fixes #1537) --- bits/39_xlsbiff.js | 2 +- bits/66_wscommon.js | 1 + bits/67_wsxml.js | 7 +++++++ bits/68_wsbin.js | 18 ++++++++++++++---- bits/75_xlml.js | 11 +++++++++-- bits/76_xls.js | 2 ++ docbits/10_install.md | 2 +- docbits/62_colrow.md | 1 + types/index.d.ts | 3 +++ 9 files changed, 39 insertions(+), 8 deletions(-) diff --git a/bits/39_xlsbiff.js b/bits/39_xlsbiff.js index 755b5de..683ae20 100644 --- a/bits/39_xlsbiff.js +++ b/bits/39_xlsbiff.js @@ -223,7 +223,7 @@ function write_WriteAccess(s/*:string*/, opts) { /* [MS-XLS] 2.4.351 */ function parse_WsBool(blob, length, opts) { var flags = opts && opts.biff == 8 || length == 2 ? blob.read_shift(2) : (blob.l += length, 0); - return { fDialog: flags & 0x10 }; + return { fDialog: flags & 0x10, fBelow: flags & 0x40, fRight: flags & 0x80 }; } /* [MS-XLS] 2.4.28 */ diff --git a/bits/66_wscommon.js b/bits/66_wscommon.js index d62e348..ce038f8 100644 --- a/bits/66_wscommon.js +++ b/bits/66_wscommon.js @@ -45,6 +45,7 @@ function col_obj_w(C/*:number*/, col) { if(wch > -1) { p.width = char2width(wch); p.customWidth = 1; } else if(col.width != null) p.width = col.width; if(col.hidden) p.hidden = true; + if(col.level != null) { p.outlineLevel = p.level = col.level; } return p; } diff --git a/bits/67_wsxml.js b/bits/67_wsxml.js index 46cbc55..1ff5ded 100644 --- a/bits/67_wsxml.js +++ b/bits/67_wsxml.js @@ -10,6 +10,7 @@ var colregex = /<(?:\w:)?col\b[^>]*[\/]?>/g; var afregex = /<(?:\w:)?autoFilter[^>]*([\/]|>([\s\S]*)<\/(?:\w:)?autoFilter)>/g; var marginregex= /<(?:\w:)?pageMargins[^>]*\/>/g; var sheetprregex = /<(?:\w:)?sheetPr\b(?:[^>a-z][^>]*)?\/>/; +var sheetprregex2= /<(?:\w:)?sheetPr[^>]*(?:[\/]|>([\s\S]*)<\/(?:\w:)?sheetPr)>/; var svsregex = /<(?:\w:)?sheetViews[^>]*(?:[\/]|>([\s\S]*)<\/(?:\w:)?sheetViews)>/; /* 18.3 Worksheets */ @@ -32,6 +33,7 @@ function parse_ws_xml(data/*:?string*/, opts, idx/*:number*/, rels, wb/*:WBWBPro /* 18.3.1.82 sheetPr CT_SheetPr */ var sheetPr = data1.match(sheetprregex); if(sheetPr) parse_ws_xml_sheetpr(sheetPr[0], s, wb, idx); + else if((sheetPr = data1.match(sheetprregex2))) parse_ws_xml_sheetpr2(sheetPr[0], sheetPr[1]||"", s, wb, idx, styles, themes); /* 18.3.1.35 dimension CT_SheetDimension */ var ridx = (data1.match(/<(?:\w*:)?dimension/)||{index:-1}).index; @@ -104,6 +106,9 @@ function parse_ws_xml_sheetpr(sheetPr/*:string*/, s, wb/*:WBWBProps*/, idx/*:num if(!wb.Sheets[idx]) wb.Sheets[idx] = {}; if(data.codeName) wb.Sheets[idx].CodeName = unescapexml(utf8read(data.codeName)); } +function parse_ws_xml_sheetpr2(sheetPr/*:string*/, body/*:string*/, s, wb/*:WBWBProps*/, idx/*:number*/, styles, themes) { + parse_ws_xml_sheetpr(sheetPr.slice(0, sheetPr.indexOf(">")), s, wb, idx); +} function write_ws_xml_sheetpr(ws, wb, idx, opts, o) { var needed = false; var props = {}, payload = null; @@ -191,6 +196,7 @@ function parse_ws_xml_cols(columns, cols) { var coll = parsexmltag(cols[coli], true); if(coll.hidden) coll.hidden = parsexmlbool(coll.hidden); var colm=parseInt(coll.min, 10)-1, colM=parseInt(coll.max,10)-1; + if(coll.outlineLevel) coll.level = (+coll.outlineLevel || 0); delete coll.min; delete coll.max; coll.width = +coll.width; if(!seencol && coll.width) { seencol = true; find_mdw_colw(coll.width); } process_col(coll); @@ -280,6 +286,7 @@ function write_ws_xml_cell(cell/*:Cell*/, ref, ws, opts/*::, idx, wb*/)/*:string case 'e': o.t = "e"; break; case 'z': break; default: if(cell.v == null) { delete cell.t; break; } + if(cell.v.length > 32767) throw new Error("Text length must not exceed 32767 characters"); if(opts && opts.bookSST) { v = writetag('v', ''+get_sst_id(opts.Strings, cell.v, opts.revStrings)); o.t = "s"; break; diff --git a/bits/68_wsbin.js b/bits/68_wsbin.js index 83c0c61..0adf921 100644 --- a/bits/68_wsbin.js +++ b/bits/68_wsbin.js @@ -79,14 +79,23 @@ function parse_BrtWsFmtInfo(/*::data, length*/) { /* [MS-XLSB] 2.4.823 BrtWsProp */ function parse_BrtWsProp(data, length) { var z = {}; + var f = data[data.l]; ++data.l; + z.above = !(f & 0x40); + z.left = !(f & 0x80); /* TODO: pull flags */ - data.l += 19; + data.l += 18; z.name = parse_XLSBCodeName(data, length - 19); return z; } -function write_BrtWsProp(str, o) { +function write_BrtWsProp(str, outl, o) { if(o == null) o = new_buf(84+4*str.length); - for(var i = 0; i < 3; ++i) o.write_shift(1,0); + var f = 0xC0; + if(outl) { + if(outl.above) f &= ~0x40; + if(outl.left) f &= ~0x80; + } + o.write_shift(1, f); + for(var i = 1; i < 3; ++i) o.write_shift(1,0); write_BrtColor({auto:1}, o); o.write_shift(-4,-1); o.write_shift(-4,-1); @@ -660,6 +669,7 @@ function parse_ws_bin(data, _opts, idx, rels, wb/*:WBWBProps*/, themes, styles)/ case 0x0093: /* 'BrtWsProp' */ if(!wb.Sheets[idx]) wb.Sheets[idx] = {}; if(val.name) wb.Sheets[idx].CodeName = val.name; + if(val.above || val.left) s['!outline'] = { above: val.above, left: val.left }; break; case 0x0089: /* 'BrtBeginWsView' */ @@ -956,7 +966,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) { /* passed back to write_zip and removed there */ ws['!comments'] = []; write_record(ba, "BrtBeginSheet"); - if(wb.vbaraw) write_record(ba, "BrtWsProp", write_BrtWsProp(c)); + if(wb.vbaraw || ws['!outline']) write_record(ba, "BrtWsProp", write_BrtWsProp(c, ws['!outline'])); write_record(ba, "BrtWsDim", write_BrtWsDim(r)); write_WSVIEWS2(ba, ws, wb.Workbook); write_WSFMTINFO(ba, ws); diff --git a/bits/75_xlml.js b/bits/75_xlml.js index c2eee20..08aac77 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -618,6 +618,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { case 'unsynced' /*case 'Unsynced'*/: break; case 'print' /*case 'Print'*/: break; + case 'printerrors' /*case 'PrintErrors'*/: break; case 'panes' /*case 'Panes'*/: break; case 'scale' /*case 'Scale'*/: break; case 'pane' /*case 'Pane'*/: break; @@ -657,11 +658,17 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ { case 'allowformatcells' /*case 'AllowFormatCells'*/: break; case 'allowsizecols' /*case 'AllowSizeCols'*/: break; case 'allowsizerows' /*case 'AllowSizeRows'*/: break; - case 'nosummaryrowsbelowdetail' /*case 'NoSummaryRowsBelowDetail'*/: break; + case 'nosummaryrowsbelowdetail' /*case 'NoSummaryRowsBelowDetail'*/: + if(!cursheet["!outline"]) cursheet["!outline"] = {}; + cursheet["!outline"].above = true; + break; case 'tabcolorindex' /*case 'TabColorIndex'*/: break; case 'donotdisplayheadings' /*case 'DoNotDisplayHeadings'*/: break; case 'showpagelayoutzoom' /*case 'ShowPageLayoutZoom'*/: break; - case 'nosummarycolumnsrightdetail' /*case 'NoSummaryColumnsRightDetail'*/: break; + case 'nosummarycolumnsrightdetail' /*case 'NoSummaryColumnsRightDetail'*/: + if(!cursheet["!outline"]) cursheet["!outline"] = {}; + cursheet["!outline"].left = true; + break; case 'blackandwhite' /*case 'BlackAndWhite'*/: break; case 'donotdisplayzeros' /*case 'DoNotDisplayZeros'*/: break; case 'displaypagebreak' /*case 'DisplayPageBreak'*/: break; diff --git a/bits/76_xls.js b/bits/76_xls.js index 31937b4..fbbb938 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -272,6 +272,8 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { case 'ForceFullCalculation': wb.opts.FullCalc = val; break; case 'WsBool': if(val.fDialog) out["!type"] = "dialog"; + if(!val.fBelow) (out["!outline"] || (out["!outline"] = {})).above = true; + if(!val.fRight) (out["!outline"] || (out["!outline"] = {})).left = true; break; // TODO case 'XF': XFs.push(val); break; diff --git a/docbits/10_install.md b/docbits/10_install.md index a981bcf..ef0f6ac 100644 --- a/docbits/10_install.md +++ b/docbits/10_install.md @@ -13,7 +13,7 @@ In the browser, just add a script tag: |-----------:|:-------------------------------------------| | `unpkg` | | | `jsDelivr` | | -| `CDNjs` | | +| `CDNjs` | | | `packd` | | `unpkg` makes the latest version available at: diff --git a/docbits/62_colrow.md b/docbits/62_colrow.md index 3aae32e..f366831 100644 --- a/docbits/62_colrow.md +++ b/docbits/62_colrow.md @@ -14,6 +14,7 @@ type ColInfo = { wch?: number; // width in characters /* other fields for preserving features from files */ + level?: number; // 0-indexed outline / group level MDW?: number; // Excel's "Max Digit Width" unit, always integral }; ``` diff --git a/types/index.d.ts b/types/index.d.ts index a2174d2..ae0aa9c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -340,6 +340,9 @@ export interface ColInfo { /** width in "characters" */ wch?: number; + /** outline / group level */ + level?: number; + /** Excel's "Max Digit Width" unit, always integral */ MDW?: number; }