sheet visibility

- XLSB read V H VH + write V H VH
- XLSX read V H VH + write V H VH
- XLML read V H VH + write V H VH
- XLS  read V H VH

- fixes #123 h/t @rla-dev @Mior
- fixes #464 h/t @enobufs @thowk
- fixes #498 h/t @digity
- fixes #503 h/t @digity
This commit is contained in:
SheetJS 2017-03-31 14:46:42 -04:00
parent 233eae2f4e
commit 97f7c1d4bf
23 changed files with 554 additions and 82 deletions

@ -44,6 +44,7 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
+ [Formulae](#formulae)
+ [Column Properties](#column-properties)
+ [Hyperlinks](#hyperlinks)
+ [Sheet Visibility](#sheet-visibility)
- [Parsing Options](#parsing-options)
* [Input Type](#input-type)
* [Guessing File Type](#guessing-file-type)
@ -714,6 +715,37 @@ ws['A3'].l = { Target:"http://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" };
Note that Excel does not automatically style hyperlinks -- they will generally
be displayed as normal text.
#### Sheet Visibility
Excel enables hiding sheets in the lower tab bar. The sheet data is stored in
the file but the UI does not readily make it available. Standard hidden sheets
are revealed in the unhide menu. Excel also has "very hidden" sheets which
cannot be revealed in the menu. It is only accessible in the VB Editor!
The visibility setting is stored in the `Hidden` property of the sheet props
array. The values are:
| Value | Definition |
|:-----:|:------------|
| 0 | Visible |
| 1 | Hidden |
| 2 | Very Hidden |
With <https://rawgit.com/SheetJS/test_files/master/sheet_visibility.xlsx>:
```js
> wb.Workbook.Sheets.map(function(x) { return [x.name, x.Hidden] })
[ [ 'Visible', 0 ], [ 'Hidden', 1 ], [ 'VeryHidden', 2 ] ]
```
Non-Excel formats do not support the Very Hidden state. The best way to test
if a sheet is visible is to check if the `Hidden` property is logical negation:
```js
> wb.Workbook.Sheets.map(function(x) { return [x.name, !x.Hidden] })
[ [ 'Visible', true ], [ 'Hidden', false ], [ 'VeryHidden', false ] ]
```
## Parsing Options
The exported `read` and `readFile` functions accept an options argument:

@ -179,6 +179,7 @@ function prep_blob(blob, pos/*:number*/) {
}
function parsenoop(blob, length/*:number*/) { blob.l += length; }
function parsenooplog(blob, length/*:number*/) { if(typeof console != 'undefined') console.log(blob.slice(blob.l, blob.l + length)); blob.l += length; }
function writenoop(blob, length/*:number*/) { blob.l += length; }

@ -35,8 +35,8 @@ function parse_ext_props(data, p) {
var v = parseVector(q.HeadingPairs);
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
var idx = 0, len = 0;
for(var i = 0; i !== v.length; ++i) {
len = +(v[++i].v);
for(var i = 0; i !== v.length; i+=2) {
len = +(v[i+1].v);
switch(v[i].v) {
case "Worksheets":
case "工作表":
@ -69,6 +69,7 @@ function parse_ext_props(data, p) {
idx += len;
}
}
return p;
}

@ -303,7 +303,7 @@ function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb)) != null) r.push(cell);
}
if(r.length > 0) {
var params = {r:rr}
var params = ({r:rr}/*:any*/);
if(rows && rows[R]) {
var row = rows[R];
if(row.hidden) params.hidden = 1;

@ -37,7 +37,7 @@ var WBViewDef = [
/* 18.2.19 (CT_Sheet) Defaults */
var SheetDef = [
['state', 'visible']
//['state', 'visible']
];
/* 18.2.2 (CT_CalcPr) Defaults */

@ -43,7 +43,15 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ {
/* 18.2.20 sheets CT_Sheets 1 */
case '<sheets>': case '</sheets>': break; // aggregate sheet
/* 18.2.19 sheet CT_Sheet + */
case '<sheet': delete y[0]; y.name = unescapexml(utf8read(y.name)); wb.Sheets.push(y); break;
case '<sheet':
switch(y.state) {
case "hidden": y.Hidden = 1; break;
case "veryHidden": y.Hidden = 2; break;
default: y.Hidden = 0;
}
delete y.state;
y.name = unescapexml(utf8read(y.name));
delete y[0]; wb.Sheets.push(y); break;
case '</sheet>': break;
/* 18.2.15 functionGroups CT_FunctionGroups ? */
@ -153,8 +161,17 @@ function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ {
o[o.length] = WB_XML_ROOT;
o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"}));
o[o.length] = "<sheets>";
for(var i = 0; i != wb.SheetNames.length; ++i)
o[o.length] = (writextag('sheet',null,{name:escapexml(wb.SheetNames[i].substr(0,31)), sheetId:""+(i+1), "r:id":"rId"+(i+1)}));
var sheets = wb.Workbook && wb.Workbook.Sheets || [];
for(var i = 0; i != wb.SheetNames.length; ++i) {
var sht = ({name:escapexml(wb.SheetNames[i].substr(0,31))}/*:any*/);
sht.sheetId = ""+(i+1);
sht["r:id"] = "rId"+(i+1);
if(sheets[i]) switch(sheets[i].Hidden) {
case 1: sht.state = "hidden"; break;
case 2: sht.state = "veryHidden"; break;
}
o[o.length] = (writextag('sheet',null,sht));
}
o[o.length] = "</sheets>";
if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); }
return o.join("");

@ -1,7 +1,7 @@
/* [MS-XLSB] 2.4.301 BrtBundleSh */
function parse_BrtBundleSh(data, length/*:number*/) {
var z = {};
z.hsState = data.read_shift(4); //ST_SheetState
z.Hidden = data.read_shift(4); //hsState ST_SheetState
z.iTabID = data.read_shift(4);
z.strRelID = parse_RelID(data,length-8);
z.name = parse_XLWideString(data);
@ -9,7 +9,7 @@ function parse_BrtBundleSh(data, length/*:number*/) {
}
function write_BrtBundleSh(data, o) {
if(!o) o = new_buf(127);
o.write_shift(4, data.hsState);
o.write_shift(4, data.Hidden);
o.write_shift(4, data.iTabID);
write_RelID(data.strRelID, o);
write_XLWideString(data.name.substr(0,31), o);
@ -138,7 +138,8 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
function write_BUNDLESHS(ba, wb, opts) {
write_record(ba, "BrtBeginBundleShs");
for(var idx = 0; idx != wb.SheetNames.length; ++idx) {
var d = { hsState: 0, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: wb.SheetNames[idx] };
var viz = wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx] && wb.Workbook.Sheets[idx].Hidden || 0;
var d = { Hidden: viz, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: wb.SheetNames[idx] };
write_record(ba, "BrtBundleSh", write_BrtBundleSh(d));
}
write_record(ba, "BrtEndBundleShs");
@ -156,9 +157,34 @@ function write_BrtFileVersion(data, o) {
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.4.298 BrtBookView */
function write_BrtBookView(idx, o) {
if(!o) o = new_buf(29);
o.write_shift(-4, 0);
o.write_shift(-4, 460);
o.write_shift(4, 28800);
o.write_shift(4, 17600);
o.write_shift(4, 500);
o.write_shift(4, idx);
o.write_shift(4, idx);
var flags = 0x78;
o.write_shift(1, flags);
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.1.7.60 Workbook */
function write_BOOKVIEWS(ba, wb, opts) {
/* required if hidden tab appears before visible tab */
if(!wb.Workbook || !wb.Workbook.Sheets) return;
var sheets = wb.Workbook.Sheets;
var i = 0, vistab = -1, hidden = -1;
for(; i < sheets.length; ++i) {
if(!sheets[i] || !sheets[i].Hidden && vistab == -1) vistab = i;
else if(sheets[i].Hidden == 1 && hidden == -1) hidden = i;
}
if(hidden > vistab) return;
write_record(ba, "BrtBeginBookViews");
write_record(ba, "BrtBookView", write_BrtBookView(vistab));
/* 1*(BrtBookView *FRT) */
write_record(ba, "BrtEndBookViews");
}
@ -192,7 +218,7 @@ function write_wb_bin(wb, opts) {
write_record(ba, "BrtWbProp", write_BrtWbProp());
/* [ACABSPATH] */
/* [[BrtBookProtectionIso] BrtBookProtection] */
/* write_BOOKVIEWS(ba, wb, opts); */
write_BOOKVIEWS(ba, wb, opts);
write_BUNDLESHS(ba, wb, opts);
/* [FNGROUP] */
/* [EXTERNALS] */

@ -186,6 +186,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var cstys = [], csty, seencol = false;
var arrayf = [];
var rowinfo = [];
var Workbook = { Sheets:[] }, wsprops = {};
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([^\u2603]*?)-->/mg,"");
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
@ -260,6 +261,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
mergecells = [];
arrayf = [];
rowinfo = [];
wsprops = {name:sheetname, Hidden:0};
Workbook.Sheets.push(wsprops);
}
break;
case 'Table':
@ -484,8 +487,15 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
/* WorksheetOptions */
case 'WorksheetOptions': switch(Rn[3]) {
case 'Visible':
if(Rn[0].slice(-2) === "/>"){}
else if(Rn[1]==="/") switch(str.slice(pidx, Rn.index)) {
case "SheetHidden": wsprops.Hidden = 1; break;
case "SheetVeryHidden": wsprops.Hidden = 2; break;
}
else pidx = Rn.index + Rn[0].length;
break;
case 'Unsynced': break;
case 'Visible': break;
case 'Print': break;
case 'Panes': break;
case 'Scale': break;
@ -740,6 +750,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var out = ({}/*:any*/);
if(!opts.bookSheets && !opts.bookProps) out.Sheets = sheets;
out.SheetNames = sheetnames;
out.Workbook = Workbook;
out.SSF = SSF.get_table();
out.Props = Props;
out.Custprops = Custprops;
@ -776,6 +787,22 @@ function write_sty_xlml(wb, opts)/*:string*/ {
/* Styles */
return "";
}
/* WorksheetOptions */
function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
var o = [];
/* PageSetup */
if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) {
/* Visible */
if(!!wb.Workbook.Sheets[idx].Hidden) o.push("<Visible>" + (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden") + "</Visible>");
else {
/* Selected */
for(var i = 0; i < idx; ++i) if(wb.Workbook.Sheets[i] && !wb.Workbook.Sheets[i].Hidden) break;
if(i == idx) o.push("<Selected/>");
}
}
if(o.length == 0) return "";
return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x});
}
/* TODO */
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
if(!cell || cell.v == undefined && cell.f == undefined) return "<Cell></Cell>";
@ -861,7 +888,10 @@ function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
/* Table */
var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : "";
if(t.length > 0) o.push("<Table>" + t + "</Table>");
/* WorksheetOptions */
o.push(write_ws_xlml_wsopts(ws, opts, idx, wb));
return o.join("");
}
function write_xlml(wb, opts)/*:string*/ {

@ -98,6 +98,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var cell_valid = true;
var XFs = []; /* XF records */
var palette = [];
var Workbook = { Sheets:[] }, wsprops = {};
var get_rgb = function getrgb(icv) {
if(icv < 8) return XLSIcv[icv];
if(icv < 64) return palette[icv-8] || XLSIcv[icv];
@ -269,6 +270,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(objects.length > 0) out["!objects"] = objects;
if(colinfo.length > 0) out["!cols"] = colinfo;
if(rowinfo.length > 0) out["!rows"] = rowinfo;
Workbook.Sheets.push(wsprops);
}
if(cur_sheet === "") Preamble = out; else Sheets[cur_sheet] = out;
out = {};
@ -285,6 +287,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(file_depth++) break;
cell_valid = true;
out = {};
if(opts.biff < 5) {
if(cur_sheet === "") cur_sheet = "Sheet1";
range = {s:{r:0,c:0},e:{r:0,c:0}};
@ -301,6 +304,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
colinfo = []; rowinfo = [];
defwidth = defheight = 0;
seencol = false;
wsprops = {Hidden:(Directory[s]||{hs:0}).hs, name:cur_sheet };
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
@ -705,6 +709,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(opts.enc) wb.Encryption = opts.enc;
wb.Metadata = {};
if(country !== undefined) wb.Metadata.Country = country;
wb.Workbook = Workbook;
return wb;
}

@ -114,14 +114,15 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
var i=0;
var sheetRels = ({}/*:any*/);
var path, relsPath;
if(!props.Worksheets) {
//if(!props.Worksheets) {
var wbsheets = wb.Sheets;
props.Worksheets = wbsheets.length;
props.SheetNames = [];
for(var j = 0; j != wbsheets.length; ++j) {
props.SheetNames[j] = wbsheets[j].name;
}
}
//}
var wbext = xlsb ? "bin" : "xml";
var wbrelsfile = 'xl/_rels/workbook.' + wbext + '.rels';

@ -33,8 +33,10 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
/*::if(!wb.Props) throw "unreachable"; */
f = "docProps/app.xml";
wb.Props.SheetNames = wb.SheetNames;
wb.Props.Worksheets = wb.SheetNames.length;
if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
/*:: else if(!wb.Props) throw "unreachable"; */
else wb.Props.SheetNames = wb.Workbook.Sheets.filter(function(x) { return x.Hidden != 2; }).map(function(x) { return x.name; });
wb.Props.Worksheets = wb.Props.SheetNames.length;
zip.file(f, write_ext_props(wb.Props, opts));
ct.extprops.push(f);
add_rels(opts.rels, 3, f, RELS.EXT_PROPS);

31
docbits/73_sheetprops.md Normal file

@ -0,0 +1,31 @@
#### Sheet Visibility
Excel enables hiding sheets in the lower tab bar. The sheet data is stored in
the file but the UI does not readily make it available. Standard hidden sheets
are revealed in the unhide menu. Excel also has "very hidden" sheets which
cannot be revealed in the menu. It is only accessible in the VB Editor!
The visibility setting is stored in the `Hidden` property of the sheet props
array. The values are:
| Value | Definition |
|:-----:|:------------|
| 0 | Visible |
| 1 | Hidden |
| 2 | Very Hidden |
With <https://rawgit.com/SheetJS/test_files/master/sheet_visibility.xlsx>:
```js
> wb.Workbook.Sheets.map(function(x) { return [x.name, x.Hidden] })
[ [ 'Visible', 0 ], [ 'Hidden', 1 ], [ 'VeryHidden', 2 ] ]
```
Non-Excel formats do not support the Very Hidden state. The best way to test
if a sheet is visible is to check if the `Hidden` property is logical negation:
```js
> wb.Workbook.Sheets.map(function(x) { return [x.name, !x.Hidden] })
[ [ 'Visible', true ], [ 'Hidden', false ], [ 'VeryHidden', false ] ]
```

@ -24,6 +24,7 @@
+ [Formulae](README.md#formulae)
+ [Column Properties](README.md#column-properties)
+ [Hyperlinks](README.md#hyperlinks)
+ [Sheet Visibility](README.md#sheet-visibility)
- [Parsing Options](README.md#parsing-options)
* [Input Type](README.md#input-type)
* [Guessing File Type](README.md#guessing-file-type)

@ -17,10 +17,25 @@ type Workbook = {
Custprops?: any;
Themes?: any;
Workbook?: WBWBProps;
SSF?: {[n:number]:string};
cfb?: any;
};
type WorkbookProps = {
SheetNames?: Array<string>;
}
type WBWBProps = {
Sheets: Array<WBWSProp>;
};
type WBWSProp = {
Hidden?: number;
name?: string;
}
interface CellAddress {
r:number;
c:number;

84
test.js

@ -73,6 +73,11 @@ var paths = {
cwxml: dir + 'column_width.xml',
cwxlsx: dir + 'column_width.xlsx',
cwxlsb: dir + 'column_width.xlsx',
svxls: dir + 'sheet_visibility.xls',
svxls5: dir + 'sheet_visibility.xls',
svxml: dir + 'sheet_visibility.xml',
svxlsx: dir + 'sheet_visibility.xlsx',
svxlsb: dir + 'sheet_visibility.xlsb',
swcxls: dir + 'apachepoi_SimpleWithComments.xls',
swcxml: dir + '2011/apachepoi_SimpleWithComments.xls.xml',
swcxlsx: dir + 'apachepoi_SimpleWithComments.xlsx',
@ -616,21 +621,54 @@ function hlink(wb) {
describe('parse features', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
var sheet = 'Sheet1';
var wb1=X.readFile(paths.swcxlsx);
var wb2=X.readFile(paths.swcxlsb);
var wb3=X.readFile(paths.swcxls);
var wb4=X.readFile(paths.swcxml);
describe('sheet visibility', function() {
var wb1, wb2, wb3, wb4, wb5;
var bef = (function() {
wb1 = X.readFile(paths.svxls);
wb2 = X.readFile(paths.svxls5);
wb3 = X.readFile(paths.svxml);
wb4 = X.readFile(paths.svxlsx);
wb5 = X.readFile(paths.svxlsb);
});
if(typeof before != 'undefined') before(bef);
else it('before', bef);
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
if(i > 0) return;
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
it('should detect visible sheets', function() {
[wb1, wb2, wb3, wb4, wb5].forEach(function(wb) {
assert(!wb.Workbook.Sheets[0].Hidden);
});
});
it('should detect all hidden sheets', function() {
[wb1, wb2, wb3, wb4, wb5].forEach(function(wb) {
assert(wb.Workbook.Sheets[1].Hidden);
assert(wb.Workbook.Sheets[2].Hidden);
});
});
it('should distinguish very hidden sheets', function() {
[wb1, wb2, wb3, wb4, wb5].forEach(function(wb) {
assert.equal(wb.Workbook.Sheets[1].Hidden,1);
assert.equal(wb.Workbook.Sheets[2].Hidden,2);
});
});
});
describe('comments', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
var sheet = 'Sheet1';
var wb1=X.readFile(paths.swcxlsx);
var wb2=X.readFile(paths.swcxlsb);
var wb3=X.readFile(paths.swcxls);
var wb4=X.readFile(paths.swcxml);
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
if(i > 0) return;
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
});
});
});
@ -1000,6 +1038,24 @@ describe('roundtrip features', function() {
});
});
});
describe('should preserve sheet visibility', function() { [
['xlml', paths.svxml],
['xlsx', paths.svxlsx],
['xlsb', paths.svxlsb]
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.readFile(w[1]);
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
var wbs1 = wb1.Workbook.Sheets;
var wbs2 = wb2.Workbook.Sheets;
assert.equal(wbs1.length, wbs2.length);
for(var i = 0; i < wbs1.length; ++i) {
assert.equal(wbs1[i].name, wbs2[i].name);
assert.equal(wbs1[i].Hidden, wbs2[i].Hidden);
}
});
});
});
});
function password_file(x){return x.match(/^password.*\.xls$/); }

@ -1 +1 @@
Subproject commit cbafd3a184d0c668aaae1f9f8420ccaed74ad8e5
Subproject commit 7926b8cd03370487edfa9476cad10b532ef22e99

@ -34,6 +34,7 @@ pivot_table_named_range.xlsb
pivot_table_test.xlsb
rich_text_stress.xlsb
row_height.xlsb
sheet_visibility.xlsb
sized_boxen.xlsb
smart_tags_2007.xlsb
sushi.xlsb
@ -199,7 +200,6 @@ apachepoi_DataTableCities.xlsx
apachepoi_DataValidationEvaluations.xlsx
apachepoi_DataValidations-49244.xlsx
# apachepoi_DateFormatTests.xlsx # xlml
apachepoi_DateFormatTests.xlsx.xlsx
apachepoi_ElapsedFormatTests.xlsx
apachepoi_ExcelTables.xlsx
apachepoi_ForShifting.xlsx
@ -288,6 +288,7 @@ apachepoi_workbookProtection_worksheet_protected.xlsx
apachepoi_xlsx-jdbc.xlsx
calendar_stress_test.xlsx.pending
cell_style_simple.xlsx
column_width.xlsx
comments_stress_test.xlsx
custom_properties.xlsx
defined_names_simple.xlsx
@ -407,6 +408,10 @@ roo_time-test.xlsx
roo_whitespace.xlsx
roo_x000D.xlsx
roo_zero-padded-number.xlsx
row_height.xlsx
sheet_visibility.xlsx
sheet_visibility_strict.xlsx
sized_boxen.xlsx
smart_tags_2007.xlsx
spout-xlsx_attack_billion_laughs.xlsx
spout-xlsx_attack_quadratic_blowup.xlsx
@ -578,6 +583,7 @@ roo_time-test.ods
roo_type_excel.ods
roo_type_excelx.ods
roo_whitespace.ods
sheet_visibility.ods
spout-ods_attack_billion_laughs.ods
spout-ods_attack_quadratic_blowup.ods
# spout-ods_file_corrupted.ods
@ -1001,6 +1007,7 @@ apachepoi_xor-encryption-abc.xls.pending
# apachepoi_yearfracExamples.xls # xlml
calendar_stress_test.xls.pending
cell_style_simple.xls
column_width.xls
comments_stress_test.xls
custom_properties.xls
defined_names_simple.xls
@ -1233,6 +1240,10 @@ roo_time-test.xls
roo_type_excelx.xls
# roo_type_openoffice.xls # incorrect baseline
roo_whitespace.xls
row_height.xls
sheet_visibility.xls
sheet_visibility5.xls
sized_boxen.xls
smart_tags_2007.xls
sushi.xls
text_and_numbers.xls
@ -1261,6 +1272,7 @@ RkNumber.xlsx.xml
apachepoi_SampleSS.xml
calendar_stress_test.xml.pending
cell_style_simple.xml
column_width.xml
comments_stress_test.xls.xml
comments_stress_test.xlsb.xml
comments_stress_test.xlsx.xml
@ -1327,12 +1339,15 @@ roo_formula_parse_error.xml
# roo_numbers1.xml # SSF TODO
roo_only_one_sheet.xml
roo_paragraph.xml
# roo_sheet1.xml
# roo_simple_spreadsheet.xml # SSF TODO
# roo_simple_spreadsheet_from_italo.xml # SSF TODO
roo_style.xml
# roo_time-test.xml # SSF TODO
# roo_whitespace.xml # SSF TODO
# roo_sheet1.xml
row_height.xml
sheet_visibility.xml
sized_boxen.xml
smart_tags_2007.xml
sushi.xml
text_and_numbers.xml

@ -63,6 +63,11 @@ var paths = {
cwxml: dir + 'column_width.xml',
cwxlsx: dir + 'column_width.xlsx',
cwxlsb: dir + 'column_width.xlsx',
svxls: dir + 'sheet_visibility.xls',
svxls5: dir + 'sheet_visibility.xls',
svxml: dir + 'sheet_visibility.xml',
svxlsx: dir + 'sheet_visibility.xlsx',
svxlsb: dir + 'sheet_visibility.xlsb',
swcxls: dir + 'apachepoi_SimpleWithComments.xls',
swcxml: dir + '2011/apachepoi_SimpleWithComments.xls.xml',
swcxlsx: dir + 'apachepoi_SimpleWithComments.xlsx',
@ -458,21 +463,54 @@ function hlink(wb) {
describe('parse features', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
var sheet = 'Sheet1';
var wb1=X.read(fs.readFileSync(paths.swcxlsx), {type:"binary"});
var wb2=X.read(fs.readFileSync(paths.swcxlsb), {type:"binary"});
var wb3=X.read(fs.readFileSync(paths.swcxls), {type:"binary"});
var wb4=X.read(fs.readFileSync(paths.swcxml), {type:"binary"});
describe('sheet visibility', function() {
var wb1, wb2, wb3, wb4, wb5;
var bef = (function() {
wb1 = X.read(fs.readFileSync(paths.svxls), {type:"binary"});
wb2 = X.read(fs.readFileSync(paths.svxls5), {type:"binary"});
wb3 = X.read(fs.readFileSync(paths.svxml), {type:"binary"});
wb4 = X.read(fs.readFileSync(paths.svxlsx), {type:"binary"});
wb5 = X.read(fs.readFileSync(paths.svxlsb), {type:"binary"});
});
if(typeof before != 'undefined') before(bef);
else it('before', bef);
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
if(i > 0) return;
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
it('should detect visible sheets', function() {
[/*wb1, wb2, wb3, wb4,*/ wb5].forEach(function(wb) {
assert(!wb.Workbook.Sheets[0].Hidden);
});
});
it('should detect all hidden sheets', function() {
[/*wb1, wb2, wb3, wb4,*/ wb5].forEach(function(wb) {
assert(wb.Workbook.Sheets[1].Hidden);
assert(wb.Workbook.Sheets[2].Hidden);
});
});
it('should distinguish very hidden sheets', function() {
[/*wb1, wb2, wb3, wb4,*/ wb5].forEach(function(wb) {
assert.equal(wb.Workbook.Sheets[1].Hidden,1);
assert.equal(wb.Workbook.Sheets[2].Hidden,2);
});
});
});
describe('comments', function() {
if(fs.existsSync(paths.swcxlsx)) it('should have comment as part of cell properties', function(){
var X = require(modp);
var sheet = 'Sheet1';
var wb1=X.read(fs.readFileSync(paths.swcxlsx), {type:"binary"});
var wb2=X.read(fs.readFileSync(paths.swcxlsb), {type:"binary"});
var wb3=X.read(fs.readFileSync(paths.swcxls), {type:"binary"});
var wb4=X.read(fs.readFileSync(paths.swcxml), {type:"binary"});
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
if(i > 0) return;
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
});
});
});
@ -841,6 +879,24 @@ describe('roundtrip features', function() {
});
});
});
describe('should preserve sheet visibility', function() { [
['xlml', paths.svxml],
['xlsx', paths.svxlsx],
['xlsb', paths.svxlsb]
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.read(fs.readFileSync(w[1]), {type:"binary"});
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"binary"}), {type:"binary"});
var wbs1 = wb1.Workbook.Sheets;
var wbs2 = wb2.Workbook.Sheets;
assert.equal(wbs1.length, wbs2.length);
for(var i = 0; i < wbs1.length; ++i) {
assert.equal(wbs1[i].name, wbs2[i].name);
assert.equal(wbs1[i].Hidden, wbs2[i].Hidden);
}
});
});
});
});
function password_file(x){return x.match(/^password.*\.xls$/); }

File diff suppressed because one or more lines are too long

@ -39,6 +39,11 @@
./test_files/column_width.xml
./test_files/column_width.xlsx
./test_files/column_width.xlsx
./test_files/sheet_visibility.xls
./test_files/sheet_visibility.xls
./test_files/sheet_visibility.xml
./test_files/sheet_visibility.xlsx
./test_files/sheet_visibility.xlsb
./test_files/apachepoi_SimpleWithComments.xls
./test_files/2011/apachepoi_SimpleWithComments.xls.xml
./test_files/apachepoi_SimpleWithComments.xlsx

@ -80,6 +80,13 @@ console.log("JSON Data: "); console.log(XLSX.utils.sheet_to_json(ws, {header:1})
console.log("Worksheet Model:")
console.log(ws);
/* TEST: hidden sheets */
wb.SheetNames.push("Hidden");
wb.Sheets["Hidden"] = XLSX.utils.aoa_to_sheet(["Hidden".split(""), [1,2,3]]);
wb.Workbook = {Sheets:[]};
wb.Workbook.Sheets[1] = {Hidden:1};
/* write file */
XLSX.writeFile(wb, 'sheetjs.xlsx', {bookSST:true});
XLSX.writeFile(wb, 'sheetjs.xlsm');

@ -1915,6 +1915,7 @@ function prep_blob(blob, pos/*:number*/) {
}
function parsenoop(blob, length/*:number*/) { blob.l += length; }
function parsenooplog(blob, length/*:number*/) { if(typeof console != 'undefined') console.log(blob.slice(blob.l, blob.l + length)); blob.l += length; }
function writenoop(blob, length/*:number*/) { blob.l += length; }
@ -3070,8 +3071,8 @@ function parse_ext_props(data, p) {
var v = parseVector(q.HeadingPairs);
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
var idx = 0, len = 0;
for(var i = 0; i !== v.length; ++i) {
len = +(v[++i].v);
for(var i = 0; i !== v.length; i+=2) {
len = +(v[i+1].v);
switch(v[i].v) {
case "Worksheets":
case "工作表":
@ -3104,6 +3105,7 @@ function parse_ext_props(data, p) {
idx += len;
}
}
return p;
}
@ -9208,7 +9210,7 @@ function write_ws_xml_data(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb)) != null) r.push(cell);
}
if(r.length > 0) {
var params = {r:rr}
var params = ({r:rr}/*:any*/);
if(rows && rows[R]) {
var row = rows[R];
if(row.hidden) params.hidden = 1;
@ -10086,7 +10088,7 @@ var WBViewDef = [
/* 18.2.19 (CT_Sheet) Defaults */
var SheetDef = [
['state', 'visible']
//['state', 'visible']
];
/* 18.2.2 (CT_CalcPr) Defaults */
@ -10199,7 +10201,15 @@ function parse_wb_xml(data, opts)/*:WorkbookFile*/ {
/* 18.2.20 sheets CT_Sheets 1 */
case '<sheets>': case '</sheets>': break; // aggregate sheet
/* 18.2.19 sheet CT_Sheet + */
case '<sheet': delete y[0]; y.name = unescapexml(utf8read(y.name)); wb.Sheets.push(y); break;
case '<sheet':
switch(y.state) {
case "hidden": y.Hidden = 1; break;
case "veryHidden": y.Hidden = 2; break;
default: y.Hidden = 0;
}
delete y.state;
y.name = unescapexml(utf8read(y.name));
delete y[0]; wb.Sheets.push(y); break;
case '</sheet>': break;
/* 18.2.15 functionGroups CT_FunctionGroups ? */
@ -10309,8 +10319,17 @@ function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ {
o[o.length] = WB_XML_ROOT;
o[o.length] = (writextag('workbookPr', null, {date1904:safe1904(wb), codeName:"ThisWorkbook"}));
o[o.length] = "<sheets>";
for(var i = 0; i != wb.SheetNames.length; ++i)
o[o.length] = (writextag('sheet',null,{name:escapexml(wb.SheetNames[i].substr(0,31)), sheetId:""+(i+1), "r:id":"rId"+(i+1)}));
var sheets = wb.Workbook && wb.Workbook.Sheets || [];
for(var i = 0; i != wb.SheetNames.length; ++i) {
var sht = ({name:escapexml(wb.SheetNames[i].substr(0,31))}/*:any*/);
sht.sheetId = ""+(i+1);
sht["r:id"] = "rId"+(i+1);
if(sheets[i]) switch(sheets[i].Hidden) {
case 1: sht.state = "hidden"; break;
case 2: sht.state = "veryHidden"; break;
}
o[o.length] = (writextag('sheet',null,sht));
}
o[o.length] = "</sheets>";
if(o.length>2){ o[o.length] = '</workbook>'; o[1]=o[1].replace("/>",">"); }
return o.join("");
@ -10318,7 +10337,7 @@ function write_wb_xml(wb/*:Workbook*/, opts/*:?WriteOpts*/)/*:string*/ {
/* [MS-XLSB] 2.4.301 BrtBundleSh */
function parse_BrtBundleSh(data, length/*:number*/) {
var z = {};
z.hsState = data.read_shift(4); //ST_SheetState
z.Hidden = data.read_shift(4); //hsState ST_SheetState
z.iTabID = data.read_shift(4);
z.strRelID = parse_RelID(data,length-8);
z.name = parse_XLWideString(data);
@ -10326,7 +10345,7 @@ function parse_BrtBundleSh(data, length/*:number*/) {
}
function write_BrtBundleSh(data, o) {
if(!o) o = new_buf(127);
o.write_shift(4, data.hsState);
o.write_shift(4, data.Hidden);
o.write_shift(4, data.iTabID);
write_RelID(data.strRelID, o);
write_XLWideString(data.name.substr(0,31), o);
@ -10455,7 +10474,8 @@ function parse_wb_bin(data, opts)/*:WorkbookFile*/ {
function write_BUNDLESHS(ba, wb, opts) {
write_record(ba, "BrtBeginBundleShs");
for(var idx = 0; idx != wb.SheetNames.length; ++idx) {
var d = { hsState: 0, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: wb.SheetNames[idx] };
var viz = wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx] && wb.Workbook.Sheets[idx].Hidden || 0;
var d = { Hidden: viz, iTabID: idx+1, strRelID: 'rId' + (idx+1), name: wb.SheetNames[idx] };
write_record(ba, "BrtBundleSh", write_BrtBundleSh(d));
}
write_record(ba, "BrtEndBundleShs");
@ -10473,9 +10493,34 @@ function write_BrtFileVersion(data, o) {
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.4.298 BrtBookView */
function write_BrtBookView(idx, o) {
if(!o) o = new_buf(29);
o.write_shift(-4, 0);
o.write_shift(-4, 460);
o.write_shift(4, 28800);
o.write_shift(4, 17600);
o.write_shift(4, 500);
o.write_shift(4, idx);
o.write_shift(4, idx);
var flags = 0x78;
o.write_shift(1, flags);
return o.length > o.l ? o.slice(0, o.l) : o;
}
/* [MS-XLSB] 2.1.7.60 Workbook */
function write_BOOKVIEWS(ba, wb, opts) {
/* required if hidden tab appears before visible tab */
if(!wb.Workbook || !wb.Workbook.Sheets) return;
var sheets = wb.Workbook.Sheets;
var i = 0, vistab = -1, hidden = -1;
for(; i < sheets.length; ++i) {
if(!sheets[i] || !sheets[i].Hidden && vistab == -1) vistab = i;
else if(sheets[i].Hidden == 1 && hidden == -1) hidden = i;
}
if(hidden > vistab) return;
write_record(ba, "BrtBeginBookViews");
write_record(ba, "BrtBookView", write_BrtBookView(vistab));
/* 1*(BrtBookView *FRT) */
write_record(ba, "BrtEndBookViews");
}
@ -10509,7 +10554,7 @@ function write_wb_bin(wb, opts) {
write_record(ba, "BrtWbProp", write_BrtWbProp());
/* [ACABSPATH] */
/* [[BrtBookProtectionIso] BrtBookProtection] */
/* write_BOOKVIEWS(ba, wb, opts); */
write_BOOKVIEWS(ba, wb, opts);
write_BUNDLESHS(ba, wb, opts);
/* [FNGROUP] */
/* [EXTERNALS] */
@ -10790,6 +10835,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var cstys = [], csty, seencol = false;
var arrayf = [];
var rowinfo = [];
var Workbook = { Sheets:[] }, wsprops = {};
xlmlregex.lastIndex = 0;
str = str.replace(/<!--([^\u2603]*?)-->/mg,"");
while((Rn = xlmlregex.exec(str))) switch(Rn[3]) {
@ -10864,6 +10910,8 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
mergecells = [];
arrayf = [];
rowinfo = [];
wsprops = {name:sheetname, Hidden:0};
Workbook.Sheets.push(wsprops);
}
break;
case 'Table':
@ -11088,8 +11136,15 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
/* WorksheetOptions */
case 'WorksheetOptions': switch(Rn[3]) {
case 'Visible':
if(Rn[0].slice(-2) === "/>"){}
else if(Rn[1]==="/") switch(str.slice(pidx, Rn.index)) {
case "SheetHidden": wsprops.Hidden = 1; break;
case "SheetVeryHidden": wsprops.Hidden = 2; break;
}
else pidx = Rn.index + Rn[0].length;
break;
case 'Unsynced': break;
case 'Visible': break;
case 'Print': break;
case 'Panes': break;
case 'Scale': break;
@ -11344,6 +11399,7 @@ function parse_xlml_xml(d, opts)/*:Workbook*/ {
var out = ({}/*:any*/);
if(!opts.bookSheets && !opts.bookProps) out.Sheets = sheets;
out.SheetNames = sheetnames;
out.Workbook = Workbook;
out.SSF = SSF.get_table();
out.Props = Props;
out.Custprops = Custprops;
@ -11380,6 +11436,22 @@ function write_sty_xlml(wb, opts)/*:string*/ {
/* Styles */
return "";
}
/* WorksheetOptions */
function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
var o = [];
/* PageSetup */
if(wb && wb.Workbook && wb.Workbook.Sheets && wb.Workbook.Sheets[idx]) {
/* Visible */
if(!!wb.Workbook.Sheets[idx].Hidden) o.push("<Visible>" + (wb.Workbook.Sheets[idx].Hidden == 1 ? "SheetHidden" : "SheetVeryHidden") + "</Visible>");
else {
/* Selected */
for(var i = 0; i < idx; ++i) if(wb.Workbook.Sheets[i] && !wb.Workbook.Sheets[i].Hidden) break;
if(i == idx) o.push("<Selected/>");
}
}
if(o.length == 0) return "";
return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x});
}
/* TODO */
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
if(!cell || cell.v == undefined && cell.f == undefined) return "<Cell></Cell>";
@ -11465,7 +11537,10 @@ function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
/* Table */
var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : "";
if(t.length > 0) o.push("<Table>" + t + "</Table>");
/* WorksheetOptions */
o.push(write_ws_xlml_wsopts(ws, opts, idx, wb));
return o.join("");
}
function write_xlml(wb, opts)/*:string*/ {
@ -11584,6 +11659,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
var cell_valid = true;
var XFs = []; /* XF records */
var palette = [];
var Workbook = { Sheets:[] }, wsprops = {};
var get_rgb = function getrgb(icv) {
if(icv < 8) return XLSIcv[icv];
if(icv < 64) return palette[icv-8] || XLSIcv[icv];
@ -11755,6 +11831,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(objects.length > 0) out["!objects"] = objects;
if(colinfo.length > 0) out["!cols"] = colinfo;
if(rowinfo.length > 0) out["!rows"] = rowinfo;
Workbook.Sheets.push(wsprops);
}
if(cur_sheet === "") Preamble = out; else Sheets[cur_sheet] = out;
out = {};
@ -11771,6 +11848,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(file_depth++) break;
cell_valid = true;
out = {};
if(opts.biff < 5) {
if(cur_sheet === "") cur_sheet = "Sheet1";
range = {s:{r:0,c:0},e:{r:0,c:0}};
@ -11787,6 +11865,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
colinfo = []; rowinfo = [];
defwidth = defheight = 0;
seencol = false;
wsprops = {Hidden:(Directory[s]||{hs:0}).hs, name:cur_sheet };
} break;
case 'Number': case 'BIFF2NUM': case 'BIFF2INT': {
@ -12191,6 +12270,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ {
if(opts.enc) wb.Encryption = opts.enc;
wb.Metadata = {};
if(country !== undefined) wb.Metadata.Country = country;
wb.Workbook = Workbook;
return wb;
}
@ -14340,14 +14420,15 @@ function parse_zip(zip/*:ZIP*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
var i=0;
var sheetRels = ({}/*:any*/);
var path, relsPath;
if(!props.Worksheets) {
//if(!props.Worksheets) {
var wbsheets = wb.Sheets;
props.Worksheets = wbsheets.length;
props.SheetNames = [];
for(var j = 0; j != wbsheets.length; ++j) {
props.SheetNames[j] = wbsheets[j].name;
}
}
//}
var wbext = xlsb ? "bin" : "xml";
var wbrelsfile = 'xl/_rels/workbook.' + wbext + '.rels';
@ -14428,8 +14509,10 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
/*::if(!wb.Props) throw "unreachable"; */
f = "docProps/app.xml";
wb.Props.SheetNames = wb.SheetNames;
wb.Props.Worksheets = wb.SheetNames.length;
if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
// $FlowIgnore
else wb.Props.SheetNames = wb.Workbook.Sheets.filter(function(x) { return x.Hidden != 2; }).map(function(x) { return x.name; });
wb.Props.Worksheets = wb.Props.SheetNames.length;
zip.file(f, write_ext_props(wb.Props, opts));
ct.extprops.push(f);
add_rels(opts.rels, 3, f, RELS.EXT_PROPS);

115
xlsx.js

@ -1864,6 +1864,7 @@ function prep_blob(blob, pos) {
}
function parsenoop(blob, length) { blob.l += length; }
function parsenooplog(blob, length) { if(typeof console != 'undefined') console.log(blob.slice(blob.l, blob.l + length)); blob.l += length; }
function writenoop(blob, length) { blob.l += length; }
@ -3018,8 +3019,8 @@ function parse_ext_props(data, p) {
var v = parseVector(q.HeadingPairs);
var parts = parseVector(q.TitlesOfParts).map(function(x) { return x.v; });
var idx = 0, len = 0;
for(var i = 0; i !== v.length; ++i) {
len = +(v[++i].v);
for(var i = 0; i !== v.length; i+=2) {
len = +(v[i+1].v);
switch(v[i].v) {
case "Worksheets":
case "工作表":
@ -3052,6 +3053,7 @@ function parse_ext_props(data, p) {
idx += len;
}
}
return p;
}
@ -9153,7 +9155,7 @@ function write_ws_xml_data(ws, opts, idx, wb, rels) {
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb))