outline minutiae

- column levels (fixes #1655)
- XLSX parse sheetPr with children (fixes #1798)
- XLSX enforce 32768 string limit (fixes #1537)
This commit is contained in:
SheetJS 2021-09-22 02:45:08 -04:00
parent 4518cc8967
commit a4ee0ef05a
9 changed files with 39 additions and 8 deletions

@ -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 */

@ -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;
}

@ -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;

@ -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);

@ -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;

@ -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;

@ -13,7 +13,7 @@ In the browser, just add a script tag:
|-----------:|:-------------------------------------------|
| `unpkg` | <https://unpkg.com/xlsx/> |
| `jsDelivr` | <https://jsdelivr.com/package/npm/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `CDNjs` | <https://cdnjs.com/libraries/xlsx> |
| `packd` | <https://bundle.run/xlsx@latest?name=XLSX> |
`unpkg` makes the latest version available at:

@ -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
};
```

3
types/index.d.ts vendored

@ -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;
}